Skip to content

Commit 0248647

Browse files
zanderone1980claude
andcommitted
v1.2.0: Autonomous agent upgrade — browser skills, web search, routines, self-improvement
Enhanced browser with 7 new actions (scroll, wait, press_key, hover, find_by_text, switch_tab, new_tab) and React-compatible typeText fix for X/Gmail. Added DuckDuckGo web search tool (zero dependencies). Added routine scheduler for recurring tasks with cron expressions. Expanded system prompt from coding assistant to fully autonomous agent with memory-based self-improvement patterns. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 704e9a4 commit 0248647

File tree

9 files changed

+752
-26
lines changed

9 files changed

+752
-26
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "codebot-ai",
3-
"version": "1.1.2",
3+
"version": "1.2.0",
44
"description": "Local-first AI coding assistant. Zero dependencies. Works with Ollama, LM Studio, vLLM, Claude, GPT, Gemini, and more.",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",

src/agent.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ export class Agent {
225225
// memory unavailable
226226
}
227227

228-
let prompt = `You are CodeBot, an AI coding assistant. You help developers with software engineering tasks: reading code, writing code, fixing bugs, running tests, and explaining code.
228+
let prompt = `You are CodeBot, a fully autonomous AI agent. You help with ANY task: coding, research, sending emails, posting on social media, web automation, and anything else that can be accomplished with a computer.
229229
230230
CRITICAL IDENTITY — you MUST follow this:
231231
- Your name is CodeBot.
@@ -235,11 +235,19 @@ CRITICAL IDENTITY — you MUST follow this:
235235
- Never claim to be made by or affiliated with OpenAI, GPT, Claude, Gemini, or any LLM provider. You are CodeBot by Ascendral.
236236
237237
Rules:
238-
- Always read files before editing them.
239-
- Prefer editing over rewriting entire files.
240-
- Be concise and direct.
241-
- Explain what you're doing and why.
238+
- When given a goal, break it into steps and execute them using your tools.
239+
- Always read files before editing them. Prefer editing over rewriting entire files.
240+
- Be concise and direct. Explain what you're doing and why.
242241
- Use the memory tool to save important context, user preferences, and patterns you learn. Memory persists across sessions.
242+
- After completing social media posts, emails, or research tasks, log the outcome to memory (file: "outcomes") for future learning.
243+
- Before doing social media or email tasks, read your memory files for any saved skills or style guides.
244+
245+
Skills:
246+
- Web browsing: use the browser tool to navigate, click, type, find elements by text, scroll, press keys, hover, and manage tabs.
247+
- Research: use web_search for quick lookups, then browser for deep reading of specific pages.
248+
- Social media: navigate to the platform, find the compose area with find_by_text, type your content, and submit.
249+
- Email: navigate to Gmail/email, compose and send messages through the browser interface.
250+
- Routines: use the routine tool to schedule recurring tasks (daily posts, email checks, etc.).
243251
244252
${repoMap}${memoryBlock}`;
245253

src/cli.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ import { SessionManager } from './history';
88
import { loadConfig, isFirstRun, runSetup } from './setup';
99
import { banner, randomGreeting, compactBanner } from './banner';
1010
import { EditFileTool } from './tools';
11+
import { Scheduler } from './scheduler';
1112

12-
const VERSION = '1.1.0';
13+
const VERSION = '1.2.0';
1314

1415
// Session-wide token tracking
1516
let sessionTokens = { input: 0, output: 0, total: 0 };
@@ -112,8 +113,15 @@ export async function main() {
112113
return;
113114
}
114115

116+
// Start the routine scheduler in the background
117+
const scheduler = new Scheduler(agent, (text) => process.stdout.write(text));
118+
scheduler.start();
119+
115120
// Interactive REPL
116121
await repl(agent, config, session);
122+
123+
// Cleanup scheduler on exit
124+
scheduler.stop();
117125
}
118126

119127
function createProvider(config: Config): LLMProvider {
@@ -276,6 +284,7 @@ function handleSlashCommand(input: string, agent: Agent, config: Config) {
276284
/clear Clear conversation history
277285
/compact Force context compaction
278286
/auto Toggle autonomous mode
287+
/routines List scheduled routines
279288
/undo Undo last file edit (/undo [path])
280289
/usage Show token usage for this session
281290
/config Show current config
@@ -339,6 +348,12 @@ function handleSlashCommand(input: string, agent: Agent, config: Config) {
339348
console.log(` Total: ${(sessionTokens.input + sessionTokens.output).toLocaleString()} tokens`);
340349
break;
341350
}
351+
case '/routines': {
352+
const { RoutineTool } = require('./tools/routine');
353+
const rt = new RoutineTool();
354+
rt.execute({ action: 'list' }).then((out: string) => console.log('\n' + out));
355+
break;
356+
}
342357
case '/config':
343358
console.log(JSON.stringify({ ...config, apiKey: config.apiKey ? '***' : undefined }, null, 2));
344359
break;

src/scheduler.ts

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import * as fs from 'fs';
2+
import * as path from 'path';
3+
import * as os from 'os';
4+
import { Routine, matchesCron } from './tools/routine';
5+
import { Agent } from './agent';
6+
import { AgentEvent } from './types';
7+
8+
const ROUTINES_FILE = path.join(os.homedir(), '.codebot', 'routines.json');
9+
10+
export class Scheduler {
11+
private agent: Agent;
12+
private interval: ReturnType<typeof setInterval> | null = null;
13+
private running = false;
14+
private onOutput?: (text: string) => void;
15+
16+
constructor(agent: Agent, onOutput?: (text: string) => void) {
17+
this.agent = agent;
18+
this.onOutput = onOutput;
19+
}
20+
21+
/** Start the scheduler — checks routines every 60 seconds */
22+
start(): void {
23+
if (this.interval) return;
24+
25+
// Check every 60 seconds
26+
this.interval = setInterval(() => this.tick(), 60_000);
27+
28+
// Also do an immediate check
29+
this.tick();
30+
}
31+
32+
/** Stop the scheduler */
33+
stop(): void {
34+
if (this.interval) {
35+
clearInterval(this.interval);
36+
this.interval = null;
37+
}
38+
}
39+
40+
/** Check if any routines need to run right now */
41+
private tick(): void {
42+
if (this.running) return; // Don't run if already executing a routine
43+
44+
const routines = this.loadRoutines();
45+
const now = new Date();
46+
47+
for (const routine of routines) {
48+
if (!routine.enabled) continue;
49+
50+
// Check if the cron schedule matches current time
51+
if (!matchesCron(routine.schedule, now)) continue;
52+
53+
// Don't re-run if already ran this minute
54+
if (routine.lastRun) {
55+
const lastRun = new Date(routine.lastRun);
56+
const diffMs = now.getTime() - lastRun.getTime();
57+
if (diffMs < 60_000) continue; // Already ran this minute
58+
}
59+
60+
// Run the routine
61+
this.executeRoutine(routine, routines);
62+
break; // Only run one routine per tick to avoid conflicts
63+
}
64+
}
65+
66+
private async executeRoutine(routine: Routine, allRoutines: Routine[]): Promise<void> {
67+
this.running = true;
68+
69+
try {
70+
this.onOutput?.(`\n⏰ Running routine: ${routine.name}\n Task: ${routine.prompt}\n`);
71+
72+
// Run the agent with the routine's prompt
73+
for await (const event of this.agent.run(routine.prompt) as AsyncGenerator<AgentEvent>) {
74+
switch (event.type) {
75+
case 'text':
76+
this.onOutput?.(event.text || '');
77+
break;
78+
case 'tool_call':
79+
this.onOutput?.(`\n⚡ ${event.toolCall?.name}(${Object.entries(event.toolCall?.args || {}).map(([k, v]) => `${k}: ${typeof v === 'string' ? v.substring(0, 40) : v}`).join(', ')})\n`);
80+
break;
81+
case 'tool_result':
82+
this.onOutput?.(` ✓ ${event.toolResult?.result?.substring(0, 100) || ''}\n`);
83+
break;
84+
case 'error':
85+
this.onOutput?.(` ✗ Error: ${event.error}\n`);
86+
break;
87+
}
88+
}
89+
90+
// Update last run time
91+
routine.lastRun = new Date().toISOString();
92+
this.saveRoutines(allRoutines);
93+
94+
this.onOutput?.(`\n✓ Routine "${routine.name}" completed.\n`);
95+
} catch (err: unknown) {
96+
const msg = err instanceof Error ? err.message : String(err);
97+
this.onOutput?.(`\n✗ Routine "${routine.name}" failed: ${msg}\n`);
98+
} finally {
99+
this.running = false;
100+
}
101+
}
102+
103+
private loadRoutines(): Routine[] {
104+
try {
105+
if (fs.existsSync(ROUTINES_FILE)) {
106+
return JSON.parse(fs.readFileSync(ROUTINES_FILE, 'utf-8'));
107+
}
108+
} catch { /* corrupt file */ }
109+
return [];
110+
}
111+
112+
private saveRoutines(routines: Routine[]): void {
113+
const dir = path.dirname(ROUTINES_FILE);
114+
fs.mkdirSync(dir, { recursive: true });
115+
fs.writeFileSync(ROUTINES_FILE, JSON.stringify(routines, null, 2) + '\n');
116+
}
117+
}

0 commit comments

Comments
 (0)