opc-agent 4.0.0 → 4.0.1
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/README.md +404 -80
- package/README.zh-CN.md +82 -0
- package/dist/cli/chat.d.ts +2 -0
- package/dist/cli/chat.js +134 -0
- package/dist/cli/setup.d.ts +4 -0
- package/dist/cli/setup.js +303 -0
- package/dist/cli.js +106 -6
- package/dist/hub/brain-seed.d.ts +14 -0
- package/dist/hub/brain-seed.js +77 -0
- package/dist/hub/client.d.ts +25 -0
- package/dist/hub/client.js +44 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +12 -3
- package/dist/providers/index.d.ts +1 -1
- package/dist/providers/index.js +54 -1
- package/dist/scheduler/cron-engine.d.ts +41 -0
- package/dist/scheduler/cron-engine.js +200 -0
- package/dist/scheduler/index.d.ts +3 -0
- package/dist/scheduler/index.js +7 -0
- package/dist/skills/builtin/index.d.ts +6 -0
- package/dist/skills/builtin/index.js +402 -0
- package/dist/skills/marketplace.d.ts +30 -0
- package/dist/skills/marketplace.js +142 -0
- package/dist/skills/types.d.ts +34 -0
- package/dist/skills/types.js +16 -0
- package/dist/studio/server.d.ts +25 -0
- package/dist/studio/server.js +780 -0
- package/dist/studio/templates-data.d.ts +21 -0
- package/dist/studio/templates-data.js +148 -0
- package/dist/studio-ui/index.html +2502 -1073
- package/dist/tools/builtin/index.d.ts +1 -0
- package/dist/tools/builtin/index.js +7 -2
- package/dist/tools/builtin/web-search.d.ts +9 -0
- package/dist/tools/builtin/web-search.js +150 -0
- package/dist/tools/document-processor.d.ts +39 -0
- package/dist/tools/document-processor.js +188 -0
- package/dist/tools/image-generator.d.ts +42 -0
- package/dist/tools/image-generator.js +136 -0
- package/dist/tools/web-scraper.d.ts +20 -0
- package/dist/tools/web-scraper.js +148 -0
- package/dist/tools/web-search.d.ts +51 -0
- package/dist/tools/web-search.js +152 -0
- package/install.ps1 +154 -0
- package/install.sh +164 -0
- package/package.json +63 -52
- package/src/cli/chat.ts +99 -0
- package/src/cli/setup.ts +314 -0
- package/src/cli.ts +108 -6
- package/src/hub/brain-seed.ts +54 -0
- package/src/hub/client.ts +60 -0
- package/src/index.ts +4 -2
- package/src/providers/index.ts +64 -1
- package/src/scheduler/cron-engine.ts +191 -0
- package/src/scheduler/index.ts +2 -0
- package/src/skills/builtin/index.ts +408 -0
- package/src/skills/marketplace.ts +113 -0
- package/src/skills/types.ts +42 -0
- package/src/studio/server.ts +1591 -791
- package/src/studio/templates-data.ts +178 -0
- package/src/studio-ui/index.html +2502 -1073
- package/src/tools/builtin/index.ts +37 -35
- package/src/tools/builtin/web-search.ts +126 -0
- package/src/tools/document-processor.ts +213 -0
- package/src/tools/image-generator.ts +150 -0
- package/src/tools/web-scraper.ts +179 -0
- package/src/tools/web-search.ts +180 -0
- package/tests/cron-engine.test.ts +101 -0
- package/tests/document-processor.test.ts +69 -0
- package/tests/e2e-nocode.test.ts +442 -0
- package/tests/image-generator.test.ts +84 -0
- package/tests/settings-api.test.ts +148 -0
- package/tests/setup.test.ts +73 -0
- package/tests/studio.test.ts +402 -229
- package/tests/voice-interaction.test.ts +38 -0
- package/tests/web-search.test.ts +155 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brain-seed downloader and auto-learner.
|
|
3
|
+
* Downloads brain-seed files from Hub and optionally imports into DeepBrain.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as fs from 'fs';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
import type { HubBrainSeed } from './client';
|
|
9
|
+
|
|
10
|
+
export interface BrainSeedResult {
|
|
11
|
+
savedFiles: string[];
|
|
12
|
+
learnedCount: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Save brain-seed files to disk and optionally auto-learn into DeepBrain.
|
|
17
|
+
*/
|
|
18
|
+
export async function downloadAndLearnBrainSeeds(
|
|
19
|
+
projectDir: string,
|
|
20
|
+
seeds: HubBrainSeed[],
|
|
21
|
+
): Promise<BrainSeedResult> {
|
|
22
|
+
if (!seeds || seeds.length === 0) {
|
|
23
|
+
return { savedFiles: [], learnedCount: 0 };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const seedDir = path.join(projectDir, 'brain-seed');
|
|
27
|
+
fs.mkdirSync(seedDir, { recursive: true });
|
|
28
|
+
|
|
29
|
+
const savedFiles: string[] = [];
|
|
30
|
+
for (const seed of seeds) {
|
|
31
|
+
const filePath = path.join(seedDir, seed.filename);
|
|
32
|
+
fs.writeFileSync(filePath, seed.content, 'utf-8');
|
|
33
|
+
savedFiles.push(seed.filename);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Try auto-learn into DeepBrain (optional dependency)
|
|
37
|
+
let learnedCount = 0;
|
|
38
|
+
try {
|
|
39
|
+
const { Brain } = require('deepbrain');
|
|
40
|
+
const brain = new Brain({ database: path.join(projectDir, 'data', 'brain.db') });
|
|
41
|
+
for (const seed of seeds) {
|
|
42
|
+
await brain.learn(seed.content, {
|
|
43
|
+
slug: `brain-seed/${seed.filename.replace(/\.md$/, '')}`,
|
|
44
|
+
title: `Brain Seed: ${seed.tier}`,
|
|
45
|
+
namespace: `seed/${seed.tier}`,
|
|
46
|
+
});
|
|
47
|
+
learnedCount++;
|
|
48
|
+
}
|
|
49
|
+
} catch {
|
|
50
|
+
// deepbrain not installed — that's fine, files are saved
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return { savedFiles, learnedCount };
|
|
54
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workstation Hub API client
|
|
3
|
+
* Fetches agent templates and brain-seed knowledge from the Hub.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const HUB_URL = process.env.OPC_HUB_URL || 'https://hub.deepleaper.com';
|
|
7
|
+
const HUB_TIMEOUT = 5000;
|
|
8
|
+
|
|
9
|
+
export interface HubTemplate {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
description: string;
|
|
13
|
+
category: string;
|
|
14
|
+
tags?: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface HubTemplateDetail extends HubTemplate {
|
|
18
|
+
files: Record<string, string>;
|
|
19
|
+
brainSeeds: HubBrainSeed[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface HubBrainSeed {
|
|
23
|
+
filename: string;
|
|
24
|
+
content: string;
|
|
25
|
+
tier: 'industry' | 'job' | 'workstation';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function hubFetch<T>(path: string): Promise<T> {
|
|
29
|
+
const controller = new AbortController();
|
|
30
|
+
const timer = setTimeout(() => controller.abort(), HUB_TIMEOUT);
|
|
31
|
+
try {
|
|
32
|
+
const res = await fetch(`${HUB_URL}${path}`, {
|
|
33
|
+
signal: controller.signal,
|
|
34
|
+
headers: { 'Accept': 'application/json', 'User-Agent': 'opc-agent/2.0' },
|
|
35
|
+
});
|
|
36
|
+
if (!res.ok) throw new Error(`Hub API ${res.status} ${res.statusText}`);
|
|
37
|
+
return (await res.json()) as T;
|
|
38
|
+
} finally {
|
|
39
|
+
clearTimeout(timer);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function fetchTemplates(query?: string): Promise<HubTemplate[]> {
|
|
44
|
+
const qs = query ? `?q=${encodeURIComponent(query)}` : '';
|
|
45
|
+
return hubFetch<HubTemplate[]>(`/api/templates${qs}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function fetchTemplate(id: string): Promise<HubTemplateDetail> {
|
|
49
|
+
return hubFetch<HubTemplateDetail>(`/api/templates/${encodeURIComponent(id)}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function fetchBrainSeeds(templateId: string): Promise<HubBrainSeed[]> {
|
|
53
|
+
return hubFetch<HubBrainSeed[]>(`/api/templates/${encodeURIComponent(templateId)}/brain-seeds`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function isHubAvailable(): Promise<boolean> {
|
|
57
|
+
return hubFetch<{ ok: boolean }>('/api/health')
|
|
58
|
+
.then(() => true)
|
|
59
|
+
.catch(() => false);
|
|
60
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -176,7 +176,9 @@ export { Scheduler, parseCron, cronMatches } from './core/scheduler';
|
|
|
176
176
|
export type { CronJob, JobHandler } from './core/scheduler';
|
|
177
177
|
|
|
178
178
|
// v1.5.0 — built-in tools + MCP client
|
|
179
|
-
export { getBuiltinTools, getBuiltinToolsByName, rlTools, homeAssistantTools, configureHomeAssistant } from './tools/builtin';
|
|
179
|
+
export { getBuiltinTools, getBuiltinToolsByName, rlTools, homeAssistantTools, configureHomeAssistant, webSearchTools, webSearchTool, webReadTool } from './tools/builtin';
|
|
180
|
+
export { webSearch, parseDuckDuckGoHTML, type SearchResult as WebSearchResult, type WebSearchConfig, type SearchEngine } from './tools/web-search';
|
|
181
|
+
export { scrapeUrl, extractReadableContent, type ScrapedContent } from './tools/web-scraper';
|
|
180
182
|
export { MCPClient } from './tools/mcp-client';
|
|
181
183
|
export type { MCPServerConfig } from './tools/mcp-client';
|
|
182
184
|
|
|
@@ -255,7 +257,7 @@ export type { SMSChannelConfig } from './channels/sms';
|
|
|
255
257
|
export { VoiceCallManager } from './channels/voice-call';
|
|
256
258
|
export type { VoiceCallConfig } from './channels/voice-call';
|
|
257
259
|
export { IDEBridge } from './core/ide-bridge';
|
|
258
|
-
export type { IDEConfig, Diagnostic, TextEdit, Range, SearchOptions, SearchResult } from './core/ide-bridge';
|
|
260
|
+
export type { IDEConfig, Diagnostic, TextEdit, Range, SearchOptions, SearchResult as IDESearchResult } from './core/ide-bridge';
|
|
259
261
|
export { NodeNetwork } from './core/node-network';
|
|
260
262
|
export type { RemoteNode } from './core/node-network';
|
|
261
263
|
export { Gateway } from './core/gateway';
|
package/src/providers/index.ts
CHANGED
|
@@ -325,9 +325,72 @@ function isGeminiNative(): boolean {
|
|
|
325
325
|
return key.startsWith('AQ.') || (baseUrl.includes('googleapis.com') && !baseUrl.includes('/openai'));
|
|
326
326
|
}
|
|
327
327
|
|
|
328
|
+
class ClaudeCLIProvider implements LLMProvider {
|
|
329
|
+
name = 'claude-cli';
|
|
330
|
+
private model: string;
|
|
331
|
+
|
|
332
|
+
constructor(model?: string) {
|
|
333
|
+
this.model = model || 'sonnet';
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
async chat(messages: Message[], systemPrompt?: string, options?: ChatOptions): Promise<string> {
|
|
337
|
+
const { execFile } = await import('child_process');
|
|
338
|
+
const { promisify } = await import('util');
|
|
339
|
+
const execFileAsync = promisify(execFile);
|
|
340
|
+
|
|
341
|
+
// Build the prompt from messages
|
|
342
|
+
const lastMessage = messages[messages.length - 1];
|
|
343
|
+
if (!lastMessage) return '';
|
|
344
|
+
|
|
345
|
+
let prompt = lastMessage.content;
|
|
346
|
+
|
|
347
|
+
// Add tool prompt if tools provided
|
|
348
|
+
if (options?.tools && options.tools.length > 0) {
|
|
349
|
+
prompt += buildToolPrompt(options.tools);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const args = ['--print'];
|
|
353
|
+
if (systemPrompt) {
|
|
354
|
+
args.push('--system-prompt', systemPrompt);
|
|
355
|
+
}
|
|
356
|
+
if (this.model) {
|
|
357
|
+
args.push('--model', this.model);
|
|
358
|
+
}
|
|
359
|
+
args.push(prompt);
|
|
360
|
+
|
|
361
|
+
try {
|
|
362
|
+
const { stdout } = await execFileAsync('claude', args, {
|
|
363
|
+
timeout: 120_000,
|
|
364
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
365
|
+
env: { ...process.env },
|
|
366
|
+
});
|
|
367
|
+
return stdout.trim();
|
|
368
|
+
} catch (err: any) {
|
|
369
|
+
if (err.code === 'ENOENT') {
|
|
370
|
+
throw new Error(
|
|
371
|
+
'Claude CLI not found. Install it: npm install -g @anthropic-ai/claude-code\n' +
|
|
372
|
+
'Then authenticate: claude login'
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
throw new Error(`Claude CLI error: ${err.message}`);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
async *chatStream(messages: Message[], systemPrompt?: string): AsyncIterable<string> {
|
|
380
|
+
// Claude CLI --print doesn't support streaming well, so we do single-shot
|
|
381
|
+
const result = await this.chat(messages, systemPrompt);
|
|
382
|
+
yield result;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
328
386
|
export function createProvider(name: string = 'openai', model?: string, baseUrl?: string, apiKey?: string): LLMProvider {
|
|
329
387
|
const finalModel = model || process.env.OPC_LLM_MODEL || 'gpt-4o-mini';
|
|
330
388
|
|
|
389
|
+
// Claude CLI mode: use local claude command (Claude Max/Pro subscription)
|
|
390
|
+
if (name === 'claude-cli' || process.env.OPC_LLM_PROVIDER === 'claude-cli') {
|
|
391
|
+
return new ClaudeCLIProvider(finalModel !== 'gpt-4o-mini' ? finalModel : undefined);
|
|
392
|
+
}
|
|
393
|
+
|
|
331
394
|
if (name === 'ollama') {
|
|
332
395
|
const ollamaBase = baseUrl || process.env.OPC_LLM_BASE_URL || 'http://localhost:11434/v1';
|
|
333
396
|
const ollamaKey = apiKey || process.env.OPC_LLM_API_KEY || 'ollama';
|
|
@@ -351,4 +414,4 @@ export function createProvider(name: string = 'openai', model?: string, baseUrl?
|
|
|
351
414
|
return new OpenAICompatibleProvider(resolvedName, finalModel, baseUrl, apiKey);
|
|
352
415
|
}
|
|
353
416
|
|
|
354
|
-
export const SUPPORTED_PROVIDERS = ['openai', 'ollama', 'deepseek', 'qwen', 'gemini', 'dashscope', 'zhipu', 'moonshot'] as const;
|
|
417
|
+
export const SUPPORTED_PROVIDERS = ['openai', 'ollama', 'claude-cli', 'deepseek', 'qwen', 'gemini', 'dashscope', 'zhipu', 'moonshot'] as const;
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cron Engine — persistent scheduler with file-based storage.
|
|
3
|
+
* Manages scheduled tasks with cron expressions, persists to ~/.opc/schedules.json,
|
|
4
|
+
* and auto-recovers on startup.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import * as os from 'os';
|
|
10
|
+
import { parseCron, cronMatches, Scheduler } from '../core/scheduler';
|
|
11
|
+
import type { CronJob, JobHandler } from '../core/scheduler';
|
|
12
|
+
|
|
13
|
+
export interface ScheduleTask {
|
|
14
|
+
id: string;
|
|
15
|
+
name: string;
|
|
16
|
+
schedule: string; // cron expression
|
|
17
|
+
description: string; // natural language description
|
|
18
|
+
frequency: 'daily' | 'weekly' | 'monthly' | 'custom';
|
|
19
|
+
time?: string; // HH:mm for simple schedules
|
|
20
|
+
outputChannel: 'telegram' | 'email' | 'web';
|
|
21
|
+
enabled: boolean;
|
|
22
|
+
createdAt: string;
|
|
23
|
+
updatedAt: string;
|
|
24
|
+
lastRun?: string;
|
|
25
|
+
nextRun?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface SchedulesStore {
|
|
29
|
+
tasks: ScheduleTask[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getSchedulesPath(): string {
|
|
33
|
+
const dir = join(os.homedir(), '.opc');
|
|
34
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
35
|
+
return join(dir, 'schedules.json');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function loadSchedules(): SchedulesStore {
|
|
39
|
+
const p = getSchedulesPath();
|
|
40
|
+
if (existsSync(p)) {
|
|
41
|
+
try { return JSON.parse(readFileSync(p, 'utf-8')); } catch { /* ignore */ }
|
|
42
|
+
}
|
|
43
|
+
return { tasks: [] };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function saveSchedules(store: SchedulesStore): void {
|
|
47
|
+
writeFileSync(getSchedulesPath(), JSON.stringify(store, null, 2));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Convert frequency + time to cron expression */
|
|
51
|
+
export function frequencyToCron(frequency: string, time?: string): string {
|
|
52
|
+
const [hour, minute] = (time || '09:00').split(':').map(Number);
|
|
53
|
+
switch (frequency) {
|
|
54
|
+
case 'daily': return `${minute} ${hour} * * *`;
|
|
55
|
+
case 'weekly': return `${minute} ${hour} * * 1`;
|
|
56
|
+
case 'monthly': return `${minute} ${hour} 1 * *`;
|
|
57
|
+
default: return '0 9 * * *'; // fallback
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Compute next run from a cron expression */
|
|
62
|
+
function computeNextRun(cronExpr: string): string | undefined {
|
|
63
|
+
try {
|
|
64
|
+
const parsed = parseCron(cronExpr);
|
|
65
|
+
const d = new Date();
|
|
66
|
+
d.setSeconds(0, 0);
|
|
67
|
+
d.setMinutes(d.getMinutes() + 1);
|
|
68
|
+
for (let i = 0; i < 48 * 60; i++) {
|
|
69
|
+
if (cronMatches(parsed, d)) return d.toISOString();
|
|
70
|
+
d.setMinutes(d.getMinutes() + 1);
|
|
71
|
+
}
|
|
72
|
+
} catch { /* ignore */ }
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export class CronEngine {
|
|
77
|
+
private scheduler: Scheduler;
|
|
78
|
+
private store: SchedulesStore;
|
|
79
|
+
private handler: JobHandler;
|
|
80
|
+
|
|
81
|
+
constructor(handler?: JobHandler) {
|
|
82
|
+
this.handler = handler || (async (job) => {
|
|
83
|
+
console.log(`[cron-engine] Executing job: ${job.name} (${job.id})`);
|
|
84
|
+
});
|
|
85
|
+
this.scheduler = new Scheduler(this.handler);
|
|
86
|
+
this.store = loadSchedules();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Initialize and recover persisted tasks */
|
|
90
|
+
start(): void {
|
|
91
|
+
for (const task of this.store.tasks) {
|
|
92
|
+
if (task.enabled) {
|
|
93
|
+
this.scheduler.addJob({
|
|
94
|
+
id: task.id,
|
|
95
|
+
name: task.name,
|
|
96
|
+
schedule: task.schedule,
|
|
97
|
+
task: task.description,
|
|
98
|
+
enabled: true,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
this.scheduler.start();
|
|
103
|
+
console.log(`[cron-engine] Started with ${this.store.tasks.filter(t => t.enabled).length} active tasks`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
stop(): void {
|
|
107
|
+
this.scheduler.stop();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
listTasks(): ScheduleTask[] {
|
|
111
|
+
// Refresh nextRun
|
|
112
|
+
return this.store.tasks.map(t => ({
|
|
113
|
+
...t,
|
|
114
|
+
nextRun: t.enabled ? computeNextRun(t.schedule) : undefined,
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
getTask(id: string): ScheduleTask | undefined {
|
|
119
|
+
return this.store.tasks.find(t => t.id === id);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
createTask(input: Omit<ScheduleTask, 'id' | 'createdAt' | 'updatedAt' | 'lastRun' | 'nextRun'>): ScheduleTask {
|
|
123
|
+
const id = `sched-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
124
|
+
const now = new Date().toISOString();
|
|
125
|
+
const schedule = input.schedule || frequencyToCron(input.frequency, input.time);
|
|
126
|
+
const task: ScheduleTask = {
|
|
127
|
+
...input,
|
|
128
|
+
id,
|
|
129
|
+
schedule,
|
|
130
|
+
createdAt: now,
|
|
131
|
+
updatedAt: now,
|
|
132
|
+
nextRun: input.enabled ? computeNextRun(schedule) : undefined,
|
|
133
|
+
};
|
|
134
|
+
this.store.tasks.push(task);
|
|
135
|
+
saveSchedules(this.store);
|
|
136
|
+
|
|
137
|
+
if (task.enabled) {
|
|
138
|
+
this.scheduler.addJob({
|
|
139
|
+
id: task.id,
|
|
140
|
+
name: task.name,
|
|
141
|
+
schedule: task.schedule,
|
|
142
|
+
task: task.description,
|
|
143
|
+
enabled: true,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
return task;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
updateTask(id: string, updates: Partial<ScheduleTask>): ScheduleTask | null {
|
|
150
|
+
const idx = this.store.tasks.findIndex(t => t.id === id);
|
|
151
|
+
if (idx === -1) return null;
|
|
152
|
+
|
|
153
|
+
const task = { ...this.store.tasks[idx], ...updates, id, updatedAt: new Date().toISOString() };
|
|
154
|
+
if (updates.frequency || updates.time) {
|
|
155
|
+
task.schedule = updates.schedule || frequencyToCron(task.frequency, task.time);
|
|
156
|
+
}
|
|
157
|
+
task.nextRun = task.enabled ? computeNextRun(task.schedule) : undefined;
|
|
158
|
+
this.store.tasks[idx] = task;
|
|
159
|
+
saveSchedules(this.store);
|
|
160
|
+
|
|
161
|
+
// Update scheduler
|
|
162
|
+
this.scheduler.removeJob(id);
|
|
163
|
+
if (task.enabled) {
|
|
164
|
+
this.scheduler.addJob({
|
|
165
|
+
id: task.id,
|
|
166
|
+
name: task.name,
|
|
167
|
+
schedule: task.schedule,
|
|
168
|
+
task: task.description,
|
|
169
|
+
enabled: true,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
return task;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
deleteTask(id: string): boolean {
|
|
176
|
+
const idx = this.store.tasks.findIndex(t => t.id === id);
|
|
177
|
+
if (idx === -1) return false;
|
|
178
|
+
this.store.tasks.splice(idx, 1);
|
|
179
|
+
saveSchedules(this.store);
|
|
180
|
+
this.scheduler.removeJob(id);
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async runTask(id: string): Promise<boolean> {
|
|
185
|
+
const task = this.store.tasks.find(t => t.id === id);
|
|
186
|
+
if (!task) return false;
|
|
187
|
+
task.lastRun = new Date().toISOString();
|
|
188
|
+
saveSchedules(this.store);
|
|
189
|
+
return this.scheduler.runJob(id);
|
|
190
|
+
}
|
|
191
|
+
}
|