feat: Ollama/gemma4 compat — /init flow, stream filter, safety fixes

provider/openai:
- Fix doubled tool call args (argsComplete flag): Ollama sends complete
  args in the first streaming chunk then repeats them as delta, causing
  doubled JSON and 400 errors in elfs
- Handle fs: prefix (gemma4 uses fs:grep instead of fs.grep)
- Add Reasoning field support for Ollama thinking output

cmd/gnoma:
- Early TTY detection so logger is created with correct destination
  before any component gets a reference to it (fixes slog WARN bleed
  into TUI textarea)

permission:
- Exempt spawn_elfs and agent tools from safety scanner: elf prompt
  text may legitimately mention .env/.ssh/credentials patterns and
  should not be blocked

tui/app:
- /init retry chain: no-tool-calls → spawn_elfs nudge → write nudge
  (ask for plain text output) → TUI fallback write from streamBuf
- looksLikeAgentsMD + extractMarkdownDoc: validate and clean fallback
  content before writing (reject refusals, strip narrative preambles)
- Collapse thinking output to 3 lines; ctrl+o to expand (live stream
  and committed messages)
- Stream-level filter for model pseudo-tool-call blocks: suppresses
  <<tool_code>>...</tool_code>> and <<function_call>>...<tool_call|>
  from entering streamBuf across chunk boundaries
- sanitizeAssistantText regex covers both block formats
- Reset streamFilterClose at every turn start
This commit is contained in:
2026-04-05 19:24:51 +02:00
parent 14b88cadcc
commit cb2d63d06f
51 changed files with 2855 additions and 353 deletions

View File

@@ -39,6 +39,11 @@ func NewLocal(eng *engine.Engine, providerName, model string) *Local {
}
func (s *Local) Send(input string) error {
return s.SendWithOptions(input, engine.TurnOptions{})
}
// SendWithOptions is like Send but applies per-turn engine options.
func (s *Local) SendWithOptions(input string, opts engine.TurnOptions) error {
s.mu.Lock()
if s.state != StateIdle {
s.mu.Unlock()
@@ -64,7 +69,7 @@ func (s *Local) Send(input string) error {
}
}
turn, err := s.eng.Submit(ctx, input, cb)
turn, err := s.eng.SubmitWithOptions(ctx, input, opts, cb)
s.mu.Lock()
s.turn = turn

View File

@@ -53,6 +53,8 @@ type Status struct {
type Session interface {
// Send submits user input and begins an agentic turn.
Send(input string) error
// SendWithOptions is like Send but applies per-turn engine options.
SendWithOptions(input string, opts engine.TurnOptions) error
// Events returns the channel that receives streaming events.
// A new channel is created per Send(). Closed when the turn completes.
Events() <-chan stream.Event