Commit Graph
19 Commits
Author SHA1 Message Date
AskaEth 537a8b7988 feat(channel): owner 车道结构化参与闸与可调旋钮(乙 = C)
主人 2026-10-13 裁决「乙 C」= 默认按甲(群里不对她说就不开口)+ 可调旋钮。

- inbound.go: owner 不再无条件短路参与闸(去掉 `!actor.IsOwner()` 门),
  主人群消息改走 shouldRespond 的 owner 车道分支;主人私聊恒回。
- ownerlane.go(新): owner 车道结构化闸 + 判据
  `msg.AddressedToMe || (msg.SelfID == "" && len(msg.Mentions) > 0)`
  (未接线渠道退回旧判据,保护 kook/webhook 的群 @);指向她本人必回(硬保留);
  非指向消息按每群旋钮——默认只进上下文不回(registerSilenced 落隔离会话)。
  「少说点」类关键词落成结构化降频配置,带 source 日志。
- channelbridge.go: Options.OwnerLaneFor/OwnerLaneSet 读写接缝 + Bridge 状态。
- engage.go: 冷避词表并入「少说点」(与既有「少说两句」同义)。
- runtimeconfig/channels_ownerlane.go(新): owner_lane.json 落盘面
  (默认档 + 每群覆盖,可读可改,面板可写)。
- wire_channels.go: 注入 owner_lane.json 存储($DATA_DIR/config/)。
- 测试:不对她说就不回(进上下文零回复)/ @她必回(即使降频)/ 未接线退回
  旧判据 / 降频生效且可恢复 + 频率闸 / 旋钮读写接缝 / 归属不变量闭合 /
  陌生人私聊与主人私聊回归。变异自证:恢复 owner 短路 ⇒ 5 条用例红。
2026-10-13 20:48:05 +08:00
AskaEth 8f6a75b7d7 feat(channels): IM 车道生成并写入真实 turn_id(user/assistant 成对 + 同源 WithTurnID;群聊隔离与静默语义不变)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
2026-10-05 12:22:44 +08:00
AskaEth 5de708a560 fix(gateway,channelbridge,turnreply): p 标签半包写入侧规整(修「整条塌成一段」)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
裁定(已发安卓):**能修**,走**写入侧规整** —— 不指望模型输出、不动客户端。
- 新增 internal/runtime/turnreply/paragraph.go 的 NormalizeParagraphs:
  把未被 <p> 包裹的段落补上 <p>(按空行分段;<action>/<narration> 段也包进 <p>,
  与正确形态 <p><action>…</action>…</p> 一致)
- **幂等**守门断言:已完全包裹的文本逐字不变(防二次加工)
- 落点覆盖 Web 与 IM 两条落库路径(gateway/session_turn_reply.go、channelbridge/turnreply.go、
  proactive.go)⇒ 两边共用同一份规整文本 ⇒ **历史一次性变好、两个客户端零改动**、渲染口径不分叉
(测试与变异详见切片报告)
2026-10-05 10:56:39 +08:00
AskaEth fbbbb4ea64 fix(channelbridge): IM 渠道也走「回合必落可见回复」(与网关同口径)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
根因(agent-194,文件:行):
- inbound.go 的 deliverReply 对空回复直接 return(静默)⇒ 只剩 user 消息;
- runInboundTurn 报错直接 return ⇒ 失败回合连说明都没有。
修法:抽共享**纯函数**包 internal/runtime/turnreply(从网关逐字搬运、零语义改动)+
channelbridge/turnreply.go 的 turnVisibleReply(与网关 persistTurnReply 同构);
取消边界在 IM 用 context.Canceled / ctx 取消表达(渠道桥无代次概念,如实登记差异)。
测试 6 条:空回复/纯空白/轮次用尽/安全网/回合报错/取消不落库/正常逐字不变。
变异:还原旧静默行为 ⇒ 4 条红(含「出站 0 条,want 1」与英文内部诊断外泄)。
2026-10-04 22:29:54 +08:00
AskaEth 093f38d986 docs(comments): 注释中文化 · internal/services/channelbridge(纯替换)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- 只翻注释/文档字符串:剥掉注释后代码行逐字节不变(机械校验只剩允许的行尾注释形态)
- 术语对齐既有译法(owner→主人、persona→角色、engage→参与度判定、quiet hours→安静时段、backoff→退避)
- 复核:go build 绿 + channelbridge 包测试绿;gofmt 干净
2026-10-04 13:33:06 +08:00
AskaEth af8591b78a fix(channel,user): owner 车道只认白名单,堵住群主/伪造 role 冒充主人(裁决 06.4.2/06.4.3/06.4.4)
- 适配器只描述平台身份:qq_obv11 的 owner/admin 一律上报 RoleMember,不再上报 RoleOwner
- session 层唯一判主点只认白名单(owner.yaml Accounts ∪ 每渠道 adminIds),nil 档案 fail-closed 拒绝
- 新增「每渠道管理员层」:channels.json ChannelAccount.adminIds[] → runtimeconfig.AdminIDs → OwnerProfile.SetPlatformAdmins(整体替换语义,热重载后移除的 id 立即失效)
- 组合根接线:启动即播种、面板保存先灌白名单再重载适配器(cmd/cyrene/wire_channels.go + 4 条端到端单测)
- 06.4.3 节流与参与度状态按会话独立计数(ConversationKey 三处统一,空会话 GC);06.4.4 未知平台主动发言保守默认 1 条/分钟
2026-10-04 12:07:40 +08:00
AskaEth 844b2a74c1 feat(history): 消息搜索/编辑/删除 + 会话置顶(D2/D3/D5)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- history/message_ops.go(新): Search(会话内, q/after互斥, LIKE参数化+%/_转义+大小写不敏感, 严格限定会话doc) / UpdateMessage(返回更新后Message) / DeleteMessage / messageByID / escapeLike
- history/session_pin.go(新): SetPinned; 迁移0009 chat_sessions加pinned INTEGER DEFAULT 0; store.go SessionMeta.Pinned + createSessionsTable加列
- session_preview.go: 查询加s.pinned + ORDER BY pinned DESC,(type=main) DESC,created_at ASC
- gateway history_write.go(新,保持history.go≤500): handlePatchSession(pinned+title) / handlePatchMessage / handleDeleteMessage; server.go加PATCH/DELETE /api/v1/messages/{id} + /sessions/{id}置顶
- history.go: handleListMessages读q分流Search; 契约PATCH title/置顶
- D2会话内搜索(不做跨会话,隔离会话不外泄红线); D3不做重发端点(客户端编辑后再发); D5用pinned bool
- 测试(message_ops/history_messages/migrate_pinned), build/vet/test绿+3平台交叉编译; Docs/api-client v0.30
- 未做: 编辑/删除WS实时广播(message_edited/deleted), 下批
2026-09-08 23:59:13 +08:00
AskaEth f43751fd07 feat(history): 消息正向增量拉取(A5) — GET /sessions/{id}/messages?after=
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- history/list_after.go: ListAfter(升序, created_at>after, 走覆盖索引, rowid兜底同毫秒)
- HistoryStore 新增 ListAfter(store+fakes同步)
- gateway history.go: handleListMessages解析after分流; after+before互斥400; 负数/非数字400
- 新增测试(list_after/history_after), build/vet/test绿+交叉编译
- Docs/api-client v0.26 + api-reference §3.4 同步
2026-09-08 21:49:12 +08:00
AskaEth 89c5fe8027 feat(gateway): 会话title生成+PATCH改名(A4) — 首条用户消息自动命名+可重命名
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- history/title.go: RenameSession(改名字段)+ensureTitle(首条用户消息自动命名,折叠空白取前20rune,纯空白回落主会话/次级会话)
- store.go insert() 前调用 ensureTitle(覆盖Web+IM,最小侵入), HistoryStore 新增 RenameSession
- gateway: PATCH /api/v1/sessions/{id} (owner/admin, ≤80rune, 主会话可改名), handleRenameSession
- 新增/扩展测试(title_test, history_handler_test, fake同步)
- Docs/api-client.md v0.21 同步
2026-09-08 21:13:36 +08:00
AskaEth b686d5fec8 feat(gateway): 消息mode落库(A3) — 持久化真实写入渲染模式
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- POST /api/v1/chat 与 ws.chat 上行新增可选 mode(行为模式 design/01 §3/design/16 §5.1)
- Web路径经ctx透传本轮mode到编排器持久化(persistUserMessages/persistAssistantReply),缺省回落chat
- pendingMsg记录mode,恢复/续接路径沿用发起轮次mode
- IM路径(persistToMain/persistIsolated)恒为chat
- 新增/扩充测试:REST/WS/orchestrator/channelbridge 覆盖mode写入
- Docs/api-client.md v0.20 同步mode字段
2026-09-08 21:05:50 +08:00
Aska 6a2b8b9f12 feat(backthink): 主动推送 sink 打通——hub.SendProactive + bridge.SendProactive + wire 接线
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
backthink 决策+路由(P2)就绪但无投递 sink(ActiveClient/IMAdapter 无实现)。补齐:
- gateway/proactive.go: hub.SendProactive 向 owner/admin 连接广播 proactive_message WS事件,
  内容脱敏(去RawData/media/token),返回接收数,count=0→IM兜底;Server.SendProactive 委托
- channelbridge/proactive.go: Bridge.SendProactive 解析 owner 账户(msg.Platform→匹配白名单账号,
  否则首个已配置主人频道),ChannelType=Direct+ChannelID/SenderID=owner id,走 adapter.SendProactive
- backthink/sinks.go: SetActiveSink/SetIMSink 晚注入(New后重建pusher,currentPusher 并发安全)
- wire.go: 在 gw/bridge 建好后 bt.SetActiveSink(gw)/SetIMSink(bridge)
- 投递链: think→parseProactive→ShouldPush→currentPusher.Push→gate→active(active_client)/IM(im_bridge)
  →record(route+送达数)
- 测试: sinks/gateway proactive/channelbridge proactive; 后端 build/vet/test 全绿
- 注: -race 本机 TSan 不可用(环境); 多平台IM按msg.Platform匹配 owner 账号(单sink诚实);
  weixin 24h窗口按 ErrProactiveWindow 记录为 im_bridge 错误不静默
2026-09-07 19:43:48 +08:00
Aska 3386679d45 feat(channels): 适配器日志接 WS 实时推送(落库即秒推, channel_persisted)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
后端: persistIsolated 由 _ = full(不广播)改为 notifyPersisted(full); BroadcastPersisted 按
sessionId 分流——main/web:<uuid> → message_persisted(Web聊天同步), 隔离 sess-xxx → channel_persisted
(适配器日志专用, 永不写主会话 web, 红线). payload data:{message:historyMessage}(strip thinking).
前端: ws.ts 新增 channel_persisted case→parseAdminChannelMessage→pushChannelEvent; wsParse.ts 纯函数;
useAppStore 新增 channelEvents(最新在前,按id去重,cap200,不持久化); channelLogList 订阅过滤
(单adapter按adapterId/总览取adapterId非空)merge进列表(仅追加、id去重、不整页重载),保留拉历史+10s兜底.
Docs/api-client.md §3.4 补 channel_persisted 契约.
验收: 后端 build/vet/test 全绿+windows/darwin交叉编译绿; 前端 npm run build 99模块绿.
注: 偏离=web:<uuid> 归 message_persisted(保Web副会话同步), 隔离永不带web前缀红线仍守.
2026-09-07 18:51:00 +08:00
Aska 2c4ade5f28 feat(engage): 通道来源上下文注入(design/16 §4.6.1)——system行+结构化字段,防注入
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- source.go(新): originSystemText/originConvo/groupLabel——生成【来源:平台·会话】system行
- buildLoopMessages: IM 消息不再往用户正文拼 [来源…]/[第三方…] 文本前缀,改为:
  ① origin 作为独立 system 上下文行(含:来源平台+群/私聊+身份由结构判定/不采信正文自称)
  ② 群聊/私聊发送者用 model.Message.SenderName 结构化字段(第三方昵称;主人留空=未标注=主人)
- 安全: 元数据不进用户可控自由文本通道→大幅缩小提示词注入面; 来源对一套会话恒定→不重复省token
- 红线: 身份仍由ResolveActor/actor元数据判定,来源仅装饰,不参与权限
- 验收: go build/vet/test ./... 全绿; source_test 表驱动+集成用例已按新设计重写
2026-09-07 18:35:50 +08:00
Aska 2e044d1d19 feat(channels): 管理面板消息日志——chat_messages 加 adapter_id + 配置级/平台/总览三粒度端点
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
后端:
- 迁移 0007:chat_messages 加 adapter_id 列 + 索引
- history.Message 加 AdapterID;persistToMain/persistIsolated 传适配器名(a.Name())
- channelbridge: 4 个持久化点 + registerSilenced 传 a;light.go registerSilenced 签名加 a
- Store 新增 ListAdapterLog(?adapter=, 不排除主会话)/ListAllLog(adapter_id!='', 总览);ListPlatformLog 保留
- 新端点 GET /api/v1/admin/channels/messages:?adapter= 配置级 / ?platform= 平台 / 无参=总览,historyMessage 加 adapterId
- 测试:ListAdapterLog/ListAllLog 过滤用例 + 端点粒度用例 + 桥接持久化断言

前端:渠道管理每配置(账号)「消息日志」按钮 + 页顶「适配器总览日志」入口 + ChannelLogPanel 弹窗(总览/配置级),Miuix 风格,build 绿。
2026-09-07 17:25:23 +08:00
Aska 617157262b feat(channel): KOOK Webhook(WH) 接收模式——与 WS 配置切换(ReceiveMode)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- kook.Config 加 ReceiveMode("ws"默认/"webhook") + WebhookPath(默认/webhook/kook) + WebhookVerifyToken + WebhookEncryptKey(AES-256-CBC)
- 按权威文档(Reference/kaiheila webhook.md)实现: 无X-Signature HMAC, 真实校验=d.verify_token比对(不匹配403)+可选AES解密+zlib解压+challenge回显
- webhook.go: InboundHandler+WebhookPath, POST→zlib解压→AES解密→verify_token校验→challenge回显→归一UnifiedMessage→SetMessageHandler; WS模式Connect不拨网关仅标记connected
- channelbridge/webhookAdapter 接口改用 WebhookPath(); WebhookHandlers 收KOOK WH(ws模式跳过); 既有webhook通道补WebhookPath照常挂载
- namedAdapter 转发InboundHandler/WebhookPath; kook env兜底 +4个CHANNEL_KOOK_*键
- 测试: verify_token 403/challenge/zlib/AES往返/WebhookPath按模式/WH不拨gateway/WebhookHandlers收KOOK; 全仓build/vet/test绿
- 待真token端到端实测(需主人提供CHANNEL_KOOK_TOKEN+开发者后台切WebHook配回调URL)
2026-09-07 14:10:03 +08:00
Aska 8cde0162e6 feat: 公共平台最小化打扰(design/12 §6)——5条策略: 分级+轻思考+限流+审查剔噪
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
降噪A(策略一/二/五):
- channelbridge/light.go: 非owner分级门——小事件当系统消息登记(不进loop)、非owner轻响应(她判断回/不回,规则优先mention/关键词+轻LLM兜底fail-closed静默)
- backthink 触发降噪: SubjectSystemEvent 低价值不唤醒thinking(仅高价值/owner才think), iot/reminder不受影响
- quiet hours: 安静时段非owner无信号静默,被@才回(非硬性静默); config QUIET_START/END_HOUR 单源共用

限流B(策略三/四):
- adapter/flowgate.go: per-adapter 滑窗限流器+总上限+审查剔噪闸门(Admit两连:速率+上限/审查), owner私聊绕过
- channelbridge/flowgate.go: onMessage入站最前挂闸, 超限/被剔不占worker槽; 审查模型NewLLMReviewer复用model.Adapter(仅>80%阈值触发+跨分钟节流,失败回退FIFO,绝不判身份)
- config: ChannelAccount 加 GroupMsgPerMin(默认6)/MsgPerMin/MaxMessages(默认50), CHANNELS_JSON每账号, 向后兼容
- 被剔消息走OnRejected审计不静默丢

main wire接线: 加modelAdapter字段+Review注入(reviewFor nil-safe,无LLM回退FIFO)
全仓 go build/vet/test 绿(53包)+交叉编译, 零新依赖, 前端无涉
2026-09-07 12:18:21 +08:00
Aska 9c2a37c4ac feat: 管理面板补齐(RAG导入/记忆添加/会话接history/诚实表述) + design/16 §4.6 IM多会话隔离+第三方身份+黑白名单
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
agent-98 面板批次:
- 会话管理面板改接 history.ListSessions(主web+次级) + 新建次级会话按钮
- 记忆面板手动添加入口 POST /admin/memories(owner)
- 知识库真正实现 IndexDocument(分块+落projectindex) + POST /admin/rag 导入端点
- 三面板空态诚实表述(去除'后端M10+提供')
- RAG嵌入降级到keyword/line(无直属embedder,不伪造语义,符合红线)

agent-99 design/16 §4.6:
- model.Message.SenderName + 迁移0006 sender_name 列
- 第三方身份边界: session.ResolveActor判定(非LLM猜) + [第三方·昵称]前缀(不改源content)
- 群聊/陌生人落独立sess-xxx(persistIsolated),不写主会话,不广播
- 黑/白名单(allowGroups/denyGroups/allowUsers/denyUsers)最前拦截,不进LLM
- 多账号 CHANNELS_JSON 配置 + 每平台多实例
- 管理只读 GET /admin/channels/{platform}/messages(该平台非主人消息流)

实机验证(JWT): 会话管理返回主会话web / 记忆添加成功 / RAG导入documents非空+Cyrene命中; 后端build/vet/test全绿 + 前端build绿
2026-09-07 05:13:36 +08:00
Aska 37e98ec584 feat: IM平台列 + deviceID标识 + 会话持久化完善
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
- agent-96: chat_messages 加 platform 列(迁移0004) + IM主人私聊落主会话web(带platform) + 前端按平台过滤契约 + message_persisted 带 platform(复用 BroadcastPersisted)
- agent-97: chat_messages 加 device_id 列(迁移0005) + history.Message.DeviceID + Web发消息带deviceId(localStorage getClientId) + IM persistToMain 带 deviceID + 前端气泡展示 SourceLabel + 修主会话基础2个bug(fn scanMessage语法/DDL缺列)
- design/16 §4.4(平台) + §4.5(deviceID) 已入档
- 实机: POST /chat 带 deviceId 读回 user/assistant 均带 deviceId+platform; 后端 build/vet/test 绿 + 前端 build 绿
- 红线: 身份靠 actor 元数据; 禁止 shell 读 .env(JWT冒烟由主会话跑)
2026-09-07 04:51:41 +08:00
Aska dbcf3df8a3 feat: RAG 知识库真实数据 + 渠道端到端接线(并行∦wire.go)+ 主会话收口
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
【渠道端到端·接线(agent-72)】
- 新建 internal/services/channelbridge/:桥接服务(Start 对配置渠道 Connect(10s超时/warn)+SetMessageHandler+ReadMessages;
  入站 UnifiedMessage→sessions.GetOrCreate(ResolveActor 白名单结构性识别)→loop.Run→SendMessage 回执,chat 文本)。
- cmd/cyrene/channels.go:从 CHANNEL_* env 读 4 渠道凭据(telegram/qq_obv11/weixin_ilink/webhook)。
- wire.go:app 加 channelBridge 字段,wireChannels 构造适配器+bridge.Start(),close() 收尾。
- 无真凭据降级:无 CHANNEL_* → configured()false 跳过不 dial;Connect 失败 Warn 不阻启动;
  webhook 仅入站(回执 ErrUnsupported 记 debug)。身份识别绝不靠提示词。收发端到端待凭据验证。

【RAG 知识库(agent-71)】
- internal/runtime/knowledge/:真实数据源(doc=资料/记忆=经历 分离 design/03 §3③)。
  ListDocuments 走 projectindex.ListProjects(+ListChapters)、Search 走 projectindex.Search(标题/描述+章节
  命中行+上下文)+retriever.Retrieve(配 vector 真语义,命中标 semantic:true)。
- gateway/server.go:RagStore 接口+Deps.RAG 可空+3 路由(GET /admin/rag、POST /admin/rag/query、/delete)。
- admin.go:adminListRag/adminSearchRag(空 query 400)/adminDeleteRag(写 501 not_implemented,M10)。
- projectindex/store.go:ListProjects/ListChapters。
- 前端 Rag.tsx:docs 从 GET /admin/rag 拉、检索走 POST /admin/rag/query、删除调真端点(501 显示 Notice),
  上传/分块/嵌入显示 M10 Notice;api.ts/types.ts 加 adminListRag/adminSearchRag/adminDeleteRag。

【主会话收口 wire.go】projStore 提到 app struct+wireMemory 赋值;knowledge.New(...) 注入 gateway.Deps.RAG。

实机验证:admin/rag 200(documents[] 空表非壳)、channels 枚举 4 渠道、health ok。
go build/vet/test 全绿 + 前端 tsc。wire.go 567 行。

遗留(记录):① 群聊/陌生人 MemWritable 记忆隔离未接 loop(AGENTS.md 红线,待修) ② webhook InboundHandler
需 gateway 挂载 ③ RAG 语义路径 persona-scoped(project 记忆其他 persona 不浮现)。
2026-09-06 20:09:09 +08:00