Fix HOME volume mount, genericize topic, add progress statuses, extract lexicon
- Volume was mounted at ~/.claude only, but claude-code writes auth to ~/.claude.json too — that file lived on the ephemeral container layer and was lost on every recreate. Now the whole $HOME is on the named volume, and claude-config moved outside HOME (/app) so it isn't shadowed by it. - Default /news topic was hardcoded to AI; now it's a configurable DEFAULT_TOPIC (plain "главные новости дня"), topic is fully free-form. - Prompt now instructs Claude to send interim "⏳ ..." progress updates via send_to_telegram while researching, not just the final digest — a bare request like "call this tool" was already taking 3+ minutes end-to-end, so silence read as broken. - All user-facing/prompt text moved into bot/lib/lexicon.dart. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ import 'dart:io';
|
||||
import '../lib/config.dart';
|
||||
import '../lib/telegram.dart';
|
||||
import '../lib/claude_runner.dart';
|
||||
import '../lib/lexicon.dart';
|
||||
|
||||
void main() async {
|
||||
final config = Config.fromEnv();
|
||||
@@ -41,12 +42,10 @@ void main() async {
|
||||
continue;
|
||||
}
|
||||
|
||||
stderr.writeln('[$chatId] $text');
|
||||
|
||||
if (text == '/start') {
|
||||
await telegram.sendMessage(
|
||||
chatId,
|
||||
'Привет! Я agent-news. Команда /news — свежая выжимка по ИИ '
|
||||
'(ресёрч делает Claude Code, займёт 1-3 минуты).',
|
||||
);
|
||||
await telegram.sendMessage(chatId, Lexicon.welcome);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -54,17 +53,13 @@ void main() async {
|
||||
final topic = text.startsWith('/news ') ? text.substring(6).trim() : null;
|
||||
|
||||
if (claude.isBusy) {
|
||||
await telegram.sendMessage(chatId, 'Уже выполняю другой запрос, подожди.',
|
||||
replyToMessageId: messageId);
|
||||
await telegram.sendMessage(chatId, Lexicon.alreadyBusy, replyToMessageId: messageId);
|
||||
continue;
|
||||
}
|
||||
|
||||
await telegram.sendMessage(
|
||||
chatId,
|
||||
'🔎 Ищу свежую информацию${topic != null ? ' по теме «$topic»' : ' по ИИ'}, '
|
||||
'это займёт пару минут…',
|
||||
replyToMessageId: messageId,
|
||||
);
|
||||
final effectiveTopic = (topic == null || topic.isEmpty) ? config.defaultTopic : topic;
|
||||
await telegram.sendMessage(chatId, Lexicon.starting(effectiveTopic),
|
||||
replyToMessageId: messageId);
|
||||
await telegram.sendTyping(chatId);
|
||||
|
||||
unawaited(_runNews(claude, telegram, chatId, topic));
|
||||
@@ -88,11 +83,12 @@ Future<void> _runNews(
|
||||
case ClaudeOutcome.busy:
|
||||
break; // уже сообщили выше
|
||||
case ClaudeOutcome.timeout:
|
||||
await telegram.sendMessage(chatId, 'Не успел уложиться по времени, попробуй позже.');
|
||||
stderr.writeln('news for $chatId timed out');
|
||||
await telegram.sendMessage(chatId, Lexicon.timedOut);
|
||||
break;
|
||||
case ClaudeOutcome.failure:
|
||||
stderr.writeln('news for $chatId failed: ${result.detail}');
|
||||
await telegram.sendMessage(chatId, 'Не получилось выполнить ресёрч, попробуй позже.');
|
||||
await telegram.sendMessage(chatId, Lexicon.failed);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'config.dart';
|
||||
import 'lexicon.dart';
|
||||
|
||||
enum ClaudeOutcome { success, busy, timeout, failure }
|
||||
|
||||
@@ -73,16 +74,11 @@ class ClaudeRunner {
|
||||
}
|
||||
|
||||
String _buildPrompt({required int chatId, String? topic}) {
|
||||
final base = topic == null || topic.isEmpty
|
||||
? config.newsPrompt
|
||||
: 'Проведи ресёрч последних новостей и трендов по теме "$topic" за последние '
|
||||
'7 дней. Используй поиск в интернете. Составь краткую выжимку: 5-8 пунктов, '
|
||||
'у каждого — суть в 1-2 предложениях и ссылка на источник.';
|
||||
|
||||
return '$base\n\n'
|
||||
'Когда выжимка готова, ОБЯЗАТЕЛЬНО вызови инструмент send_to_telegram с '
|
||||
'параметрами chat_id=$chatId и text=<готовый текст в Markdown, с заголовком>. '
|
||||
'Это единственный способ доставить материал — не выводи финальный ответ никак '
|
||||
'иначе, только через вызов этого инструмента.';
|
||||
final effectiveTopic = (topic == null || topic.isEmpty) ? config.defaultTopic : topic;
|
||||
return [
|
||||
Lexicon.researchInstruction(effectiveTopic),
|
||||
Lexicon.progressInstruction(chatId),
|
||||
Lexicon.finalDeliveryInstruction(chatId),
|
||||
].join('\n\n');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import 'dart:io';
|
||||
import 'lexicon.dart';
|
||||
|
||||
class Config {
|
||||
final String telegramToken;
|
||||
final Set<int> allowedChatIds;
|
||||
final String newsPrompt;
|
||||
final String defaultTopic;
|
||||
final int claudeTimeoutSeconds;
|
||||
final String mcpConfigPath;
|
||||
|
||||
Config({
|
||||
required this.telegramToken,
|
||||
required this.allowedChatIds,
|
||||
required this.newsPrompt,
|
||||
required this.defaultTopic,
|
||||
required this.claudeTimeoutSeconds,
|
||||
required this.mcpConfigPath,
|
||||
});
|
||||
@@ -27,14 +28,10 @@ class Config {
|
||||
return Config(
|
||||
telegramToken: e['TELEGRAM_BOT_TOKEN'] ?? '',
|
||||
allowedChatIds: allowed,
|
||||
newsPrompt: e['NEWS_PROMPT'] ??
|
||||
'Проведи ресёрч последних новостей и трендов в области искусственного '
|
||||
'интеллекта за последние 7 дней. Используй поиск в интернете. '
|
||||
'Составь краткую выжимку: 5-8 пунктов, у каждого — суть в 1-2 '
|
||||
'предложениях и ссылка на источник.',
|
||||
defaultTopic: e['DEFAULT_TOPIC'] ?? Lexicon.defaultTopic,
|
||||
claudeTimeoutSeconds:
|
||||
int.tryParse(e['CLAUDE_TIMEOUT_SECONDS'] ?? '360') ?? 360,
|
||||
mcpConfigPath: e['MCP_CONFIG_PATH'] ?? '/home/agent/claude-config/mcp.json',
|
||||
int.tryParse(e['CLAUDE_TIMEOUT_SECONDS'] ?? '600') ?? 600,
|
||||
mcpConfigPath: e['MCP_CONFIG_PATH'] ?? '/app/claude-config/mcp.json',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
40
bot/lib/lexicon.dart
Normal file
40
bot/lib/lexicon.dart
Normal file
@@ -0,0 +1,40 @@
|
||||
// Все тексты, которые бот показывает пользователю или подставляет в промпт
|
||||
// Claude — в одном месте, чтобы менять формулировки не по всему коду.
|
||||
class Lexicon {
|
||||
static const welcome =
|
||||
'Привет! Я agent-news. Команда /news <тема> — сделаю ресёрч и пришлю '
|
||||
'выжимку с источниками. Без темы — возьму главные новости дня. '
|
||||
'Ресёрч ведёт Claude Code, обычно несколько минут — по ходу буду '
|
||||
'присылать статусы, что происходит.';
|
||||
|
||||
static const alreadyBusy = 'Уже выполняю другой запрос, подожди.';
|
||||
|
||||
static String starting(String topic) =>
|
||||
'🔎 Начинаю ресёрч по теме «$topic». Пришлю статусы по ходу и выжимку в конце.';
|
||||
|
||||
static const timedOut = 'Не успел уложиться по времени, попробуй позже.';
|
||||
|
||||
static const failed = 'Не получилось выполнить ресёрч, попробуй позже.';
|
||||
|
||||
// Тема по умолчанию для /news без аргумента — просто «главные новости»,
|
||||
// без привязки к конкретной сфере: тема запроса задаётся пользователем.
|
||||
static const defaultTopic = 'главные новости дня';
|
||||
|
||||
static String researchInstruction(String topic) =>
|
||||
'Проведи ресёрч по теме "$topic" за последние 7 дней. Используй поиск '
|
||||
'в интернете. Составь выжимку: 5-8 пунктов, у каждого — суть в 1-2 '
|
||||
'предложениях и ссылка на источник.';
|
||||
|
||||
static String progressInstruction(int chatId) =>
|
||||
'По ходу работы присылай короткие статусы через инструмент '
|
||||
'send_to_telegram (chat_id=$chatId, text="⏳ <что делаешь сейчас>") — '
|
||||
'например сразу после того как определишь план поиска, и ещё раз '
|
||||
'после того как соберёшь основные источники. Это нужно, чтобы '
|
||||
'пользователь видел, что процесс идёт, а не просто ждал молча.';
|
||||
|
||||
static String finalDeliveryInstruction(int chatId) =>
|
||||
'В конце ОБЯЗАТЕЛЬНО вызови send_to_telegram ещё раз с параметрами '
|
||||
'chat_id=$chatId и text=<готовая выжимка в Markdown, с заголовком>. Это '
|
||||
'единственный способ доставить результат — не выводи финальный ответ '
|
||||
'никак иначе, только через вызов этого инструмента.';
|
||||
}
|
||||
Reference in New Issue
Block a user