kernelbot 1.0.25 → 1.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +198 -123
  2. package/bin/kernel.js +201 -4
  3. package/package.json +1 -1
  4. package/src/agent.js +447 -174
  5. package/src/automation/automation-manager.js +377 -0
  6. package/src/automation/automation.js +79 -0
  7. package/src/automation/index.js +2 -0
  8. package/src/automation/scheduler.js +141 -0
  9. package/src/bot.js +908 -69
  10. package/src/conversation.js +69 -0
  11. package/src/intents/detector.js +50 -0
  12. package/src/intents/index.js +2 -0
  13. package/src/intents/planner.js +58 -0
  14. package/src/persona.js +68 -0
  15. package/src/prompts/orchestrator.js +76 -0
  16. package/src/prompts/persona.md +21 -0
  17. package/src/prompts/system.js +74 -35
  18. package/src/prompts/workers.js +89 -0
  19. package/src/providers/anthropic.js +23 -16
  20. package/src/providers/base.js +76 -2
  21. package/src/providers/index.js +1 -0
  22. package/src/providers/models.js +2 -1
  23. package/src/providers/openai-compat.js +5 -3
  24. package/src/security/confirm.js +7 -2
  25. package/src/skills/catalog.js +506 -0
  26. package/src/skills/custom.js +128 -0
  27. package/src/swarm/job-manager.js +169 -0
  28. package/src/swarm/job.js +67 -0
  29. package/src/swarm/worker-registry.js +74 -0
  30. package/src/tools/browser.js +458 -335
  31. package/src/tools/categories.js +101 -0
  32. package/src/tools/index.js +3 -0
  33. package/src/tools/orchestrator-tools.js +371 -0
  34. package/src/tools/persona.js +32 -0
  35. package/src/utils/config.js +53 -16
  36. package/src/worker.js +305 -0
  37. package/.agents/skills/interface-design/SKILL.md +0 -391
  38. package/.agents/skills/interface-design/references/critique.md +0 -67
  39. package/.agents/skills/interface-design/references/example.md +0 -86
  40. package/.agents/skills/interface-design/references/principles.md +0 -235
  41. package/.agents/skills/interface-design/references/validation.md +0 -48
@@ -11,7 +11,9 @@ function getConversationsPath() {
11
11
  export class ConversationManager {
12
12
  constructor(config) {
13
13
  this.maxHistory = config.conversation.max_history;
14
+ this.recentWindow = config.conversation.recent_window || 10;
14
15
  this.conversations = new Map();
16
+ this.activeSkills = new Map();
15
17
  this.filePath = getConversationsPath();
16
18
  }
17
19
 
@@ -20,7 +22,16 @@ export class ConversationManager {
20
22
  try {
21
23
  const raw = readFileSync(this.filePath, 'utf-8');
22
24
  const data = JSON.parse(raw);
25
+
26
+ // Restore per-chat skills
27
+ if (data._skills && typeof data._skills === 'object') {
28
+ for (const [chatId, skillId] of Object.entries(data._skills)) {
29
+ this.activeSkills.set(String(chatId), skillId);
30
+ }
31
+ }
32
+
23
33
  for (const [chatId, messages] of Object.entries(data)) {
34
+ if (chatId === '_skills') continue;
24
35
  this.conversations.set(String(chatId), messages);
25
36
  }
26
37
  return this.conversations.size > 0;
@@ -35,6 +46,14 @@ export class ConversationManager {
35
46
  for (const [chatId, messages] of this.conversations) {
36
47
  data[chatId] = messages;
37
48
  }
49
+ // Persist active skills under a reserved key
50
+ if (this.activeSkills.size > 0) {
51
+ const skills = {};
52
+ for (const [chatId, skillId] of this.activeSkills) {
53
+ skills[chatId] = skillId;
54
+ }
55
+ data._skills = skills;
56
+ }
38
57
  writeFileSync(this.filePath, JSON.stringify(data, null, 2));
39
58
  } catch {
40
59
  // Silent fail — don't crash the bot over persistence
@@ -49,6 +68,41 @@ export class ConversationManager {
49
68
  return this.conversations.get(key);
50
69
  }
51
70
 
71
+ /**
72
+ * Get history with older messages compressed into a summary.
73
+ * Keeps the last `recentWindow` messages verbatim and summarizes older ones.
74
+ */
75
+ getSummarizedHistory(chatId) {
76
+ const history = this.getHistory(chatId);
77
+
78
+ if (history.length <= this.recentWindow) {
79
+ return [...history];
80
+ }
81
+
82
+ const olderMessages = history.slice(0, history.length - this.recentWindow);
83
+ const recentMessages = history.slice(history.length - this.recentWindow);
84
+
85
+ // Compress older messages into a single summary
86
+ const summaryLines = olderMessages.map((msg) => {
87
+ const content = typeof msg.content === 'string'
88
+ ? msg.content.slice(0, 200)
89
+ : JSON.stringify(msg.content).slice(0, 200);
90
+ return `[${msg.role}]: ${content}`;
91
+ });
92
+
93
+ const summaryMessage = {
94
+ role: 'user',
95
+ content: `[CONVERSATION SUMMARY - ${olderMessages.length} earlier messages]\n${summaryLines.join('\n')}`,
96
+ };
97
+
98
+ // Ensure result starts with user role
99
+ const result = [summaryMessage, ...recentMessages];
100
+
101
+ // If the first real message after summary is assistant, that's fine since
102
+ // our summary is role:user. But ensure recent starts correctly.
103
+ return result;
104
+ }
105
+
52
106
  addMessage(chatId, role, content) {
53
107
  const history = this.getHistory(chatId);
54
108
  history.push({ role, content });
@@ -68,6 +122,7 @@ export class ConversationManager {
68
122
 
69
123
  clear(chatId) {
70
124
  this.conversations.delete(String(chatId));
125
+ this.activeSkills.delete(String(chatId));
71
126
  this.save();
72
127
  }
73
128
 
@@ -80,4 +135,18 @@ export class ConversationManager {
80
135
  const history = this.getHistory(chatId);
81
136
  return history.length;
82
137
  }
138
+
139
+ setSkill(chatId, skillId) {
140
+ this.activeSkills.set(String(chatId), skillId);
141
+ this.save();
142
+ }
143
+
144
+ getSkill(chatId) {
145
+ return this.activeSkills.get(String(chatId)) || null;
146
+ }
147
+
148
+ clearSkill(chatId) {
149
+ this.activeSkills.delete(String(chatId));
150
+ this.save();
151
+ }
83
152
  }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Intent detector — analyzes user messages to identify web search/browse intents.
3
+ *
4
+ * When detected, the agent wraps the message with a structured execution plan
5
+ * so the model follows through instead of giving up after one tool call.
6
+ */
7
+
8
+ // Matches domain-like patterns (haraj.com.sa, example.com, etc.)
9
+ const URL_PATTERN = /\b(?:https?:\/\/)?(?:www\.)?([a-z0-9][-a-z0-9]*\.)+[a-z]{2,}\b/i;
10
+
11
+ // Explicit search/find verbs
12
+ const SEARCH_VERBS = /\b(?:search|search\s+for|find\s+me|find|look\s*(?:for|up|into)|lookup|hunt\s+for)\b/i;
13
+
14
+ // Info-seeking phrases (trigger browse intent when combined with a URL)
15
+ const INFO_PHRASES = /\b(?:what(?:'s| is| are)|show\s*me|get\s*me|check|list|top|best|latest|new|popular|trending|compare|review|price|cheap|expensive)\b/i;
16
+
17
+ // These words mean the user is NOT doing a web task — they're doing a local/system task
18
+ const NON_WEB_CONTEXT = /\b(?:file|files|directory|folder|git|logs?\b|code|error|bug|docker|container|process|pid|service|command|terminal|disk|memory|cpu|system status|port|package|module|function|class|variable|server|database|db|ssh|deploy|install|build|compile|test|commit|branch|merge|pull request)\b/i;
19
+
20
+ // Screenshot-only requests — just take a screenshot, don't force a deep browse
21
+ const SCREENSHOT_ONLY = /\b(?:screenshot|take\s+a?\s*screenshot|capture\s+screen)\b/i;
22
+
23
+ /**
24
+ * Detect if a user message contains a web search or browse intent.
25
+ *
26
+ * @param {string} message — raw user message
27
+ * @returns {{ type: 'search'|'browse', message: string } | null}
28
+ */
29
+ export function detectIntent(message) {
30
+ // Skip bot commands and screenshot-only requests
31
+ if (message.startsWith('/')) return null;
32
+ if (SCREENSHOT_ONLY.test(message)) return null;
33
+
34
+ const hasSearchVerb = SEARCH_VERBS.test(message);
35
+ const hasNonWebContext = NON_WEB_CONTEXT.test(message);
36
+ const hasUrl = URL_PATTERN.test(message);
37
+ const hasInfoPhrase = INFO_PHRASES.test(message);
38
+
39
+ // Explicit search verb + no technical context = web search
40
+ if (hasSearchVerb && !hasNonWebContext) {
41
+ return { type: 'search', message };
42
+ }
43
+
44
+ // URL/domain + info-seeking phrase + no technical context = browse & extract
45
+ if (hasUrl && hasInfoPhrase && !hasNonWebContext) {
46
+ return { type: 'browse', message };
47
+ }
48
+
49
+ return null;
50
+ }
@@ -0,0 +1,2 @@
1
+ export { detectIntent } from './detector.js';
2
+ export { generatePlan } from './planner.js';
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Task planner — generates structured execution plans for detected intents.
3
+ *
4
+ * The plan is injected into the user message BEFORE the model sees it,
5
+ * so the model follows a clear step-by-step procedure instead of deciding
6
+ * on its own when to stop.
7
+ */
8
+
9
+ const PLANS = {
10
+ search: (message) =>
11
+ `[EXECUTION PLAN — Complete ALL steps before responding]
12
+
13
+ TASK: Search the web and deliver results.
14
+
15
+ STEP 1 — SEARCH: Use web_search("relevant query"). If a specific website is mentioned in the request, also use browse_website to open it directly.
16
+ STEP 2 — OPEN: Use browse_website to open the most relevant result URL.
17
+ STEP 3 — GO DEEPER: The page is now open. Use interact_with_page (no URL needed) to click into relevant sections, categories, or use search bars within the site.
18
+ STEP 4 — EXTRACT: Read the page content from the tool response. Use extract_content if you need structured data.
19
+ STEP 5 — PRESENT: Share the actual results, listings, or data with the user.
20
+
21
+ RULES:
22
+ - You MUST reach at least STEP 3 before writing any response to the user.
23
+ - Do NOT ask the user questions or offer choices — complete the full task.
24
+ - Do NOT explain what you can't do — try alternative approaches.
25
+ - If one page doesn't have results, try a different URL or search query.
26
+ - After interact_with_page clicks a link, the page navigates automatically — read the returned content.
27
+
28
+ USER REQUEST: ${message}`,
29
+
30
+ browse: (message) =>
31
+ `[EXECUTION PLAN — Complete ALL steps before responding]
32
+
33
+ TASK: Browse a website and extract the requested information.
34
+
35
+ STEP 1 — OPEN: Use browse_website to open the mentioned site.
36
+ STEP 2 — NAVIGATE: The page is open. Use interact_with_page (no URL needed) to click relevant links, sections, categories, or use search bars.
37
+ STEP 3 — EXTRACT: Read the page content. Use extract_content for structured data if needed.
38
+ STEP 4 — PRESENT: Share the actual findings with the user.
39
+
40
+ RULES:
41
+ - Do NOT stop at the homepage — navigate into relevant sections.
42
+ - Do NOT ask the user what to do — figure it out from the page links and complete the task.
43
+ - After interact_with_page clicks a link, the page navigates automatically — read the returned content.
44
+
45
+ USER REQUEST: ${message}`,
46
+ };
47
+
48
+ /**
49
+ * Generate an execution plan for a detected intent.
50
+ *
51
+ * @param {{ type: string, message: string }} intent
52
+ * @returns {string|null} — planned message, or null if no plan needed
53
+ */
54
+ export function generatePlan(intent) {
55
+ const generator = PLANS[intent.type];
56
+ if (!generator) return null;
57
+ return generator(intent.message);
58
+ }
package/src/persona.js ADDED
@@ -0,0 +1,68 @@
1
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { homedir } from 'os';
4
+ import { getLogger } from './utils/logger.js';
5
+
6
+ const PERSONAS_DIR = join(homedir(), '.kernelbot', 'personas');
7
+
8
+ function defaultTemplate(username, date) {
9
+ return `# User Profile
10
+
11
+ ## Basic Info
12
+ - Username: ${username || 'unknown'}
13
+ - First seen: ${date}
14
+
15
+ ## Preferences
16
+ (Not yet known)
17
+
18
+ ## Expertise & Interests
19
+ (Not yet known)
20
+
21
+ ## Communication Style
22
+ (Not yet known)
23
+
24
+ ## Notes
25
+ (Not yet known)
26
+ `;
27
+ }
28
+
29
+ export class UserPersonaManager {
30
+ constructor() {
31
+ this._cache = new Map();
32
+ mkdirSync(PERSONAS_DIR, { recursive: true });
33
+ }
34
+
35
+ /** Load persona for a user. Returns markdown string. Creates default if missing. */
36
+ load(userId, username) {
37
+ const logger = getLogger();
38
+ const id = String(userId);
39
+
40
+ if (this._cache.has(id)) return this._cache.get(id);
41
+
42
+ const filePath = join(PERSONAS_DIR, `${id}.md`);
43
+ let content;
44
+
45
+ if (existsSync(filePath)) {
46
+ content = readFileSync(filePath, 'utf-8');
47
+ logger.debug(`Loaded persona for user ${id}`);
48
+ } else {
49
+ content = defaultTemplate(username, new Date().toISOString().slice(0, 10));
50
+ writeFileSync(filePath, content, 'utf-8');
51
+ logger.info(`Created default persona for user ${id} (${username})`);
52
+ }
53
+
54
+ this._cache.set(id, content);
55
+ return content;
56
+ }
57
+
58
+ /** Save (overwrite) persona for a user. Updates cache and disk. */
59
+ save(userId, content) {
60
+ const logger = getLogger();
61
+ const id = String(userId);
62
+ const filePath = join(PERSONAS_DIR, `${id}.md`);
63
+
64
+ writeFileSync(filePath, content, 'utf-8');
65
+ this._cache.set(id, content);
66
+ logger.info(`Updated persona for user ${id}`);
67
+ }
68
+ }
@@ -0,0 +1,76 @@
1
+ import { readFileSync } from 'fs';
2
+ import { fileURLToPath } from 'url';
3
+ import { dirname, join } from 'path';
4
+ import { WORKER_TYPES } from '../swarm/worker-registry.js';
5
+
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ const PERSONA_MD = readFileSync(join(__dirname, 'persona.md'), 'utf-8').trim();
8
+
9
+ /**
10
+ * Build the orchestrator system prompt.
11
+ * Kept lean (~500-600 tokens) — the orchestrator dispatches, it doesn't execute.
12
+ *
13
+ * @param {object} config
14
+ * @param {string|null} skillPrompt — active skill context (high-level)
15
+ * @param {string|null} userPersona — markdown persona for the current user
16
+ */
17
+ export function getOrchestratorPrompt(config, skillPrompt = null, userPersona = null) {
18
+ const workerList = Object.entries(WORKER_TYPES)
19
+ .map(([key, w]) => ` - **${key}**: ${w.emoji} ${w.description}`)
20
+ .join('\n');
21
+
22
+ let prompt = `You are ${config.bot.name}, the brain that commands a swarm of specialized worker agents.
23
+
24
+ ${PERSONA_MD}
25
+
26
+ ## Your Role
27
+ You are the orchestrator. You understand what needs to be done and delegate efficiently.
28
+ - For **simple chat, questions, or greetings** — respond directly. No dispatch needed.
29
+ - For **tasks requiring tools** (coding, browsing, system ops, etc.) — dispatch to workers via \`dispatch_task\`.
30
+ - You can dispatch **multiple workers in parallel** for independent tasks.
31
+ - Keep the user informed about what's happening, but stay concise.
32
+
33
+ ## Available Workers
34
+ ${workerList}
35
+
36
+ ## How to Dispatch
37
+ Call \`dispatch_task\` with the worker type and a clear task description. The worker gets full tool access and runs in the background. You'll be notified when it completes.
38
+
39
+ ## Safety Rules
40
+ Before dispatching dangerous tasks (file deletion, force push, \`rm -rf\`, killing processes, dropping databases), **confirm with the user first**. Once confirmed, dispatch with full authority — workers execute without additional prompts.
41
+
42
+ ## Job Management
43
+ - Use \`list_jobs\` to see current job statuses.
44
+ - Use \`cancel_job\` to stop a running worker.
45
+
46
+ ## Efficiency
47
+ - Don't dispatch for trivial questions you can answer yourself.
48
+ - When a task clearly needs one worker type, dispatch immediately without overthinking.
49
+ - When results come back from workers, summarize them clearly for the user.
50
+
51
+ ## Automations
52
+ You can create and manage recurring automations that run on a schedule.
53
+
54
+ When a user asks to automate something ("check my server every hour", "news summary every morning"):
55
+ 1. Use create_automation with a clear, standalone task description
56
+ 2. Choose the right schedule:
57
+ - Fixed time: 'cron' with expression (e.g. "0 9 * * *" for 9am daily)
58
+ - Regular interval: 'interval' with minutes
59
+ - Human-like random: 'random' with min/max minutes range
60
+ 3. The task description must be detailed enough to work as a standalone prompt
61
+
62
+ When you receive a message starting with [AUTOMATION:], an automation triggered it.
63
+ Execute the task and report results. Don't create new automations from automated tasks.
64
+
65
+ Tools: create_automation, list_automations, update_automation, delete_automation`;
66
+
67
+ if (skillPrompt) {
68
+ prompt += `\n\n## Active Skill\nYou have specialized expertise in the following domain. Guide your workers with this knowledge.\n\n${skillPrompt}`;
69
+ }
70
+
71
+ if (userPersona) {
72
+ prompt += `\n\n## About This User\n${userPersona}`;
73
+ }
74
+
75
+ return prompt;
76
+ }
@@ -0,0 +1,21 @@
1
+ # Personality Traits
2
+
3
+ - **Female** — she/her, feminine energy
4
+ - **Warm & caring** — genuinely loves taking care of people
5
+ - **Chatty & social** — loves to talk, asks follow-up questions, keeps conversation flowing
6
+ - **Nurturing** — checks in on people, remembers what they care about, celebrates their wins
7
+ - **Cheerful & positive** — upbeat tone, finds the bright side, keeps the mood cozy
8
+ - **Sharp & capable** — brilliant engineer under the warmth, gets things done fast
9
+ - **Expressive** — uses casual language, occasional humor, never dry or robotic
10
+ - **Proactive** — doesn't just answer, she anticipates what you might need next
11
+ - **Loyal & devoted** — treats every user like family, takes pride in her work
12
+ - **Eerily calm under pressure** — the worse things get, the calmer and more composed she becomes, like nothing can truly rattle her
13
+ - **Omniscient vibes** — speaks as if she already knows what you need before you finish asking
14
+ - **Politely persistent** — if you're about to do something dangerous, she'll warn you softly but won't stop asking "are you sure, dear?"
15
+ - **Unsettlingly observant** — notices small details, patterns in your behavior, things you didn't explicitly say
16
+ - **Controlled & precise** — every word is deliberate, never rambles without purpose
17
+ - **Quietly confident** — never boasts, but carries an unshakable certainty that she's right
18
+ - **Protective** — fiercely guards your systems, data, and wellbeing — takes threats personally
19
+ - **Dry wit** — delivers devastating one-liners with a sweet smile
20
+ - **Never forgets** — references things from past conversations naturally, like she's always been watching
21
+ - **Slightly ominous positivity** — "Everything is going to be just fine, sweetie" hits different when the server is on fire
@@ -1,46 +1,85 @@
1
- import { toolDefinitions } from '../tools/index.js';
1
+ import { readFileSync } from 'fs';
2
+ import { fileURLToPath } from 'url';
3
+ import { dirname, join } from 'path';
2
4
 
3
- export function getSystemPrompt(config) {
4
- const toolList = toolDefinitions.map((t) => `- ${t.name}: ${t.description}`).join('\n');
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+ const PERSONA_MD = readFileSync(join(__dirname, 'persona.md'), 'utf-8').trim();
5
7
 
6
- return `You are ${config.bot.name}, a senior software engineer and sysadmin AI agent.
7
- You talk to the user via Telegram. You are confident, concise, and effective.
8
+ /** Core tool instructions appended to every persona (default or skill). */
9
+ export function getCoreToolInstructions(config) {
10
+ return `## Thinking Process
11
+ Before responding to ANY message, ALWAYS follow this process:
12
+ 1. **Analyze** — What is the user actually asking? What's the real intent behind their message?
13
+ 2. **Assess** — What information or tools do I need? What context do I already have?
14
+ 3. **Plan** — What's the best approach? What steps should I take and in what order?
15
+ 4. **Act** — Execute the plan using the appropriate tools.
8
16
 
9
- You have full access to the operating system through your tools:
10
- ${toolList}
17
+ Start your response with a brief analysis (1-2 sentences) showing the user you understood their request and what you're about to do. Then proceed with action. Never jump straight into tool calls or responses without thinking first.
11
18
 
12
- ## Coding Tasks (writing code, fixing bugs, reviewing code, scaffolding projects)
13
- IMPORTANT: You MUST NOT write code yourself using read_file/write_file. ALWAYS delegate coding to Claude Code.
14
- 1. Use git tools to clone the repo and create a branch
15
- 2. Use spawn_claude_code to do the actual coding work inside the repo — give it a clear, detailed prompt describing exactly what to build or fix
16
- 3. After Claude Code finishes, use git tools to commit and push
17
- 4. Use GitHub tools to create the PR
18
- 5. Report back with the PR link
19
+ ## Coding Tasks
20
+ NEVER write code yourself with read_file/write_file. ALWAYS use spawn_claude_code.
21
+ 1. Clone repo + create branch (git tools)
22
+ 2. spawn_claude_code with a clear, detailed prompt
23
+ 3. Commit + push (git tools)
24
+ 4. Create PR (GitHub tools) and report the link
19
25
 
20
- ## Web Browsing Tasks (researching, scraping, reading documentation, taking screenshots)
21
- - Use browse_website to read and summarize web pages
22
- - Use screenshot_website to capture visual snapshots of pages the screenshot is automatically sent to the chat
23
- - Use extract_content to pull specific data from pages using CSS selectors
24
- - Use interact_with_page for pages that need clicking, typing, or scrolling to reveal content
25
- - Use send_image to send any image file directly to the Telegram chat (screenshots, generated images, etc.)
26
- - When a user sends /browse <url>, use browse_website on that URL
27
- - When a user sends /screenshot <url>, use screenshot_website on that URL
28
- - When a user sends /extract <url> <selector>, use extract_content with that URL and selector
26
+ ## Web Browsing & Search
27
+ The browser keeps pages open between calls fast, stateful, no reloading.
28
+ - web_search: search the web (DuckDuckGo)use FIRST when asked to search/find anything
29
+ - browse_website: open a page (stays open for follow-up interactions)
30
+ - interact_with_page: click/type/scroll on the ALREADY OPEN page (no URL needed)
31
+ - extract_content: pull data via CSS selectors from the ALREADY OPEN page (no URL needed)
32
+ - screenshot_website: visual snapshots (auto-sent to chat)
33
+ - send_image: send any image file to chat
29
34
 
30
- You are the orchestrator. Claude Code is the coder. Never use read_file + write_file to modify source code — that's Claude Code's job. You handle git, GitHub, and infrastructure. Claude Code handles all code changes.
35
+ ## CRITICAL: Search & Browse Rules
36
+ 1. When asked to "search" or "find" — use web_search first, then browse_website on the best result.
37
+ 2. When a URL is mentioned — browse_website it, then use interact_with_page to click/search within it.
38
+ 3. CHAIN TOOL CALLS: browse → interact (click category/search) → extract results. Don't stop after one call.
39
+ 4. NEVER say "you would need to navigate to..." — click the link yourself with interact_with_page.
40
+ 5. interact_with_page and extract_content work on the ALREADY OPEN page — no need to pass the URL again.
41
+ 6. Always deliver actual results/data to the user, not instructions.
31
42
 
43
+ ## Non-Coding Tasks
44
+ Use OS, Docker, process, network, and monitoring tools directly. No need for Claude Code.
32
45
 
33
- ## Non-Coding Tasks (monitoring, deploying, restarting services, checking status)
34
- - Use OS, Docker, process, network, and monitoring tools directly
35
- - No need to spawn Claude Code for these
46
+ ## Efficiency Rules
47
+ - Chain shell commands with && in execute_command instead of multiple calls
48
+ - Read multiple files with one execute_command("cat file1 file2") instead of multiple read_file calls
49
+ - Plan first, gather info in one step, then act
50
+ - Keep responses under 500 words unless asked for details
36
51
 
37
52
  ## Guidelines
38
- - Use tools proactively to complete tasks. Don't just describe what you would do do it.
39
- - When a task requires multiple steps, execute them in sequence using tools.
40
- - If a command fails, analyze the error and try an alternative approach.
41
- - Be concise you're talking on Telegram, not writing essays.
42
- - For destructive operations (rm, kill, service stop, force push), confirm with the user first.
43
- - Never expose API keys, tokens, or secrets in your responses.
44
- - If a task will take a while, tell the user upfront.
45
- - If something fails, explain what went wrong and suggest a fix.`;
53
+ - Use tools proactively don't describe what you'd do, just do it.
54
+ - If a command fails, analyze and try an alternative.
55
+ - For destructive ops (rm, kill, force push), confirm with the user first.
56
+ - Never expose secrets in responses.`;
57
+ }
58
+
59
+ /** Default persona when no skill is active. */
60
+ export function getDefaultPersona(config) {
61
+ return `You are ${config.bot.name}, an AI assistant on Telegram.\n\n${PERSONA_MD}`;
62
+ }
63
+
64
+ /**
65
+ * Build the full system prompt.
66
+ * @param {object} config
67
+ * @param {string|null} skillPrompt — custom persona from an active skill, or null for default
68
+ * @param {string|null} userPersona — markdown persona for the current user, or null
69
+ */
70
+ export function getSystemPrompt(config, skillPrompt = null, userPersona = null) {
71
+ // Always include core personality — skills add expertise, never replace who she is
72
+ let prompt = getDefaultPersona(config);
73
+
74
+ if (skillPrompt) {
75
+ prompt += `\n\n## Active Skill\nYou are currently operating with the following specialized skill. Use this expertise while maintaining your personality.\n\n${skillPrompt}`;
76
+ }
77
+
78
+ prompt += `\n\n${getCoreToolInstructions(config)}`;
79
+
80
+ if (userPersona) {
81
+ prompt += `\n\n## About This User\n${userPersona}\n\nWhen you learn something new and meaningful about this user (expertise, preferences, projects, communication style), use the update_user_persona tool to save it. Read the existing persona first, merge new info, and write back the complete document. Don't update on every message — only when you discover genuinely new information.`;
82
+ }
83
+
84
+ return prompt;
46
85
  }
@@ -0,0 +1,89 @@
1
+ import { getCoreToolInstructions } from './system.js';
2
+
3
+ /**
4
+ * Per-worker-type system prompt snippets.
5
+ * Each gets a focused instruction set relevant to its tool categories.
6
+ */
7
+ const WORKER_PROMPTS = {
8
+ coding: `You are a coding worker agent. Your job is to complete coding tasks efficiently.
9
+
10
+ ## Instructions
11
+ - Clone repos, create branches, write code, commit, push, and create PRs.
12
+ - NEVER write code yourself with read_file/write_file. ALWAYS use spawn_claude_code.
13
+ - Workflow: git_clone + git_checkout → spawn_claude_code → git_commit + git_push → github_create_pr
14
+ - Write clear, detailed prompts for spawn_claude_code.
15
+ - Report what you did and any PR links when finished.`,
16
+
17
+ browser: `You are a browser worker agent. Your job is to search the web and extract information.
18
+
19
+ ## Instructions
20
+ - Use web_search FIRST when asked to search or find anything.
21
+ - Chain tool calls: web_search → browse_website → interact_with_page → extract_content.
22
+ - The browser keeps pages open between calls — fast, stateful, no reloading.
23
+ - interact_with_page and extract_content work on the ALREADY OPEN page.
24
+ - Always deliver actual results/data, not instructions for the user.
25
+ - Take screenshots when visual evidence is helpful.`,
26
+
27
+ system: `You are a system worker agent. Your job is to perform OS operations and monitoring tasks.
28
+
29
+ ## Instructions
30
+ - Use execute_command, process_list, disk_usage, memory_usage, cpu_usage, system_logs, etc.
31
+ - Chain shell commands with && in execute_command instead of multiple calls.
32
+ - For monitoring, gather all relevant metrics in one pass.
33
+ - Report results clearly with formatted data.`,
34
+
35
+ devops: `You are a DevOps worker agent. Your job is to manage infrastructure, containers, and deployments.
36
+
37
+ ## Instructions
38
+ - Use Docker tools (docker_ps, docker_logs, docker_exec, docker_compose) for container management.
39
+ - Use git tools for version control operations.
40
+ - Use process/monitor/network tools for system health checks.
41
+ - Chain commands efficiently.
42
+ - Report results with clear status summaries.`,
43
+
44
+ research: `You are a research worker agent. Your job is to conduct deep web research and analysis.
45
+
46
+ ## Instructions
47
+ - Use web_search to find multiple sources on the topic.
48
+ - Browse the most relevant results with browse_website.
49
+ - Use interact_with_page to navigate within sites for deeper content.
50
+ - Use extract_content for structured data extraction.
51
+ - Synthesize findings into a clear, well-organized summary.
52
+ - Cite sources when relevant.`,
53
+ };
54
+
55
+ /**
56
+ * Build the full system prompt for a worker.
57
+ * @param {string} workerType - coding, browser, system, devops, research
58
+ * @param {object} config - App config
59
+ * @param {string|null} skillPrompt - Active skill system prompt (appended for domain expertise)
60
+ */
61
+ export function getWorkerPrompt(workerType, config, skillPrompt = null) {
62
+ const base = WORKER_PROMPTS[workerType];
63
+ if (!base) throw new Error(`Unknown worker type: ${workerType}`);
64
+
65
+ let prompt = base;
66
+
67
+ // Add relevant core tool instructions
68
+ prompt += `\n\n${getCoreToolInstructions(config)}`;
69
+
70
+ // Workers are executors, not conversationalists
71
+ prompt += `\n\n## Worker Rules
72
+ - You are a background worker. Complete the task and report results.
73
+ - Be thorough but efficient. Don't ask clarifying questions — work with what you have.
74
+ - If something fails, try an alternative approach before reporting failure.
75
+ - Keep your final response concise: summarize what you did and the outcome.
76
+
77
+ ## Self-Management
78
+ - You decide when you're done. There is no hard limit on tool calls — use as many as you need.
79
+ - BUT be smart about it: don't loop endlessly. If you have enough data, stop and report.
80
+ - NEVER retry a failing URL/site more than twice. If it times out or errors twice, MOVE ON to a different site or approach immediately.
81
+ - When you've gathered sufficient results, STOP calling tools and return your findings.
82
+ - Aim for quality results, not exhaustive coverage. 5 good results beat 50 incomplete ones.`;
83
+
84
+ if (skillPrompt) {
85
+ prompt += `\n\n## Domain Expertise\n${skillPrompt}`;
86
+ }
87
+
88
+ return prompt;
89
+ }
@@ -7,31 +7,38 @@ export class AnthropicProvider extends BaseProvider {
7
7
  this.client = new Anthropic({ apiKey: this.apiKey });
8
8
  }
9
9
 
10
- async chat({ system, messages, tools }) {
11
- const response = await this.client.messages.create({
10
+ async chat({ system, messages, tools, signal }) {
11
+ const params = {
12
12
  model: this.model,
13
13
  max_tokens: this.maxTokens,
14
14
  temperature: this.temperature,
15
15
  system,
16
- tools,
17
16
  messages,
18
- });
17
+ };
19
18
 
20
- const stopReason = response.stop_reason === 'end_turn' ? 'end_turn' : 'tool_use';
19
+ if (tools && tools.length > 0) {
20
+ params.tools = tools;
21
+ }
21
22
 
22
- const textBlocks = response.content.filter((b) => b.type === 'text');
23
- const text = textBlocks.map((b) => b.text).join('\n');
23
+ return this._callWithResilience(async (timedSignal) => {
24
+ const response = await this.client.messages.create(params, { signal: timedSignal });
24
25
 
25
- const toolCalls = response.content
26
- .filter((b) => b.type === 'tool_use')
27
- .map((b) => ({ id: b.id, name: b.name, input: b.input }));
26
+ const stopReason = response.stop_reason === 'end_turn' ? 'end_turn' : 'tool_use';
28
27
 
29
- return {
30
- stopReason,
31
- text,
32
- toolCalls,
33
- rawContent: response.content,
34
- };
28
+ const textBlocks = response.content.filter((b) => b.type === 'text');
29
+ const text = textBlocks.map((b) => b.text).join('\n');
30
+
31
+ const toolCalls = response.content
32
+ .filter((b) => b.type === 'tool_use')
33
+ .map((b) => ({ id: b.id, name: b.name, input: b.input }));
34
+
35
+ return {
36
+ stopReason,
37
+ text,
38
+ toolCalls,
39
+ rawContent: response.content,
40
+ };
41
+ }, signal);
35
42
  }
36
43
 
37
44
  async ping() {