feat(loop): wire speak tool into the loop (M57)
- speak_tool.go: the speak tool itself — rune-derived timeout budget, structured failure codes, persona/emotion stamped on the synthesis ctx, audio persisted through the store seam, structured result for the model. - voice_deps.go: the wiring seam (VoiceSynth / VoiceStore + VoiceStoreFunc adapter), voiceEnabled, withSpeakSpec (appended after every schema shrink), and captureVoice (success beats failure, last one wins). - options.go / loop.go: VoiceSynth, VoiceStore, SpeakBudget injection points. - request.go: advertise speak on both toolSchemas return paths. - tools.go / tools_invoke.go: carry the batch voice outcome; split invokeOne / emitToolExecution / spanStatusForToolPhase / invokeParallel into their own file (tools.go was at the 500-line ceiling) and let speak run without a dispatcher. - run.go: keep the voice outcome across tool batches (a later batch without speak must not erase it). Known, deliberate: speak bypasses the capacity allow-set and skill-round narrowing (it belongs to no built-in toolset), so it is also advertised in quiet/awake; execution still passes the tool security gate, where speak is not registered and non-owner actors are denied fail-closed.
This commit is contained in:
1 parent
f84a358e30
commit
1347a77d33
8 files changed
+628
-94
No files matched your search
@@ -187,6 +187,11 @@ func (l *Loop) driveTurn(ctx context.Context, sessionID string, msgs []model.Mes
|
||||
}
|
||||
msgs = batch.messages
|
||||
res.ToolCallsExecuted += batch.executed
|
||||
// speak 的出站结论逐批回填(M57):后面批次里没有 speak 时保留
|
||||
// 前面那批的结论 —— 这条回复想用语音这件事发生在哪一批都算数。
|
||||
if batch.voice != nil {
|
||||
res.Voice = batch.voice
|
||||
}
|
||||
l.saveCheckpoint(ctx, sessionID, step, msgs, agent.StopReasonToolCalls)
|
||||
|
||||
// 关键工具循环安全网(或终止钩子)不等真正的最终答案
|
||||
|
||||
Reference in new issue
Block a user