- SOUL.md: list all available tools (web.search, memory.*, cron.*, etc.)
and add Tool Usage Rules section enforcing 'act, don't narrate'
- cron.ts: add getJob(), addJob(), removeJob() to CronScheduler for
runtime (ephemeral) cron job management
- cron tools: add cron.create and cron.delete tools, enhance cron.list
to show schedule/output/message details
- policy.ts: add cron tools to messaging and coding profiles, add
group:cron to tool groups
Fixes issue where models would narrate tool intent ('let me search...')
then stop without actually calling tools.
Allow cron jobs to specify a `model_tier` field that controls which LLM
tier handles the job, without needing separate agent configs. Precedence:
cron job model_tier > agent config > global primary_tier > 'default'.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Local backends using strict chat templates (e.g. Mistral 3) rejected
Flynn's Anthropic-style tool_use/tool_result content blocks, causing
'roles must alternate' errors. Added getMessageTextWithTools() and
normalizeMessagesForLocal() to serialize structured blocks to plain
text, drop empty messages, and merge consecutive same-role messages.
Also fixed compaction to ensure kept messages start with user role.
Adds process.loadEnvFile() to CLI entry point so API keys (ZHIPUAI_API_KEY,
OPENROUTER_API_KEY, XAI_API_KEY, etc.) can be stored in a project .env file
instead of shell environment or systemd service config. Uses Node >= 20.12
built-in — no dotenv dependency needed. Silent no-op if .env doesn't exist.
Updates .env.example with placeholders for all provider API keys.
Previously, switching to zhipuai/openrouter/xai via /model would throw a
confusing 'OPENAI_API_KEY missing' error from the OpenAI SDK. Now
createClientFromConfig validates API keys before constructing the client,
throwing errors that name the correct env var (e.g. ZHIPUAI_API_KEY).
Also fixes the misleading 'as anthropic' type cast in the /model handler
to validate against MODEL_PROVIDERS and use the ModelProvider type.
Wrap each message in a .message-wrapper div and render copy/edit buttons
below the bubble instead of overlapping inside it. Improves readability
and prevents buttons from covering message content.
Extract MODEL_PROVIDERS const from config schema as single source of truth
for provider names. PROVIDER_NAMES in TUI commands now imports from schema
instead of maintaining a hardcoded list. Adds tests verifying sync.
Updates README TUI Commands section with /model hot-swap documentation,
supported providers, and runtime model switching examples.
Copy button on all messages (clipboard API with checkmark feedback).
Edit button on user messages populates the input textarea.
Buttons appear on hover (desktop) or always visible (mobile).
ModelRouter now supports multiple tier-change listeners via addOnTierChange(),
SessionBridge subscribes to tier changes and propagates them to all WebChat
agents (both existing and newly created), and the fullscreen TUI now also
updates the agent's tier when switching models (matching minimal TUI behavior).
Local LLMs often get stuck calling the same tool repeatedly because they
lack the sophistication to synthesize results. The agent loop had no
safeguard — it re-executed whatever the model requested up to 10 times.
Add fingerprint-based loop detection: if the same tool+args combination
repeats 3 consecutive times, break the loop and return the last results.
Also add agents.max_iterations to the config schema so the iteration
limit is user-configurable (default: 10).
Gmail API returns snippets with HTML entities (&, ', <br>, etc.)
that leaked into LLM responses as raw HTML. Added shared sanitizeHtml()
utility in src/utils/html.ts and applied it to gmail tool snippets,
HTML body fallback, and gmail watcher snippets.
Use agent.reset() instead of session.clear() when agent is available,
ensuring tool fingerprint, usage counters, and agent history are all
cleared properly alongside the session.
Replace process.stdout.write-based onToolUse callback (which corrupts
Ink rendering) with a React state-driven approach that shows tool names,
args, and completion status in the streaming content area.
Fullscreen TUI was calling modelClient directly, bypassing the NativeAgent
tool loop entirely. Pass the agent through FullscreenTuiConfig → App and
use agent.process() for message handling, which enables the full tool
registry and executor.
Add three new Google service integrations following the established
Gmail/GCal pattern:
- Google Docs (docs.list, docs.search, docs.read): list, search, and
read document content as plain text via Docs + Drive APIs
- Google Drive (drive.list, drive.search, drive.read): list, search,
and read files with export support for Workspace files (Docs→text,
Sheets→CSV, Slides→text)
- Google Tasks (tasks.lists, tasks.list): list task lists and tasks
with status, due dates, and notes
Each service has its own config section, OAuth auth command, tool
policy group, and test suite (53 new tests). The setup wizard now
offers to configure all Google services together and run OAuth auth
flows automatically after saving config.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Persist /model tier choice to ~/.local/share/flynn/preferences.json so
it survives restarts. Decode HTML entities (e.g. ') in markdown
renderer output. Suppress noisy logger.info and punycode deprecation
warnings in TUI startup.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The existing gmail.list and gmail.search tools only return snippets.
gmail.read fetches the full message by ID using format: 'full', decodes
base64url body parts (preferring text/plain, falling back to stripped
HTML), and returns headers + body text. This enables workflows like
searching for invoices and extracting amounts from the full content.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add GCal tools setup guide to README (prerequisites, config, fields).
Add gmail-auth, gcal-auth, setup to the CLI commands table. Add
"Adding a New Tool" checklist to CLAUDE.md covering the full wiring
chain including the TUI registration gotcha.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Format tool names as human-readable labels (e.g. "Gmail: List") and
show args as compact key-value pairs instead of raw JSON. Also register
Gmail tools in the TUI when automation.gmail is enabled.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Stale session history can cause the model to follow old "I can't do
that" patterns even when new tools are available. NativeAgent now tracks
a tool fingerprint and appends a system prompt note listing current
tools when the inventory changes, resetting on session reset.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add calendar.today, calendar.list, calendar.search tools mirroring the
Gmail tool pattern. Includes gcal-auth CLI command, config schema, tool
policy entries (messaging/coding profiles + group:gcal), and 17 tests.
Also wires up gmail and gcal tool registration in the daemon and TUI.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Block-level renderer methods (paragraph, heading, blockquote, list) were
using raw token.text instead of this.parser.parseInline(tokens), causing
bold, italic, and inline code to never render. Add table renderer with
aligned columns and box-drawing separators. Remove unused marked-terminal
dependency (incompatible with marked v17).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements `flynn gmail-auth` to complete the OAuth2 flow that
GmailWatcher references but was never built. Supports local callback
server (default) and --manual paste mode. Adds Gmail health check
to `flynn doctor`.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Mark onboard wizard as MATCH (100/128, 78%). Update test count to 1151.
Add setup-wizard plan entry to state.json.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds comprehensive integration tests for the first-run wizard verifying config
generation for different provider/channel combinations. Updates shell completion
to include the 'setup' command with its options.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Register setup command in CLI index
- Offer setup wizard when running `flynn start` with no config
- Guard telegram log output since telegram is now optional
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements Task 6 of the setup wizard:
- orchestrator.ts: runMenu() for interactive configuration loop
- orchestrator.ts: runFirstRunWizard() for new user onboarding
- orchestrator.test.ts: test for menu exit behavior
- setup.ts: registerSetupCommand() and runSetup() handler
- Handles both first-run and existing config scenarios
- Saves YAML config to disk
- Optional daemon startup after first-run
All tests pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement setupChannels function with support for Telegram, Discord, Slack, and WhatsApp.
Includes WebChat gateway configuration and channel choice loop.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add ConfigBuilder class to accumulate wizard answers into config objects with YAML
serialization, and renderSummary function to display configuration summary. Includes
9 test cases covering provider setup, channel configuration, and feature flags.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Created a Prompter interface and factory function for interactive CLI prompts:
- ask(): text input with optional default values
- confirm(): yes/no confirmation with default
- choose(): numbered menu selection with fallback
- password(): text input (no echo planned in TUI)
- println(): simple output helper
All 9 tests pass (ask, confirm, choose, password scenarios).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
9 tasks with TDD approach: prompt helpers, config builder, provider/channel
flows, menu sections, orchestrator, CLI wiring, integration tests. ~29 new
tests, 13 new files, 0 new dependencies.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Interactive setup wizard with two entry points: auto-trigger on
first run (no config detected) and explicit `flynn setup` command.
Minimal-first flow for quick start, menu-driven for reconfiguration.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Clean up the once('close') listener on the readline Interface when
rl.question() resolves normally. Previously, each prompt loop iteration
accumulated a close listener that was never removed, triggering
MaxListenersExceededWarning after 11 prompts.
- Core counters: messages processed, sessions, queue depth, uptime, active requests, errors
- Model performance table: recent calls with latency, tokens/sec, provider, status
- Event stream: scrollable log with color-coded levels (error/warn/info)
- Active requests: in-flight request table with session, channel, duration
- Channels grid: existing channel status cards preserved
- Dual timer refresh: 3s for metrics/events/requests, 10s for health/channels
- Targeted DOM updates via getElementById for flicker-free fast updates