docs(cyrene): cmd/cyrene 存量英文注释中文化(22 个非测试文件,仅注释)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

- 四组并行完成:main/wire*.go、wire_capacity|channels|runtime、channels|persona_*|emotion|observability、
  voice|plugins|stream|skills|vision|subagent_tool|extractor|mcp|sessionread_tool|terminal_tool。
- 机械校验(git diff -U0 去 // 行与空行)仅剩 wire_frontend.go:84 一处行尾注释:代码前缀
  「\ta.gw = gw 」经 cat -A 与 HEAD 逐字节比对一致(allow 清单已声明)。
- 验收:隔离 worktree(HEAD + 本批 patch)go build/vet 绿、全树 58 包 ok、
  linux/amd64 + darwin/arm64 交叉编译绿;gofmt -l 干净。
- 备注:整树两次红均为负载敏感 flake(backthink / orchestration 的 timeout 类用例单独 -count=20 全过)。
This commit is contained in:
AskaEth committed 2026-10-04 16:58:06 +08:00
1 parent 5c2189efb0
commit 52f08dc343
22 files changed
+455 -586

No files matched your search

+45 -55
View File
@@ -33,8 +33,8 @@ import (
iotools "git.yeij.top/AskaEth/Cyrene_v2/plugins/tools/io"
)
// buildPersonas loads the persona definitions and registers them, plus the
// default character and the owner profile used by channel identity resolution.
// buildPersonas 加载人格定义并注册它们,同时注册默认角色与渠道身份识别所用的
// 主人档案。
func (a *app) buildPersonas() error {
a.personas = personareg.New()
@@ -55,7 +55,7 @@ func (a *app) buildPersonas() error {
a.log.Warn("owner profile load skipped", "error", oerr)
}
// Load the default persona explicitly if it was not under the personas dir.
// 若默认角色不在 personas 目录下,则显式加载一次。
defaultID := "cyrene"
if a.owner != nil && a.owner.DefaultPersona != "" {
defaultID = a.owner.DefaultPersona
@@ -77,8 +77,8 @@ func (a *app) buildPersonas() error {
return nil
}
// wireMemory builds the long-term memory stack: store, embedding manager, vector
// index, project index and the retriever wired into the io knowledge tool.
// wireMemory 构建长期记忆栈:store、嵌入管理器、向量索引、项目索引,以及接进
// io 知识工具的检索器。
func (a *app) wireMemory(ctx context.Context) error {
store, err := memory.NewStore(ctx, a.db, a.log)
if err != nil {
@@ -119,10 +119,9 @@ func (a *app) wireMemory(ctx context.Context) error {
func (a *app) wireRuntime() error {
a.attachToolPolicyStore()
// The LLM provider stack is hot-swappable: it loads models.json (or falls
// back to .env) and the panel save endpoint reloads it without a restart
// (design/04 §3.1). a.modelAdapter stays an Adapter (the runtime stack
// implements it) for the channel-bridge review model (design/12 §6 策略四).
// LLM 提供商栈可热插拔:它加载 models.json(或回落 .env),面板的保存端点会
// 就地重载它而无需重启(design/04 §3.1)。a.modelAdapter 仍保持 Adapter 接口
// (运行时栈实现了它),供渠道 bridge 的复审模型使用(design/12 §6 策略四)。
modelsPath := runtimeconfig.DefaultPath(resolveDataDir(a.cfg))
// 启动即补齐一次(主人裁决):`.env` 只是默认/播种层,models.json 才是面板的
// 一等来源——把 `.env` 补齐进面板配置里缺失的键(面板已有的键绝不覆盖,组集合
@@ -169,17 +168,16 @@ func (a *app) wireRuntime() error {
}
store := a.memStore
// One agent per role, all sharing the dispatcher/guards/adapter above, so a session
// can answer as any role (design/22 §4.2 方案 A); the loop picks per turn.
// 每个角色一个 agent,共用上面的 dispatcher / 护栏 / 适配器,因此一个会话能以
// 任意角色作答(design/22 §4.2 方案 A);具体由 loop 按 turn 选取。
agents, defaultAgent := a.personaAgentPool(runtimeStack, a.dispatcher, guards, ag)
// Spans fan out to the JSONL stderr recorder and — once wireFrontend has built
// the gateway — to its observability buffer (cmd/cyrene/observability.go).
// span 分两路:一路写 JSONL(stderr 记录器),另一路在 wireFrontend 建好网关后
// 进入它的可观测性缓冲(cmd/cyrene/observability.go)。
a.traceWriter = newAppTraceWriter()
tracer := trace.New(a.traceWriter)
// Realtime flow visualization (design/19 §1): the publisher accumulates the
// loop's trace spans into a per-session flow graph and publishes
// ws.graph_update on each span transition.
// 实时链路可视化(design/19 §1):发布器把 loop 的 trace span 累积成按会话的
// 链路图,并在每次 span 跃迁时发布 ws.graph_update。
//
// 图源白名单(裁决 16.4.2):它决定**哪些会话产出实时链路图**,也是后端能提供
// 完整链路导图的那一侧 —— 主人 web 会话族("web" 主会话 + "web:<uuid>" 次级会话,
@@ -204,15 +202,13 @@ func (a *app) wireRuntime() error {
return fmt.Errorf("checkpoint store: %w", perr)
}
// Vision routing: the loop rewrites an image-bearing user message per the
// vision Router — a main model that really accepts image input sees the image
// directly, otherwise the vision/OCR tool's text description replaces it
// (design/06 §0)。
// 视觉路由:loop 按 vision Router 改写带图用户消息——真正能收图的主模型直接
// 看到图像,否则改用 vision/OCR 工具产出的文字描述替代它(design/06 §0)。
//
// SupportsVision means "主模型能否收图"(裁决 14.4.1 修语义,C-7), read from the
// active models.json group's `multimodal` declaration — NOT from
// LLM_VISION_MODEL, which configures the vision *tool's* model. It is read per
// call so a panel save/reload takes effect on the next turn.
// SupportsVision 指「主模型能否收图」(裁决 14.4.1 修语义,C-7),取自当前生效
// 的 models.json 组的 `multimodal` 声明——**不是**取自 LLM_VISION_MODEL,后者配
// 的是 vision **工具**用的模型。它按调用读取,因此面板保存/重载在下一个 turn
// 即生效。
visionRouter := vision.NewRouter(
vision.WithVisionTool(visionToolName),
vision.WithOCRTool(ocrToolName),
@@ -227,15 +223,13 @@ func (a *app) wireRuntime() error {
)
capDecider := a.buildCapacity()
// The persona emotion observer (design/01 §2) is built here because it is the
// loop's turn-boundary dependency: after every settled turn the bound persona's
// emotion machine and affection move, and the returned mood label is written
// onto Result.Mood and the assistant message (design/16 §3.3).
// 人格情绪观测器(design/01 §2)在此构建,因为它是 loop 的 turn 边界依赖:
// 每个 turn 落定后,该 turn 绑定人格的情绪机与好感度都会推进,返回的心情标签
// 写进 Result.Mood 与助手消息(design/16 §3.3)。
a.emotion = a.buildEmotionObserver()
// Reasoning knobs from the default persona (budget / effort / thinking broadcast),
// which are also the per-turn defaults: the loop re-resolves them for each turn's
// role through PersonaKnobs, so a session bound to another role uses that role's
// budget instead.
// 推理旋钮取自默认人格(预算 / 强度 / 是否广播思考),它们同时也是按 turn 的
// 默认值:loop 会经 PersonaKnobs 按每个 turn 的角色重新解析,因此绑定其他角色
// 的会话用的是那个角色的预算。
thinking, reasoningEffort, thinkingShow := a.defaultThinkingOptions()
// 敏感操作代审(design/11 §4;裁决 13.4.3):terminal 这类敏感工具在 loop 的
@@ -293,14 +287,12 @@ func (a *app) wireRuntime() error {
Bus: a.bus,
Memory: store,
Tracer: tracer,
// A sub-agent dispatched as another role (subagent_spawn `persona`, design/22
// §8-2) may only name a role this process can actually run with its own
// instructions, i.e. one the pool above has an agent for; anything else is
// rejected instead of silently answering in the default role's voice.
// 以其他角色派发的子代理(subagent_spawn 的 `persona`,design/22 §8-2)只能
// 指名本进程确实能用该角色自己的指令跑起来的角色,即上面那个池子里有 agent
// 的角色;其余一律拒绝,而不是默默用默认角色的口吻作答。
PersonaExists: personaExists(agents),
// The sub-agent's span and its inner llm/tool steps fold into the spawning
// session's live graph, so a branch shows up as a swimlane of that session's
// flow instead of opening a second graph (design/19 §2).
// 子代理的 span 与它内部的 llm/工具步骤并进派生它的那个会话的实时图,因此
// 一条分支表现为该会话链路图里的一条泳道,而不是另开第二张图(design/19 §2)。
Flow: flowPub,
Approver: approver,
Logger: logger.WithSource(a.log, "orchestration"),
@@ -404,15 +396,14 @@ func (a *app) approvalActor(ctx context.Context) string {
return pid
}
// buildCapacity / onCapacity / capacity JSON+TTL parsing live in
// wire_capacity.go (AGENTS §10 文件 ≤500 行).
// buildCapacity / onCapacity 以及 capacity 的 JSON+TTL 解析都在
// wire_capacity.go(AGENTS §10 文件 ≤500 行)。
// onToolAudit is the loop's OnAudit sink: it forwards a tool call denied by the
// design/11 §3 security gate in a group/stranger environment into the gateway
// audit trail, so the rejection is "audit 可查" and the frontend can render the
// small "xx 工具在群聊被拒" note (design/11 §8). The gateway is built after the
// loop (wireFrontend), so the sink reads a.gw at call time — by then the app is
// fully assembled and any Run is served through the gateway.
// onToolAudit 是 loop 的 OnAudit 出口:它把群聊/陌生人环境里被 design/11 §3 安全闸
// 拦下的工具调用转发进网关的审计流水,使这次拒绝「audit 可查」,前端也能渲染那行
// 「xx 工具在群聊被拒」小字(design/11 §8)。网关在 loop 之后才建好
// (wireFrontend),所以该出口在调用时才读 a.gw——那时应用已装配完整,任何 Run 都
// 经网关服务。
func (a *app) onToolAudit(ev loop.AuditEvent) {
if a.gw == nil {
logger.WithSource(a.log, "cmd/cyrene").Warn("loop audit dropped: gateway not ready",
@@ -422,18 +413,17 @@ func (a *app) onToolAudit(ev loop.AuditEvent) {
a.gw.AuditEvent(ev.Who, ev.What, ev.Result)
}
// parseCapacityToolSets / parseL3CacheTTL live in wire_capacity.go.
// parseCapacityToolSets / parseL3CacheTTL 在 wire_capacity.go 里。
// dbCfgForEmbed returns the DB config used by the vector index, which opens the
// same SQLite file as the primary pool.
// dbCfgForEmbed 返回向量索引所用的数据库配置——它打开的是与主连接池同一个
// SQLite 文件。
func (a *app) dbCfgForEmbed() config.DBConfig {
cfg := a.cfg.DB
cfg.SQLitePath = resolveSQLitePath(a.cfg, resolveDataDir(a.cfg))
return cfg
}
// defaultPersonaID returns the id of the default persona, falling back to
// "cyrene".
// defaultPersonaID 返回默认人格的 id,取不到时回落 "cyrene"。
func (a *app) defaultPersonaID() string {
if c, ok := a.personas.DefaultCharacter(); ok {
return c.PersonaID
@@ -441,9 +431,9 @@ func (a *app) defaultPersonaID() string {
return "cyrene"
}
// defaultInstructions returns the default persona's system prompt followed by the
// personality/values/speechStyle/catchphrase/background sections (design/01
// §2.2), falling back to its role text or a minimal instruction when neither is set.
// defaultInstructions 返回默认人格的系统提示词,其后依次接 personality / values /
// speechStyle / catchphrase / background 各段(design/01 §2.2);两者都取不到时回落
// 到该角色的 role 文本或一句最小指令。
//
// 主人身份说明书**不在这里**(P0-10 收窄后按会话注入,见 personaKnowledgeFor):
// 静态拼进指令会让它进入该角色的每一个会话,含群聊与陌生人私聊。