feat: implement tier 1 quick wins (tool groups, typing, pruning, verbose, think)

Five additive features with no breaking changes:

- Tool groups: group:fs, group:runtime, group:web, group:memory syntactic
  sugar for allow/deny lists in tool policy config
- Typing indicators: Discord sendTyping() and WhatsApp sendStateTyping()
  on message receipt for better UX feedback
- Session pruning: TTL-based auto-cleanup via sessions.ttl config with
  hourly daemon timer and SQLite GROUP BY pruning
- /verbose command: TUI command parser toggle for raw streaming display
- !!think prefix: per-message extended thinking mode wired through
  Anthropic (budget_tokens), OpenAI/GitHub (reasoning_effort), and
  Gemini (thinkingConfig) providers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
William Valentin
2026-02-07 13:35:00 -08:00
parent 6bb424cddc
commit 1c2f54fae3
19 changed files with 563 additions and 20 deletions
+18 -1
View File
@@ -7,7 +7,7 @@ import { AnthropicClient, OpenAIClient, OllamaClient, LlamaCppClient, GeminiClie
import type { ModelClient, RetryConfig, ModelTier } from '../models/index.js';
import { AgentOrchestrator, type DelegationConfig } from '../backends/index.js';
import { OutboundAttachmentCollector } from '../backends/native/attachments.js';
import { SessionStore, SessionManager } from '../session/index.js';
import { SessionStore, SessionManager, parseDuration } from '../session/index.js';
import { HookEngine } from '../hooks/index.js';
import { ToolRegistry, ToolExecutor, ToolPolicy, allBuiltinTools, createWebSearchTools, createProcessTools, ProcessManager, BrowserManager, createBrowserTools, createMediaSendTool, createSessionTools, createAgentsListTool, createMessageSendTool, createCronTools } from '../tools/index.js';
import type { Tool } from '../tools/types.js';
@@ -453,6 +453,23 @@ export async function startDaemon(config: Config): Promise<DaemonContext> {
console.log('Session store closed');
});
// Session pruning timer (TTL-based cleanup)
const ttlMs = parseDuration(config.sessions?.ttl ?? '30d');
if (ttlMs) {
const pruneInterval = setInterval(() => {
const cutoff = Math.floor((Date.now() - ttlMs) / 1000); // created_at is unix seconds
const pruned = sessionStore.pruneStale(cutoff);
if (pruned.length > 0) {
sessionManager.evictSessions(pruned);
console.log(`Pruned ${pruned.length} stale session(s) (TTL: ${config.sessions?.ttl ?? '30d'})`);
}
}, 3_600_000); // every hour
lifecycle.onShutdown(async () => {
clearInterval(pruneInterval);
});
}
// Initialize hook engine
const hookEngine = new HookEngine(config.hooks);