OLD: streaming and non-streaming were separate paths. Streaming detected tool
calls and recursively called non-streaming which only did ONE round of tool
execution with no loop-back. This caused silent hangs.
NEW: single chatWithAI with internal while loop (max 10 turns):
1. Call API (stream or non-stream)
2. If tool_calls → execute all → append results → loop
3. If text content → return final answer
Key fixes:
- streamChat now ACCUMULATES tool_call deltas instead of aborting
- Tool results are fed back to the AI in the same conversation
- Multi-turn: AI can call tools multiple times before answering
- Max 10 turns with forced final answer as safety net
- Proper { content, tool_calls, error } return type from both paths
- Non-streaming fallback if SSE fails
- No more recursive calls between stream/non-stream
Previously tool calls in non-streaming path returned raw tool output as the
response. Now executes tool, sends results back to model for a synthesized
answer. Fixes the 'silent after streaming fallback' bug.
- DelegateTool.js: multi-turn sub-agent (max 10 turns), feeds tool results back
- Moved TOOL_DEFS to startBot scope so delegate handler can access tool schemas
- Fixed scoping: delegate handler resolves model from svc.config instead of chatWithAI local
- Wired into tools/index.js, TOOL_DEFS, and toolHandlers
- delegate_agent: now makes actual AI call with role-specific system prompts
(coder=code review, architect=system design, devops=infrastructure)
- run_skill: now makes actual AI call with skill-specific system prompts
(code_review, bug_fix, refactor, documentation, testing)
- Both return structured AI-generated results instead of placeholder text
Model says "let me research" then calls web_search tool.
Streaming path ignored tool_calls entirely (no-op comment).
Now: detect tool_calls delta, cancel stream, fall back to non-streaming
which properly executes tools and returns results.
- Pidfile lock prevents duplicate instances (auto-kills stale PIDs)
- EADDRINUSE retry: kills port hog, retries up to 3x with 1.5s delay
- releasePidfile() on graceful shutdown
- Added fs/path imports needed by pidfile utilities
- ConversationStore: per-chat JSON files in data/, survives restarts
- 6000 token budget per chat context (fits ~20-30 exchanges)
- Auto-trims old messages, always includes most recent
- Wired into message handler: loads history before AI call, saves after
- /reset command to clear chat history per chat
- Cross-session, cross-model, cross-chat isolation
When streaming produces a long essay that exceeds Telegram's edit limit,
the final HTML edit silently fails, leaving the user with raw ** markers.
Now: delete the draft message and send fresh formatted message(s) via
sendFormatted which handles HTML conversion, splitting, and fallback.
- New memory.js: JSON-backed MemoryStore with 5 categories (lesson, pattern, preference, discovery, gotcha)
- Memory injected into system prompt — bot sees past learnings every session
- Curiosity engine: auto-detects errors/fixes, corrections, successful patterns, new tool discoveries
- New commands: /memory (stats), /remember (save), /recall (search), /forget (delete)
- Runs AFTER response delivery — zero latency impact
- 500 memory cap with smart eviction (keeps gotchas/lessons, evicts old discoveries)
- data/ directory gitignored (memory is local to each deployment)
- start.sh: use dirname instead of hardcoded path
- src/zcode.js: remove hardcoded chat_id fallback
- src/utils/rtk.js: use 'rtk' from PATH instead of hardcoded binary path
- src/telegram-bot.ts: use process.cwd() instead of hardcoded path
- TELEGRAM_SETUP.md: replace token/chat_id with placeholders
- QUICKSTART.md: sanitize all references
- SERVICE_MAP.md: use relative paths instead of absolute
- Add markdownToHtml() converter: **bold**, *italic*, code blocks, links, headings, quotes, lists
- StreamConsumer: intermediate edits stay plain text, FINAL message gets full HTML formatting
- sendFormatted() now uses HTML parse_mode with fallback to stripped plain text
- stripMarkdown() for plain-text fallback (no raw syntax chars)
- All Telegram sends now use HTML instead of legacy Markdown mode