opc-agent 1.4.0 → 2.0.0
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.
- package/CHANGELOG.md +25 -0
- package/README.md +91 -32
- package/dist/channels/telegram.d.ts +30 -9
- package/dist/channels/telegram.js +125 -33
- package/dist/cli.js +415 -8
- package/dist/core/agent.d.ts +23 -0
- package/dist/core/agent.js +120 -3
- package/dist/core/runtime.d.ts +1 -0
- package/dist/core/runtime.js +44 -0
- package/dist/core/scheduler.d.ts +52 -0
- package/dist/core/scheduler.js +168 -0
- package/dist/core/subagent.d.ts +28 -0
- package/dist/core/subagent.js +65 -0
- package/dist/daemon.d.ts +3 -0
- package/dist/daemon.js +134 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +17 -1
- package/dist/providers/index.d.ts +5 -1
- package/dist/providers/index.js +16 -9
- package/dist/schema/oad.d.ts +179 -4
- package/dist/schema/oad.js +12 -1
- package/dist/skills/auto-learn.d.ts +28 -0
- package/dist/skills/auto-learn.js +257 -0
- package/dist/tools/builtin/datetime.d.ts +3 -0
- package/dist/tools/builtin/datetime.js +44 -0
- package/dist/tools/builtin/file.d.ts +3 -0
- package/dist/tools/builtin/file.js +151 -0
- package/dist/tools/builtin/index.d.ts +15 -0
- package/dist/tools/builtin/index.js +30 -0
- package/dist/tools/builtin/shell.d.ts +3 -0
- package/dist/tools/builtin/shell.js +43 -0
- package/dist/tools/builtin/web.d.ts +3 -0
- package/dist/tools/builtin/web.js +37 -0
- package/dist/tools/mcp-client.d.ts +24 -0
- package/dist/tools/mcp-client.js +119 -0
- package/package.json +1 -1
- package/src/channels/telegram.ts +212 -90
- package/src/cli.ts +418 -8
- package/src/core/agent.ts +295 -152
- package/src/core/runtime.ts +47 -0
- package/src/core/scheduler.ts +187 -0
- package/src/core/subagent.ts +98 -0
- package/src/daemon.ts +96 -0
- package/src/index.ts +11 -0
- package/src/providers/index.ts +354 -339
- package/src/schema/oad.ts +167 -154
- package/src/skills/auto-learn.ts +262 -0
- package/src/tools/builtin/datetime.ts +41 -0
- package/src/tools/builtin/file.ts +107 -0
- package/src/tools/builtin/index.ts +28 -0
- package/src/tools/builtin/shell.ts +43 -0
- package/src/tools/builtin/web.ts +35 -0
- package/src/tools/mcp-client.ts +131 -0
- package/tests/auto-learn.test.ts +105 -0
- package/tests/builtin-tools.test.ts +83 -0
- package/tests/cli.test.ts +46 -0
- package/tests/subagent.test.ts +130 -0
- package/tests/telegram-discord.test.ts +60 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple cron scheduler — no external dependencies.
|
|
3
|
+
* Supports cron expressions: star, star-slash-N, M-N, M,N for minute/hour/day/month/weekday.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface CronJob {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
schedule: string;
|
|
10
|
+
task: string;
|
|
11
|
+
enabled: boolean;
|
|
12
|
+
lastRun?: Date;
|
|
13
|
+
nextRun?: Date;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
type CronField = { type: 'any' } | { type: 'every'; step: number } | { type: 'list'; values: number[] };
|
|
17
|
+
|
|
18
|
+
interface ParsedCron {
|
|
19
|
+
minute: CronField;
|
|
20
|
+
hour: CronField;
|
|
21
|
+
dayOfMonth: CronField;
|
|
22
|
+
month: CronField;
|
|
23
|
+
dayOfWeek: CronField;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function parseField(field: string, min: number, max: number): CronField {
|
|
27
|
+
if (field === '*') return { type: 'any' };
|
|
28
|
+
if (field.startsWith('*/')) {
|
|
29
|
+
const step = parseInt(field.slice(2), 10);
|
|
30
|
+
if (isNaN(step) || step <= 0) throw new Error(`Invalid cron step: ${field}`);
|
|
31
|
+
return { type: 'every', step };
|
|
32
|
+
}
|
|
33
|
+
// Could be comma-separated, each part could be a range
|
|
34
|
+
const values: number[] = [];
|
|
35
|
+
for (const part of field.split(',')) {
|
|
36
|
+
if (part.includes('-')) {
|
|
37
|
+
const [a, b] = part.split('-').map(Number);
|
|
38
|
+
if (isNaN(a) || isNaN(b)) throw new Error(`Invalid cron range: ${part}`);
|
|
39
|
+
for (let i = a; i <= b; i++) values.push(i);
|
|
40
|
+
} else {
|
|
41
|
+
const n = parseInt(part, 10);
|
|
42
|
+
if (isNaN(n)) throw new Error(`Invalid cron value: ${part}`);
|
|
43
|
+
values.push(n);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return { type: 'list', values };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function parseCron(expr: string): ParsedCron {
|
|
50
|
+
const parts = expr.trim().split(/\s+/);
|
|
51
|
+
if (parts.length !== 5) throw new Error(`Invalid cron expression (need 5 fields): ${expr}`);
|
|
52
|
+
return {
|
|
53
|
+
minute: parseField(parts[0], 0, 59),
|
|
54
|
+
hour: parseField(parts[1], 0, 23),
|
|
55
|
+
dayOfMonth: parseField(parts[2], 1, 31),
|
|
56
|
+
month: parseField(parts[3], 1, 12),
|
|
57
|
+
dayOfWeek: parseField(parts[4], 0, 6),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function fieldMatches(field: CronField, value: number): boolean {
|
|
62
|
+
switch (field.type) {
|
|
63
|
+
case 'any': return true;
|
|
64
|
+
case 'every': return value % field.step === 0;
|
|
65
|
+
case 'list': return field.values.includes(value);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function cronMatches(parsed: ParsedCron, date: Date): boolean {
|
|
70
|
+
return (
|
|
71
|
+
fieldMatches(parsed.minute, date.getMinutes()) &&
|
|
72
|
+
fieldMatches(parsed.hour, date.getHours()) &&
|
|
73
|
+
fieldMatches(parsed.dayOfMonth, date.getDate()) &&
|
|
74
|
+
fieldMatches(parsed.month, date.getMonth() + 1) &&
|
|
75
|
+
fieldMatches(parsed.dayOfWeek, date.getDay())
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Compute approximate next run (scans forward up to 48h). */
|
|
80
|
+
function computeNextRun(parsed: ParsedCron, from: Date): Date | undefined {
|
|
81
|
+
const d = new Date(from);
|
|
82
|
+
d.setSeconds(0, 0);
|
|
83
|
+
d.setMinutes(d.getMinutes() + 1);
|
|
84
|
+
const limit = 48 * 60; // 48 hours in minutes
|
|
85
|
+
for (let i = 0; i < limit; i++) {
|
|
86
|
+
if (cronMatches(parsed, d)) return new Date(d);
|
|
87
|
+
d.setMinutes(d.getMinutes() + 1);
|
|
88
|
+
}
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export type JobHandler = (job: CronJob) => void | Promise<void>;
|
|
93
|
+
|
|
94
|
+
export class Scheduler {
|
|
95
|
+
private jobs = new Map<string, CronJob>();
|
|
96
|
+
private parsed = new Map<string, ParsedCron>();
|
|
97
|
+
private timer: ReturnType<typeof setInterval> | null = null;
|
|
98
|
+
private handler: JobHandler;
|
|
99
|
+
|
|
100
|
+
constructor(handler: JobHandler) {
|
|
101
|
+
this.handler = handler;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
addJob(job: CronJob): void {
|
|
105
|
+
const p = parseCron(job.schedule);
|
|
106
|
+
this.parsed.set(job.id, p);
|
|
107
|
+
job.nextRun = computeNextRun(p, new Date()) ?? undefined;
|
|
108
|
+
this.jobs.set(job.id, job);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
removeJob(id: string): void {
|
|
112
|
+
this.jobs.delete(id);
|
|
113
|
+
this.parsed.delete(id);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
enableJob(id: string): void {
|
|
117
|
+
const job = this.jobs.get(id);
|
|
118
|
+
if (job) job.enabled = true;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
disableJob(id: string): void {
|
|
122
|
+
const job = this.jobs.get(id);
|
|
123
|
+
if (job) job.enabled = false;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
getJobs(): CronJob[] {
|
|
127
|
+
return Array.from(this.jobs.values());
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
getJob(id: string): CronJob | undefined {
|
|
131
|
+
return this.jobs.get(id);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Run a specific job immediately */
|
|
135
|
+
async runJob(id: string): Promise<boolean> {
|
|
136
|
+
const job = this.jobs.get(id);
|
|
137
|
+
if (!job) return false;
|
|
138
|
+
job.lastRun = new Date();
|
|
139
|
+
await this.handler(job);
|
|
140
|
+
const parsed = this.parsed.get(id);
|
|
141
|
+
if (parsed) job.nextRun = computeNextRun(parsed, new Date());
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
start(): void {
|
|
146
|
+
if (this.timer) return;
|
|
147
|
+
// Check every 60 seconds
|
|
148
|
+
this.timer = setInterval(() => this.tick(), 60_000);
|
|
149
|
+
// Also tick immediately
|
|
150
|
+
this.tick();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
stop(): void {
|
|
154
|
+
if (this.timer) {
|
|
155
|
+
clearInterval(this.timer);
|
|
156
|
+
this.timer = null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private tick(): void {
|
|
161
|
+
const now = new Date();
|
|
162
|
+
for (const [id, job] of this.jobs) {
|
|
163
|
+
if (!job.enabled) continue;
|
|
164
|
+
const parsed = this.parsed.get(id);
|
|
165
|
+
if (!parsed) continue;
|
|
166
|
+
if (cronMatches(parsed, now)) {
|
|
167
|
+
// Avoid double-fire: check lastRun isn't same minute
|
|
168
|
+
if (job.lastRun) {
|
|
169
|
+
const last = job.lastRun;
|
|
170
|
+
if (last.getFullYear() === now.getFullYear() &&
|
|
171
|
+
last.getMonth() === now.getMonth() &&
|
|
172
|
+
last.getDate() === now.getDate() &&
|
|
173
|
+
last.getHours() === now.getHours() &&
|
|
174
|
+
last.getMinutes() === now.getMinutes()) {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
job.lastRun = new Date(now);
|
|
179
|
+
job.nextRun = computeNextRun(parsed, now);
|
|
180
|
+
// Fire and forget (log errors)
|
|
181
|
+
Promise.resolve(this.handler(job)).catch((err) => {
|
|
182
|
+
console.error(`[scheduler] Job "${job.name}" failed:`, err);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { BaseAgent } from './agent';
|
|
2
|
+
import { InMemoryStore } from '../memory';
|
|
3
|
+
import type { Message } from './types';
|
|
4
|
+
|
|
5
|
+
export interface SubAgentConfig {
|
|
6
|
+
name: string;
|
|
7
|
+
task: string;
|
|
8
|
+
systemPrompt?: string;
|
|
9
|
+
provider?: string;
|
|
10
|
+
model?: string;
|
|
11
|
+
timeout?: number;
|
|
12
|
+
isolated?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface SubAgentResult {
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
status: 'completed' | 'failed' | 'timeout';
|
|
19
|
+
result: string;
|
|
20
|
+
duration: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface SubAgentEntry {
|
|
24
|
+
agent: BaseAgent;
|
|
25
|
+
status: string;
|
|
26
|
+
name: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class SubAgentManager {
|
|
30
|
+
private agents: Map<string, SubAgentEntry> = new Map();
|
|
31
|
+
|
|
32
|
+
async spawn(config: SubAgentConfig, parentProvider?: any): Promise<SubAgentResult> {
|
|
33
|
+
const id = `sub_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
34
|
+
const timeout = config.timeout ?? 300000;
|
|
35
|
+
const isolated = config.isolated !== false;
|
|
36
|
+
|
|
37
|
+
const agent = new BaseAgent({
|
|
38
|
+
name: config.name,
|
|
39
|
+
systemPrompt: config.systemPrompt ?? 'You are a helpful sub-agent.',
|
|
40
|
+
provider: config.provider ?? 'openai',
|
|
41
|
+
model: config.model,
|
|
42
|
+
memory: isolated ? new InMemoryStore() : undefined,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
this.agents.set(id, { agent, status: 'running', name: config.name });
|
|
46
|
+
|
|
47
|
+
const message: Message = {
|
|
48
|
+
id: `msg_${Date.now()}`,
|
|
49
|
+
role: 'user',
|
|
50
|
+
content: config.task,
|
|
51
|
+
timestamp: Date.now(),
|
|
52
|
+
metadata: { subAgentId: id },
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const start = Date.now();
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const result = await Promise.race([
|
|
59
|
+
agent.handleMessage(message),
|
|
60
|
+
new Promise<never>((_, reject) =>
|
|
61
|
+
setTimeout(() => reject(new Error('SubAgent timeout')), timeout),
|
|
62
|
+
),
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
const duration = Date.now() - start;
|
|
66
|
+
this.agents.set(id, { agent, status: 'completed', name: config.name });
|
|
67
|
+
|
|
68
|
+
return { id, name: config.name, status: 'completed', result: result.content, duration };
|
|
69
|
+
} catch (err) {
|
|
70
|
+
const duration = Date.now() - start;
|
|
71
|
+
const isTimeout = (err as Error).message.includes('timeout');
|
|
72
|
+
const status = isTimeout ? 'timeout' : 'failed';
|
|
73
|
+
this.agents.set(id, { agent, status, name: config.name });
|
|
74
|
+
|
|
75
|
+
return { id, name: config.name, status, result: (err as Error).message, duration };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async spawnParallel(configs: SubAgentConfig[], parentProvider?: any): Promise<SubAgentResult[]> {
|
|
80
|
+
return Promise.all(configs.map((c) => this.spawn(c, parentProvider)));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
list(): Array<{ id: string; name: string; status: string }> {
|
|
84
|
+
return Array.from(this.agents.entries()).map(([id, entry]) => ({
|
|
85
|
+
id,
|
|
86
|
+
name: entry.name,
|
|
87
|
+
status: entry.status,
|
|
88
|
+
}));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
kill(id: string): boolean {
|
|
92
|
+
const entry = this.agents.get(id);
|
|
93
|
+
if (!entry) return false;
|
|
94
|
+
entry.status = 'killed';
|
|
95
|
+
this.agents.set(id, entry);
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
}
|
package/src/daemon.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Daemon entry point — spawned by `opc start` as a detached background process.
|
|
4
|
+
* Loads agent.yaml, creates runtime, starts all channels, writes heartbeat.
|
|
5
|
+
*/
|
|
6
|
+
import * as fs from 'fs';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
import { AgentRuntime } from './core/runtime';
|
|
9
|
+
|
|
10
|
+
const OPC_DIR = path.resolve('.opc');
|
|
11
|
+
const HEARTBEAT_FILE = path.join(OPC_DIR, 'heartbeat');
|
|
12
|
+
const LOG_FILE = path.join(OPC_DIR, 'agent.log');
|
|
13
|
+
const PID_FILE = path.join(OPC_DIR, 'agent.pid');
|
|
14
|
+
const HEARTBEAT_INTERVAL = 30_000;
|
|
15
|
+
|
|
16
|
+
function ensureDir(dir: string) {
|
|
17
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function log(msg: string) {
|
|
21
|
+
const line = `[${new Date().toISOString()}] ${msg}\n`;
|
|
22
|
+
try { fs.appendFileSync(LOG_FILE, line); } catch { /* ignore */ }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function main() {
|
|
26
|
+
ensureDir(OPC_DIR);
|
|
27
|
+
|
|
28
|
+
// Redirect stdout/stderr to log file
|
|
29
|
+
const logStream = fs.createWriteStream(LOG_FILE, { flags: 'a' });
|
|
30
|
+
process.stdout.write = logStream.write.bind(logStream) as any;
|
|
31
|
+
process.stderr.write = logStream.write.bind(logStream) as any;
|
|
32
|
+
|
|
33
|
+
// Write PID
|
|
34
|
+
fs.writeFileSync(PID_FILE, String(process.pid));
|
|
35
|
+
log(`Daemon started, PID=${process.pid}`);
|
|
36
|
+
|
|
37
|
+
// Write start time for uptime calculation
|
|
38
|
+
fs.writeFileSync(path.join(OPC_DIR, 'started'), String(Date.now()));
|
|
39
|
+
|
|
40
|
+
// Heartbeat
|
|
41
|
+
const heartbeatTimer = setInterval(() => {
|
|
42
|
+
try { fs.writeFileSync(HEARTBEAT_FILE, String(Date.now())); } catch { /* ignore */ }
|
|
43
|
+
}, HEARTBEAT_INTERVAL);
|
|
44
|
+
fs.writeFileSync(HEARTBEAT_FILE, String(Date.now()));
|
|
45
|
+
|
|
46
|
+
// Load .env
|
|
47
|
+
const envPath = path.resolve('.env');
|
|
48
|
+
if (fs.existsSync(envPath)) {
|
|
49
|
+
try {
|
|
50
|
+
const content = fs.readFileSync(envPath, 'utf-8');
|
|
51
|
+
for (const line of content.split('\n')) {
|
|
52
|
+
const trimmed = line.trim();
|
|
53
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
54
|
+
const eqIdx = trimmed.indexOf('=');
|
|
55
|
+
if (eqIdx === -1) continue;
|
|
56
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
57
|
+
const value = trimmed.slice(eqIdx + 1).trim();
|
|
58
|
+
if (!process.env[key]) process.env[key] = value;
|
|
59
|
+
}
|
|
60
|
+
} catch { /* ignore */ }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Determine config file
|
|
64
|
+
const configFile = fs.existsSync('agent.yaml') ? 'agent.yaml' : 'oad.yaml';
|
|
65
|
+
|
|
66
|
+
const runtime = new AgentRuntime();
|
|
67
|
+
await runtime.loadConfig(configFile);
|
|
68
|
+
await runtime.initialize();
|
|
69
|
+
await runtime.start();
|
|
70
|
+
|
|
71
|
+
log(`Agent running (config=${configFile})`);
|
|
72
|
+
|
|
73
|
+
// Graceful shutdown
|
|
74
|
+
const shutdown = async (signal: string) => {
|
|
75
|
+
log(`Received ${signal}, shutting down...`);
|
|
76
|
+
clearInterval(heartbeatTimer);
|
|
77
|
+
await runtime.stop();
|
|
78
|
+
try { fs.unlinkSync(PID_FILE); } catch { /* ignore */ }
|
|
79
|
+
log('Daemon stopped');
|
|
80
|
+
process.exit(0);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
84
|
+
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
85
|
+
|
|
86
|
+
// On Windows, handle the message-based kill
|
|
87
|
+
process.on('message', (msg) => {
|
|
88
|
+
if (msg === 'shutdown') shutdown('message:shutdown');
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
main().catch((err) => {
|
|
93
|
+
log(`Fatal error: ${err instanceof Error ? err.message : String(err)}`);
|
|
94
|
+
try { fs.unlinkSync(PID_FILE); } catch { /* ignore */ }
|
|
95
|
+
process.exit(1);
|
|
96
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -76,6 +76,8 @@ export { SchedulerSkill } from './skills/scheduler';
|
|
|
76
76
|
export type { ScheduledTask } from './skills/scheduler';
|
|
77
77
|
export { DocumentSkill } from './skills/document';
|
|
78
78
|
export type { DocumentChunk } from './skills/document';
|
|
79
|
+
export { SkillLearner, skillToMarkdown, parseSkillMarkdown } from './skills/auto-learn';
|
|
80
|
+
export type { LearnedSkill } from './skills/auto-learn';
|
|
79
81
|
|
|
80
82
|
// v0.9.0 modules
|
|
81
83
|
export { runTests, loadTestCases, formatReport } from './testing';
|
|
@@ -113,3 +115,12 @@ export type { StreamChunk, StreamOptions } from './core/streaming';
|
|
|
113
115
|
// v1.3.0 modules
|
|
114
116
|
export { TraceCollector, ConsoleExporter, DeepBrainExporter } from './traces';
|
|
115
117
|
export type { Span, SpanEvent, TraceExporter } from './traces';
|
|
118
|
+
|
|
119
|
+
// v1.4.0 modules
|
|
120
|
+
export { Scheduler, parseCron, cronMatches } from './core/scheduler';
|
|
121
|
+
export type { CronJob, JobHandler } from './core/scheduler';
|
|
122
|
+
|
|
123
|
+
// v1.5.0 — built-in tools + MCP client
|
|
124
|
+
export { getBuiltinTools, getBuiltinToolsByName } from './tools/builtin';
|
|
125
|
+
export { MCPClient } from './tools/mcp-client';
|
|
126
|
+
export type { MCPServerConfig } from './tools/mcp-client';
|