docs(runtime): internal/runtime/loop 44 个文件注释改中文(逐行 1:1,代码零字节改动)

主人 2026-10-03 拍板「注释一律中文」;本模块按长期分批协议(一个模块一次派工)执行。
注释行数 1329 → 1329 逐行 1:1(含中文 33 → 1304);行尾注释 5 条一并译。
校验:机械校验(非注释改动行)= 空;独立 Go AST 程序(剥注释复渲)证明 44/44 文件代码全等;
全树 go test ./... 58 包 ok / 0 FAIL;交叉编译绿。
gofmt:capacity_loop_test.go / flow.go 改动前即未格式化,按铁律保留原样未动。
This commit is contained in:
AskaEth committed 2026-10-03 22:28:21 +08:00
1 parent 355f81d04a
commit 91e8d7a029
44 files changed
+1315 -1315

No files matched your search

+111 -111
View File
@@ -13,95 +13,95 @@ import (
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/trace"
)
// ToolExecutionMode tokens accepted by Options.ToolExecution. Empty selects
// toolExecParallel.
// Options.ToolExecution 接受的 ToolExecutionMode 取值。为空时选用
// toolExecParallel。
const (
toolExecParallel = "parallel"
toolExecSequential = "sequential"
)
// criticalToolLoopTerminalReason is surfaced as Result.Text and on every blocked
// call when the critical-tool-loop safety net stops the run.
// criticalToolLoopTerminalReason 在关键工具循环安全网停止
// run 时,作为 Result.Text 与每个被拦截调用的文本浮出。
const criticalToolLoopTerminalReason = "critical tool-loop detected: an identical tool call was repeated across rounds; the run is stopping"
// ToolCallVeto is returned by a CallAdmit hook to block a single call.
// ToolCallVeto 由 CallAdmit 钩子返回,用于拦截单个调用。
type ToolCallVeto struct {
Block bool
Reason string
}
// ToolOutcome is the normalized result of executing one tool call. It is what an
// after-call / after-outcome hook observes and may rewrite before the loop
// renders it back to the model.
// ToolOutcome 是一次工具调用执行的归一化结果。它正是
// after-call / after-outcome 钩子所观察、并可能在循环把它
// 回渲给模型之前改写的东西。
type ToolOutcome struct {
// Result is the value the tool returned (nil on failure or skip).
// Result 是工具返回的值(失败或跳过时为 nil)。
Result any
// Err is non-nil when the call failed to execute; it is nil on success.
// Err 在调用执行失败时非 nil;成功时为 nil。
Err error
// ErrKind classifies a failure as tool.KindArgumentValidation or
// tool.KindExecution (the task's errorKind status classification). Empty on
// success.
// ErrKind 把失败归类为 tool.KindArgumentValidation 或
// tool.KindExecution(任务 errorKind 状态分类)。成功时为
// 空。
ErrKind string
// Skipped is true when the call never executed (duplicate / blocked / vetoed).
// Skipped 在调用从未执行(重复 / 被拦截 / 被否决)时为 true。
Skipped bool
// Reason is a human-readable explanation for a skipped call.
// Reason 是被跳过调用的人类可读解释。
Reason string
// Terminate, when set by a hook, signals that the whole round should stop
// once every call in the batch is also marked terminate.
// Terminate 由钩子设置时,表示一旦该批中每个调用都被
// 标记 terminate,整轮就应停止。
Terminate bool
}
// ToolLoopHooks groups the optional per-batch / per-call tool-loop hooks. Every
// field is nil-tolerant. These are the extension points the loop keeps open for
// "敏感操作代审" (design/11 batch admission) and the future async-tool path, so
// the audit layer and the loop are decoupled.
// ToolLoopHooks 归组可选的按批次 / 按调用工具循环钩子。每个
// 字段都容忍 nil。这些是循环为「敏感操作代审」(design/11
// 批次准入)以及未来的异步工具路径保留的扩展点,使
// 审计层与循环解耦。
type ToolLoopHooks struct {
// BatchAdmit is called once per batch before any call executes. Returning
// (false, reason) blocks the whole batch: every call becomes a skipped result
// carrying reason. nil = always admit.
// BatchAdmit 在任何调用执行前每批调用一次。返回
// (false, reason) 会拦截整批:每个调用都变成携带 reason 的
// 跳过结果。为 nil = 总是准入。
BatchAdmit func(ctx context.Context, calls []agent.ToolCall) (bool, string)
// CallAdmit is called per call before it executes. A non-nil veto with
// Block=true blocks that single call. nil = always admit.
// CallAdmit 在每个调用执行前对它调用一次。非 nil 且
// Block=true 的否决会拦截该单个调用。为 nil = 总是准入。
CallAdmit func(ctx context.Context, call agent.ToolCall) *ToolCallVeto
// AfterCall rewrites a call's outcome after the tool executes. A nil return
// keeps the original. It runs before AfterOutcome.
// AfterCall 在工具执行后改写调用的结果。返回 nil
// 则保持原样。它在 AfterOutcome 之前运行。
AfterCall func(ctx context.Context, call agent.ToolCall, outcome ToolOutcome) *ToolOutcome
// AfterOutcome observes/rewrites the finalized outcome (after AfterCall and
// after any skip/block decision) immediately before the result is rendered
// back to the model. nil = keep the outcome.
// AfterOutcome 观察/改写最终结果(在 AfterCall 与
// 任何跳过/拦截决策之后),就在结果被回渲给
// 模型之前。为 nil = 保持结果。
AfterOutcome func(ctx context.Context, call agent.ToolCall, outcome ToolOutcome) *ToolOutcome
}
// toolRunResult is the outcome of running one tool batch.
// toolRunResult 是运行一个工具批次的结果。
type toolRunResult struct {
messages []model.Message
executed int
terminate bool
}
// toolLoopState carries the per-run state of the critical-tool-loop safety net.
// It is scoped to a single Run call, so concurrent runs never share it.
// toolLoopState 携带关键工具循环安全网的每次 run 状态。
// 它作用域限于单次 Run 调用,因此并发 run 绝不共享它。
type toolLoopState struct {
criticalSeen bool // a critical loop was already reported this run
prevFP map[string]bool // fingerprints seen in the immediately previous batch
criticalSeen bool // 本次 run 已报告过关键循环
prevFP map[string]bool // 紧邻上一批中见过的指纹
}
// toolCallAction is the execution decision for one call after classification.
// toolCallAction 是一个调用经分类后的执行决策。
type toolCallAction struct {
run bool // execute the call
reason string // when run==false, the human reason fed back to the model
run bool // 执行该调用
reason string // run==false 时,回灌给模型的人类可读原因
}
// runToolCalls executes a batch of model-requested tool calls. It classifies
// duplicate invocations (the critical-tool-loop safety net), applies the optional
// batch/per-call hooks, runs the surviving calls in parallel (bounded) or
// serially, and feeds every outcome back as a RoleTool message in the original
// call order. It returns the number of calls actually invoked (executed), the
// updated message slice, and whether the round should terminate.
// runToolCalls 执行一批模型请求的工具调用。它分类重复
// 调用(关键工具循环安全网)、应用可选的
// 批次/按调用钩子、以并行(有界)或串行方式运行存活的调用,
// 并按原始调用顺序把每个结果作为 RoleTool 消息回灌。
// 它返回实际调用的数量(executed)、更新后的消息切片,
// 以及该轮是否应终止。
//
// Invocation is delegated to the dispatched tool, which applies the per-tool
// deadline; a missing dispatcher is fed back as an error so the loop never
// panics or crashes on an unhandled call.
// 调用被委托给被分派的工具,由后者应用每工具
// 截止时间;缺少 dispatcher 时以错误形式回灌,使循环绝不
// 对未处理的调用 panic 或崩溃。
func (l *Loop) runToolCalls(ctx context.Context, messages []model.Message, calls []agent.ToolCall, st *toolLoopState) (toolRunResult, error) {
d := l.dispatcher()
res := toolRunResult{messages: messages}
@@ -114,16 +114,16 @@ func (l *Loop) runToolCalls(ctx context.Context, messages []model.Message, calls
res.terminate = true
}
// Security gate (design/11 §3): a group/stranger session may invoke only
// tools whose editable marker allows that environment (default closed). A
// disallowed call never executes; it is fed back as a not_allowed soft result
// and recorded to the audit sink. It is the final authority over the batch —
// a later audit hook may tighten but never re-admit a denied tool.
// 安全门(design/11 §3):群聊/陌生人会话只能调用
// 其可编辑标记允许该环境的工具(默认关闭)。不被允许的
// 调用绝不执行;它以 not_allowed 软结果回灌
// 并记录到审计 sink。它是该批的最终权威 ——
// 后续的审计钩子可以收紧,但绝不重新放行被拒的工具。
if d != nil {
l.applyToolSecurityGate(ctx, d, calls, actions)
}
// BatchAdmit hook (audit / political review): block the whole batch.
// BatchAdmit 钩子(审计 / 政治审查):拦截整批。
if l.hooks.BatchAdmit != nil {
if ok, reason := l.hooks.BatchAdmit(ctx, calls); !ok {
if reason == "" {
@@ -135,7 +135,7 @@ func (l *Loop) runToolCalls(ctx context.Context, messages []model.Message, calls
}
}
// CallAdmit hook: per-call veto over the calls we intend to run.
// CallAdmit 钩子:对我们打算运行的调用逐个否决。
if l.hooks.CallAdmit != nil {
for i := range calls {
if !actions[i].run {
@@ -151,9 +151,9 @@ func (l *Loop) runToolCalls(ctx context.Context, messages []model.Message, calls
}
}
// Decide serial vs parallel: explicit sequential mode, or any call whose tool
// is declared sequential (a single sequential marker makes the whole group
// serial, matching OpenClaw's launchParallelToolCalls).
// 决定串行还是并行:显式串行模式,或任一调用的工具
// 被声明为串行(单个串行标记即让整组
// 串行,与 OpenClaw 的 launchParallelToolCalls 一致)。
sequential := l.toolExec == toolExecSequential
if !sequential && d != nil {
for i := range calls {
@@ -187,7 +187,7 @@ func (l *Loop) runToolCalls(ctx context.Context, messages []model.Message, calls
res.executed += len(idxs)
}
// AfterCall hook (only for calls that actually executed).
// AfterCall 钩子(仅对实际执行过的调用)。
if l.hooks.AfterCall != nil {
for i := range calls {
if outcomes[i].Skipped {
@@ -199,7 +199,7 @@ func (l *Loop) runToolCalls(ctx context.Context, messages []model.Message, calls
}
}
// AfterOutcome hook (runs on every finalized outcome, skipped or not).
// AfterOutcome 钩子(对每个最终结果运行,无论是否跳过)。
if l.hooks.AfterOutcome != nil {
for i := range calls {
if after := l.hooks.AfterOutcome(ctx, calls[i], outcomes[i]); after != nil {
@@ -208,31 +208,31 @@ func (l *Loop) runToolCalls(ctx context.Context, messages []model.Message, calls
}
}
// A hook may ask to terminate the round by marking every call terminate
// (OpenClaw's shouldTerminateToolBatch).
// 钩子可以把每个调用都标记为 terminate 来要求终止本轮
// (OpenClaw 的 shouldTerminateToolBatch)。
if !res.terminate && shouldTerminateBatch(outcomes) {
res.terminate = true
}
// Render the assistant tool-call turn (native providers require it so the
// following tool_result can be correlated) plus every result, in call order so
// the model sees a stable feed.
// 渲染 assistant 工具调用轮(原生 provider 需要它以让
// 后续 tool_result 能被关联),后接每个结果,按调用顺序
// 排列,使模型看到稳定的回灌。
res.messages = appendToolCallHistory(res.messages, calls, outcomes)
return res, nil
}
// classifyToolBatch partitions a batch into run/skip decisions, enforcing the
// critical-tool-loop safety net:
// classifyToolBatch 把一批调用划分为运行/跳过决策,执行
// 关键工具循环安全网:
//
// - A call identical (same name + arguments) to one earlier in the same batch
// is skipped with a duplicate hint (never executes).
// - A call identical to one from a previous round is a critical-loop repeat:
// the first occurrence blocks the repeated call and sets criticalSeen, so the
// model gets a turn to reassess; a second consecutive occurrence terminates
// the whole run.
// - 与同批中更早的一个调用完全相同(同名 + 同参数)的
// 调用被跳过并给出重复提示(绝不执行)。
// - 与上一轮某个调用完全相同的调用是关键的循环重复:
// 第一次出现会拦截该重复调用并置位 criticalSeen,使
// 模型有一轮重新评估;连续第二次出现则终止
// 整个 run。
//
// It returns (actions, terminate). When terminate is true every call is blocked
// with criticalToolLoopTerminalReason and the round must end.
// 它返回 (actions, terminate)。terminate 为 true 时每个调用
// 都以 criticalToolLoopTerminalReason 拦截,且该轮必须结束。
func classifyToolBatch(st *toolLoopState, calls []agent.ToolCall) ([]toolCallAction, bool) {
seen := make(map[string]bool, len(calls))
actions := make([]toolCallAction, len(calls))
@@ -240,7 +240,7 @@ func classifyToolBatch(st *toolLoopState, calls []agent.ToolCall) ([]toolCallAct
for i, call := range calls {
fp := toolFingerprint(call)
if seen[fp] {
// Duplicate of a call already in this batch.
// 与同批中已有调用的重复。
if st.prevFP[fp] {
actions[i] = toolCallAction{run: false, reason: repeatedCallReason()}
repeat = true
@@ -251,7 +251,7 @@ func classifyToolBatch(st *toolLoopState, calls []agent.ToolCall) ([]toolCallAct
}
seen[fp] = true
if st.prevFP[fp] {
// Repeated from the previous round — a critical loop.
// 与上一轮的重复 —— 一个关键循环。
actions[i] = toolCallAction{run: false, reason: repeatedCallReason()}
repeat = true
} else {
@@ -259,8 +259,8 @@ func classifyToolBatch(st *toolLoopState, calls []agent.ToolCall) ([]toolCallAct
}
}
// Remember which fingerprints were live this round for the next round's
// cross-round repeat detection.
// 记住本轮哪些指纹是活跃的,供下一轮的
// 跨轮重复检测使用。
st.prevFP = seen
terminate := false
@@ -277,9 +277,9 @@ func classifyToolBatch(st *toolLoopState, calls []agent.ToolCall) ([]toolCallAct
return actions, terminate
}
// invokeOne executes a single tool call under its per-tool deadline, emitting a
// tool span, streaming tool_execution_update progress and normalizing the
// outcome.
// invokeOne 在其每工具截止时间下执行单个工具调用,发出一个
// tool span、流式输出 tool_execution_update 进度并归一化
// 结果。
func (l *Loop) invokeOne(ctx context.Context, d *tool.Dispatcher, tc agent.ToolCall) ToolOutcome {
if d == nil {
return ToolOutcome{Err: fmt.Errorf("no tool dispatcher configured for %q", tc.Name), ErrKind: tool.KindExecution}
@@ -305,10 +305,10 @@ func (l *Loop) invokeOne(ctx context.Context, d *tool.Dispatcher, tc agent.ToolC
return out
}
// emitToolExecution forwards a tool_execution_update progress event to the
// frontend when the loop is on the streaming path (OnStream configured); it is a
// no-op otherwise (muted internal passes skip it too). phase is a short label
// like "start" or "end:error".
// emitToolExecution 在循环处于流式路径(配置了 OnStream)时把
// tool_execution_update 进度事件转发给前端;否则为空操作
// (被静音的内部流程也会跳过它)。phase 是一个短标签,
// 如 "start" 或 "end:error"。
func (l *Loop) emitToolExecution(ctx context.Context, name, phase string) {
if l.onStream == nil || isMutedBroadcast(ctx) {
return
@@ -319,11 +319,11 @@ func (l *Loop) emitToolExecution(ctx context.Context, name, phase string) {
l.onStream(ev)
}
// invokeParallel runs calls at idxs concurrently, capped at the loop's
// parallelism, writing each outcome back into out at its call index so callers
// can render results in the original order. The semaphore bounds concurrency and
// in-flight goroutines release their slot on completion, so a canceled context
// (which makes every Invoke return promptly) cannot leak goroutines.
// invokeParallel 并发运行 idxs 处的调用,上限为循环的
// 并行度,并把每个结果写回 out 的对应调用索引处,使调用方
// 能按原始顺序渲染结果。信号量限制并发,在途
// goroutine 完成时释放其槽位,因此被取消的 context
// (它使每个 Invoke 迅速返回)不会泄露 goroutine。
func (l *Loop) invokeParallel(ctx context.Context, d *tool.Dispatcher, calls []agent.ToolCall, idxs []int, out []ToolOutcome) {
if len(idxs) == 0 {
return
@@ -342,7 +342,7 @@ func (l *Loop) invokeParallel(ctx context.Context, d *tool.Dispatcher, calls []a
wg.Wait()
}
// returned by classifyToolBatch for the two skip reasons.
// 由 classifyToolBatch 为两种跳过原因返回。
func duplicateCallReason() string {
return "duplicate call with identical arguments already issued in this batch"
}
@@ -351,7 +351,7 @@ func repeatedCallReason() string {
return "already used with identical arguments in a previous round; do not repeat it"
}
// skippedOutcome builds a Skipped outcome carrying the human reason.
// skippedOutcome 构建一个携带人类可读原因的 Skipped 结果。
func skippedOutcome(reason string) ToolOutcome {
if reason == "" {
reason = "tool call skipped"
@@ -359,8 +359,8 @@ func skippedOutcome(reason string) ToolOutcome {
return ToolOutcome{Skipped: true, Reason: reason}
}
// shouldTerminateBatch reports whether every call in the batch asked to terminate
// (OpenClaw's shouldTerminateToolBatch), so a hook can stop the round.
// shouldTerminateBatch 报告该批中每个调用是否都要求终止
// (OpenClaw 的 shouldTerminateToolBatch),使钩子能停止本轮。
func shouldTerminateBatch(outcomes []ToolOutcome) bool {
if len(outcomes) == 0 {
return false
@@ -373,9 +373,9 @@ func shouldTerminateBatch(outcomes []ToolOutcome) bool {
return true
}
// toolFingerprint canonicalizes a tool call (name + args) so identical calls can
// be detected. Args are JSON-marshaled, so equivalent objects with different key
// orders collapse to the same fingerprint; a marshal failure falls back to %v.
// toolFingerprint 规范化一个工具调用(name + args),使相同调用
// 可被检测。Args 被 JSON 序列化,因此键序不同的等价对象
// 会塌缩为同一指纹;序列化失败则回退到 %v。
func toolFingerprint(call agent.ToolCall) string {
b, err := json.Marshal(call.Args)
if err != nil {
@@ -384,9 +384,9 @@ func toolFingerprint(call agent.ToolCall) string {
return call.Name + "\x00" + string(b)
}
// errorKind classifies an invocation error into a tool.Kind* label so the model
// can distinguish an argument-validation failure (re-issue a corrected call) from
// an execution failure. A nil error yields "" (success).
// errorKind 把调用错误归类为 tool.Kind* 标签,使模型
// 能区分参数校验失败(重新发出修正后的调用)与
// 执行失败。nil 错误产出 ""(成功)。
func errorKind(err error) string {
if err == nil {
return ""
@@ -401,8 +401,8 @@ func errorKind(err error) string {
return tool.KindExecution
}
// dispatcher returns the loop's tool dispatcher, falling back to the agent's own
// when the loop was not given one.
// dispatcher 返回循环的工具 dispatcher;循环未被给予时
// 回退到 agent 自身的。
func (l *Loop) dispatcher() *tool.Dispatcher {
if l.tools != nil {
return l.tools
@@ -413,16 +413,16 @@ func (l *Loop) dispatcher() *tool.Dispatcher {
return nil
}
// toolResultMessage renders a tool call's outcome as a RoleTool message ready to
// be fed back to the model. Skipped calls carry their reason; failures carry a
// structured {kind,message} so the LLM can react appropriately; success carries
// the value.
// toolResultMessage 把工具调用的结果渲染为可回灌给模型的
// RoleTool 消息。被跳过的调用携带其原因;失败携带
// 结构化的 {kind,message},使 LLM 能恰当应对;成功携带
// 该值。
func toolResultMessage(tc agent.ToolCall, out ToolOutcome) model.Message {
return model.Message{Role: model.RoleTool, ToolCallName: tc.Name,
Content: []model.Content{{Type: model.ContentTypeText, Text: toolResultText(tc, out)}}}
}
// toolResultText renders the text payload of a tool result message.
// toolResultText 渲染工具结果消息的文本载荷。
func toolResultText(tc agent.ToolCall, out ToolOutcome) string {
switch {
case out.Skipped:
@@ -442,9 +442,9 @@ func toolResultText(tc agent.ToolCall, out ToolOutcome) string {
}
}
// formatToolResult renders a tool return value as text for the model. Strings are
// passed through (an empty string becomes "empty"); everything else is JSON
// encoded, falling back to a plain %v render on an encoding error.
// formatToolResult 把工具返回值渲染为给模型的文本。字符串
// 直通(空字符串变为 "empty");其他一切都 JSON
// 编码,编码出错时回退到朴素的 %v 渲染。
func formatToolResult(result any) string {
switch v := result.(type) {
case nil: