ideaco 1.1.5
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/.dockerignore +33 -0
- package/.nvmrc +1 -0
- package/ARCHITECTURE.md +394 -0
- package/Dockerfile +50 -0
- package/LICENSE +29 -0
- package/README.md +206 -0
- package/bin/i18n.js +46 -0
- package/bin/ideaco.js +494 -0
- package/deploy.sh +15 -0
- package/docker-compose.yml +30 -0
- package/electron/main.cjs +986 -0
- package/electron/preload.cjs +14 -0
- package/electron/web-backends.cjs +854 -0
- package/jsconfig.json +8 -0
- package/next.config.mjs +34 -0
- package/package.json +134 -0
- package/postcss.config.mjs +6 -0
- package/public/demo/dashboard.png +0 -0
- package/public/demo/employee.png +0 -0
- package/public/demo/messages.png +0 -0
- package/public/demo/office.png +0 -0
- package/public/demo/requirement.png +0 -0
- package/public/logo.jpeg +0 -0
- package/public/logo.png +0 -0
- package/scripts/prepare-electron.js +67 -0
- package/scripts/release.js +76 -0
- package/src/app/api/agents/[agentId]/chat/route.js +70 -0
- package/src/app/api/agents/[agentId]/conversations/route.js +35 -0
- package/src/app/api/agents/[agentId]/route.js +106 -0
- package/src/app/api/avatar/route.js +104 -0
- package/src/app/api/browse-dir/route.js +44 -0
- package/src/app/api/chat/route.js +265 -0
- package/src/app/api/company/factory-reset/route.js +43 -0
- package/src/app/api/company/route.js +82 -0
- package/src/app/api/departments/[deptId]/agents/[agentId]/dismiss/route.js +19 -0
- package/src/app/api/departments/route.js +92 -0
- package/src/app/api/group-chat-loop/events/route.js +70 -0
- package/src/app/api/group-chat-loop/route.js +94 -0
- package/src/app/api/mailbox/route.js +100 -0
- package/src/app/api/messages/route.js +14 -0
- package/src/app/api/providers/[id]/configure/route.js +21 -0
- package/src/app/api/providers/[id]/refresh-cookie/route.js +38 -0
- package/src/app/api/providers/[id]/test-cookie/route.js +28 -0
- package/src/app/api/providers/route.js +11 -0
- package/src/app/api/requirements/route.js +242 -0
- package/src/app/api/secretary/route.js +65 -0
- package/src/app/api/system/cli-backends/route.js +91 -0
- package/src/app/api/system/cron/route.js +110 -0
- package/src/app/api/system/knowledge/route.js +104 -0
- package/src/app/api/system/plugins/route.js +40 -0
- package/src/app/api/system/skills/route.js +46 -0
- package/src/app/api/system/status/route.js +46 -0
- package/src/app/api/talent-market/[profileId]/recall/route.js +22 -0
- package/src/app/api/talent-market/[profileId]/route.js +17 -0
- package/src/app/api/talent-market/route.js +26 -0
- package/src/app/api/teams/route.js +773 -0
- package/src/app/api/ws-files/[departmentId]/file/route.js +27 -0
- package/src/app/api/ws-files/[departmentId]/files/route.js +22 -0
- package/src/app/globals.css +130 -0
- package/src/app/layout.jsx +40 -0
- package/src/app/page.jsx +97 -0
- package/src/components/AgentChatModal.jsx +164 -0
- package/src/components/AgentDetailModal.jsx +425 -0
- package/src/components/AgentSpyModal.jsx +481 -0
- package/src/components/AvatarGrid.jsx +29 -0
- package/src/components/BossProfileModal.jsx +162 -0
- package/src/components/CachedAvatar.jsx +77 -0
- package/src/components/ChatPanel.jsx +219 -0
- package/src/components/ChatShared.jsx +255 -0
- package/src/components/DepartmentDetail.jsx +842 -0
- package/src/components/DepartmentView.jsx +367 -0
- package/src/components/FileReference.jsx +260 -0
- package/src/components/FilesView.jsx +465 -0
- package/src/components/GroupChatView.jsx +799 -0
- package/src/components/Mailbox.jsx +926 -0
- package/src/components/MessagesView.jsx +112 -0
- package/src/components/OnboardingGuide.jsx +209 -0
- package/src/components/OrgTree.jsx +151 -0
- package/src/components/Overview.jsx +391 -0
- package/src/components/PixelOffice.jsx +2281 -0
- package/src/components/ProviderGrid.jsx +551 -0
- package/src/components/ProvidersBoard.jsx +16 -0
- package/src/components/RequirementDetail.jsx +1279 -0
- package/src/components/RequirementsBoard.jsx +187 -0
- package/src/components/SecretarySettings.jsx +295 -0
- package/src/components/SetupWizard.jsx +388 -0
- package/src/components/Sidebar.jsx +169 -0
- package/src/components/SystemMonitor.jsx +808 -0
- package/src/components/TalentMarket.jsx +183 -0
- package/src/components/TeamDetail.jsx +697 -0
- package/src/core/agent/base-agent.js +104 -0
- package/src/core/agent/chat-store.js +602 -0
- package/src/core/agent/cli-agent/backends/claude-code/README.md +52 -0
- package/src/core/agent/cli-agent/backends/claude-code/config.js +27 -0
- package/src/core/agent/cli-agent/backends/codebuddy/README.md +236 -0
- package/src/core/agent/cli-agent/backends/codebuddy/config.js +27 -0
- package/src/core/agent/cli-agent/backends/codex/README.md +51 -0
- package/src/core/agent/cli-agent/backends/codex/config.js +27 -0
- package/src/core/agent/cli-agent/backends/index.js +27 -0
- package/src/core/agent/cli-agent/backends/registry.js +580 -0
- package/src/core/agent/cli-agent/index.js +154 -0
- package/src/core/agent/index.js +60 -0
- package/src/core/agent/llm-agent/client.js +320 -0
- package/src/core/agent/llm-agent/index.js +97 -0
- package/src/core/agent/message-bus.js +211 -0
- package/src/core/agent/session.js +608 -0
- package/src/core/agent/tools.js +596 -0
- package/src/core/agent/web-agent/backends/base-backend.js +180 -0
- package/src/core/agent/web-agent/backends/chatgpt/client.js +146 -0
- package/src/core/agent/web-agent/backends/chatgpt/config.js +148 -0
- package/src/core/agent/web-agent/backends/chatgpt/dom-scripts.js +303 -0
- package/src/core/agent/web-agent/backends/index.js +91 -0
- package/src/core/agent/web-agent/index.js +278 -0
- package/src/core/agent/web-agent/web-client.js +407 -0
- package/src/core/employee/base-employee.js +1088 -0
- package/src/core/employee/index.js +35 -0
- package/src/core/employee/knowledge.js +327 -0
- package/src/core/employee/lifecycle.js +990 -0
- package/src/core/employee/memory/index.js +642 -0
- package/src/core/employee/memory/store.js +143 -0
- package/src/core/employee/performance.js +224 -0
- package/src/core/employee/secretary.js +625 -0
- package/src/core/employee/skills.js +398 -0
- package/src/core/index.js +38 -0
- package/src/core/organization/company.js +2600 -0
- package/src/core/organization/department.js +737 -0
- package/src/core/organization/group-chat-loop.js +264 -0
- package/src/core/organization/index.js +8 -0
- package/src/core/organization/persistence.js +111 -0
- package/src/core/organization/team.js +267 -0
- package/src/core/organization/workforce/hr.js +377 -0
- package/src/core/organization/workforce/providers.js +468 -0
- package/src/core/organization/workforce/role-archetypes.js +805 -0
- package/src/core/organization/workforce/talent-market.js +205 -0
- package/src/core/prompts.js +532 -0
- package/src/core/requirement.js +1789 -0
- package/src/core/system/audit.js +483 -0
- package/src/core/system/cron.js +449 -0
- package/src/core/system/index.js +7 -0
- package/src/core/system/plugin.js +2183 -0
- package/src/core/utils/json-parse.js +188 -0
- package/src/core/workspace.js +239 -0
- package/src/lib/api-i18n.js +211 -0
- package/src/lib/avatar.js +268 -0
- package/src/lib/client-store.js +1025 -0
- package/src/lib/config-validator.js +483 -0
- package/src/lib/format-time.js +22 -0
- package/src/lib/hooks.js +414 -0
- package/src/lib/i18n.js +134 -0
- package/src/lib/paths.js +23 -0
- package/src/lib/store.js +72 -0
- package/src/locales/de.js +393 -0
- package/src/locales/en.js +1054 -0
- package/src/locales/es.js +393 -0
- package/src/locales/fr.js +393 -0
- package/src/locales/ja.js +501 -0
- package/src/locales/ko.js +513 -0
- package/src/locales/zh.js +828 -0
- package/tailwind.config.mjs +11 -0
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cron Scheduler - Automated task scheduling system for agents
|
|
3
|
+
*
|
|
4
|
+
* Distilled from OpenClaw's cron system (vendor/openclaw/src/cron/)
|
|
5
|
+
* Re-implemented as an enterprise "automated workflow / standing orders" system
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Cron expression parsing (simplified subset)
|
|
9
|
+
* - Job registration and lifecycle management
|
|
10
|
+
* - Agent-bound scheduled tasks
|
|
11
|
+
* - Execution history and failure tracking
|
|
12
|
+
* - Graceful concurrent execution control
|
|
13
|
+
*/
|
|
14
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Job status enum
|
|
18
|
+
*/
|
|
19
|
+
export const JobStatus = {
|
|
20
|
+
ACTIVE: 'active',
|
|
21
|
+
PAUSED: 'paused',
|
|
22
|
+
RUNNING: 'running',
|
|
23
|
+
COMPLETED: 'completed', // For one-shot jobs
|
|
24
|
+
ERROR: 'error',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Simplified cron expression parser
|
|
29
|
+
* Supports: "every Xm" (minutes), "every Xh" (hours), "daily HH:MM", "weekly DOW HH:MM"
|
|
30
|
+
* Also supports standard 5-field cron: "* * * * *" (min hour dom month dow)
|
|
31
|
+
*/
|
|
32
|
+
export function parseCronExpression(expression) {
|
|
33
|
+
const expr = expression.trim().toLowerCase();
|
|
34
|
+
|
|
35
|
+
// "every Xm" - every X minutes
|
|
36
|
+
const minuteMatch = expr.match(/^every\s+(\d+)\s*m(?:in(?:ute)?s?)?$/);
|
|
37
|
+
if (minuteMatch) {
|
|
38
|
+
return { type: 'interval', intervalMs: parseInt(minuteMatch[1]) * 60 * 1000 };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// "every Xh" - every X hours
|
|
42
|
+
const hourMatch = expr.match(/^every\s+(\d+)\s*h(?:ours?)?$/);
|
|
43
|
+
if (hourMatch) {
|
|
44
|
+
return { type: 'interval', intervalMs: parseInt(hourMatch[1]) * 3600 * 1000 };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// "daily HH:MM" - daily at specific time
|
|
48
|
+
const dailyMatch = expr.match(/^daily\s+(\d{1,2}):(\d{2})$/);
|
|
49
|
+
if (dailyMatch) {
|
|
50
|
+
return {
|
|
51
|
+
type: 'daily',
|
|
52
|
+
hour: parseInt(dailyMatch[1]),
|
|
53
|
+
minute: parseInt(dailyMatch[2]),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// "weekly DOW HH:MM" - weekly on specific day and time
|
|
58
|
+
const weeklyMatch = expr.match(/^weekly\s+(mon|tue|wed|thu|fri|sat|sun)\s+(\d{1,2}):(\d{2})$/);
|
|
59
|
+
if (weeklyMatch) {
|
|
60
|
+
const dayMap = { sun: 0, mon: 1, tue: 2, wed: 3, thu: 4, fri: 5, sat: 6 };
|
|
61
|
+
return {
|
|
62
|
+
type: 'weekly',
|
|
63
|
+
dayOfWeek: dayMap[weeklyMatch[1]],
|
|
64
|
+
hour: parseInt(weeklyMatch[2]),
|
|
65
|
+
minute: parseInt(weeklyMatch[3]),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
throw new Error(`Invalid cron expression: "${expression}". Supported formats: "every Xm", "every Xh", "daily HH:MM", "weekly DOW HH:MM"`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Calculate next run time for a parsed cron schedule
|
|
74
|
+
* @param {object} schedule - Parsed cron schedule
|
|
75
|
+
* @param {Date} after - Calculate next run after this time
|
|
76
|
+
* @returns {Date}
|
|
77
|
+
*/
|
|
78
|
+
export function getNextRunTime(schedule, after = new Date()) {
|
|
79
|
+
if (schedule.type === 'interval') {
|
|
80
|
+
return new Date(after.getTime() + schedule.intervalMs);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (schedule.type === 'daily') {
|
|
84
|
+
const next = new Date(after);
|
|
85
|
+
next.setHours(schedule.hour, schedule.minute, 0, 0);
|
|
86
|
+
if (next <= after) {
|
|
87
|
+
next.setDate(next.getDate() + 1);
|
|
88
|
+
}
|
|
89
|
+
return next;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (schedule.type === 'weekly') {
|
|
93
|
+
const next = new Date(after);
|
|
94
|
+
next.setHours(schedule.hour, schedule.minute, 0, 0);
|
|
95
|
+
const currentDay = next.getDay();
|
|
96
|
+
let daysUntil = schedule.dayOfWeek - currentDay;
|
|
97
|
+
if (daysUntil < 0 || (daysUntil === 0 && next <= after)) {
|
|
98
|
+
daysUntil += 7;
|
|
99
|
+
}
|
|
100
|
+
next.setDate(next.getDate() + daysUntil);
|
|
101
|
+
return next;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
throw new Error('Unknown schedule type');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Scheduled Job definition
|
|
109
|
+
*/
|
|
110
|
+
class CronJob {
|
|
111
|
+
/**
|
|
112
|
+
* @param {object} config
|
|
113
|
+
* @param {string} config.id - Unique job ID
|
|
114
|
+
* @param {string} config.name - Human-readable job name
|
|
115
|
+
* @param {string} config.description - What this job does
|
|
116
|
+
* @param {string} config.cronExpression - Schedule expression
|
|
117
|
+
* @param {string} config.agentId - Agent to execute this job
|
|
118
|
+
* @param {string} config.taskPrompt - The prompt/instruction for the agent
|
|
119
|
+
* @param {boolean} config.oneShot - If true, run once then mark completed
|
|
120
|
+
* @param {number} config.maxConsecutiveFailures - Stop after N consecutive failures
|
|
121
|
+
*/
|
|
122
|
+
constructor(config) {
|
|
123
|
+
this.id = config.id || uuidv4();
|
|
124
|
+
this.name = config.name;
|
|
125
|
+
this.description = config.description || '';
|
|
126
|
+
this.cronExpression = config.cronExpression;
|
|
127
|
+
this.schedule = parseCronExpression(config.cronExpression);
|
|
128
|
+
this.agentId = config.agentId;
|
|
129
|
+
this.taskPrompt = config.taskPrompt;
|
|
130
|
+
this.oneShot = config.oneShot || false;
|
|
131
|
+
this.maxConsecutiveFailures = config.maxConsecutiveFailures ?? 3;
|
|
132
|
+
|
|
133
|
+
this.status = JobStatus.ACTIVE;
|
|
134
|
+
this.nextRun = getNextRunTime(this.schedule);
|
|
135
|
+
this.lastRun = null;
|
|
136
|
+
this.lastResult = null;
|
|
137
|
+
this.lastError = null;
|
|
138
|
+
this.runCount = 0;
|
|
139
|
+
this.failCount = 0;
|
|
140
|
+
this.consecutiveFailures = 0;
|
|
141
|
+
this.createdAt = new Date();
|
|
142
|
+
|
|
143
|
+
// History of recent executions
|
|
144
|
+
this.history = [];
|
|
145
|
+
this.maxHistory = 20;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Cron Scheduler - Manages and executes scheduled jobs
|
|
151
|
+
*/
|
|
152
|
+
export class CronScheduler {
|
|
153
|
+
/**
|
|
154
|
+
* @param {object} options
|
|
155
|
+
* @param {number} options.tickInterval - How often to check for jobs to run (ms)
|
|
156
|
+
* @param {Function} options.executor - async (agentId, taskPrompt, jobId) => result
|
|
157
|
+
* @param {Function} options.onJobRun - Callback when a job starts running
|
|
158
|
+
* @param {Function} options.onJobComplete - Callback when a job completes
|
|
159
|
+
* @param {Function} options.onJobError - Callback when a job fails
|
|
160
|
+
*/
|
|
161
|
+
constructor(options = {}) {
|
|
162
|
+
this.tickInterval = options.tickInterval ?? 60000; // Check every minute
|
|
163
|
+
this.executor = options.executor || null;
|
|
164
|
+
this.onJobRun = options.onJobRun || null;
|
|
165
|
+
this.onJobComplete = options.onJobComplete || null;
|
|
166
|
+
this.onJobError = options.onJobError || null;
|
|
167
|
+
|
|
168
|
+
this.jobs = new Map(); // id -> CronJob
|
|
169
|
+
this.timer = null;
|
|
170
|
+
this.running = false;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Register a new scheduled job
|
|
175
|
+
* @param {object} config - Job configuration
|
|
176
|
+
* @returns {CronJob}
|
|
177
|
+
*/
|
|
178
|
+
addJob(config) {
|
|
179
|
+
const job = new CronJob(config);
|
|
180
|
+
this.jobs.set(job.id, job);
|
|
181
|
+
console.log(`⏰ Cron job registered: "${job.name}" [${job.cronExpression}]`);
|
|
182
|
+
return job;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Remove a job
|
|
187
|
+
* @param {string} jobId
|
|
188
|
+
*/
|
|
189
|
+
removeJob(jobId) {
|
|
190
|
+
this.jobs.delete(jobId);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Pause a job
|
|
195
|
+
* @param {string} jobId
|
|
196
|
+
*/
|
|
197
|
+
pauseJob(jobId) {
|
|
198
|
+
const job = this.jobs.get(jobId);
|
|
199
|
+
if (job) job.status = JobStatus.PAUSED;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Resume a paused job
|
|
204
|
+
* @param {string} jobId
|
|
205
|
+
*/
|
|
206
|
+
resumeJob(jobId) {
|
|
207
|
+
const job = this.jobs.get(jobId);
|
|
208
|
+
if (job && (job.status === JobStatus.PAUSED || job.status === JobStatus.ERROR)) {
|
|
209
|
+
job.status = JobStatus.ACTIVE;
|
|
210
|
+
job.consecutiveFailures = 0;
|
|
211
|
+
job.nextRun = getNextRunTime(job.schedule);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Start the scheduler
|
|
217
|
+
*/
|
|
218
|
+
start() {
|
|
219
|
+
if (this.running) return;
|
|
220
|
+
this.running = true;
|
|
221
|
+
console.log(`⏰ Cron scheduler started (tick every ${this.tickInterval / 1000}s)`);
|
|
222
|
+
this.timer = setInterval(() => this._tick(), this.tickInterval);
|
|
223
|
+
// Run an immediate tick
|
|
224
|
+
this._tick();
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Stop the scheduler
|
|
229
|
+
*/
|
|
230
|
+
stop() {
|
|
231
|
+
this.running = false;
|
|
232
|
+
if (this.timer) {
|
|
233
|
+
clearInterval(this.timer);
|
|
234
|
+
this.timer = null;
|
|
235
|
+
}
|
|
236
|
+
console.log('⏰ Cron scheduler stopped');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Check for and execute due jobs
|
|
241
|
+
*/
|
|
242
|
+
async _tick() {
|
|
243
|
+
const now = new Date();
|
|
244
|
+
|
|
245
|
+
for (const [jobId, job] of this.jobs) {
|
|
246
|
+
// Skip non-active jobs
|
|
247
|
+
if (job.status !== JobStatus.ACTIVE) continue;
|
|
248
|
+
|
|
249
|
+
// Check if job is due
|
|
250
|
+
if (job.nextRun && job.nextRun <= now) {
|
|
251
|
+
await this._executeJob(job);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Execute a single job
|
|
258
|
+
* @param {CronJob} job
|
|
259
|
+
*/
|
|
260
|
+
async _executeJob(job) {
|
|
261
|
+
if (!this.executor) {
|
|
262
|
+
console.warn(`[CronScheduler] No executor configured, skipping job: ${job.name}`);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
job.status = JobStatus.RUNNING;
|
|
267
|
+
job.lastRun = new Date();
|
|
268
|
+
job.runCount++;
|
|
269
|
+
|
|
270
|
+
// Notify: job starting
|
|
271
|
+
if (this.onJobRun) {
|
|
272
|
+
try { this.onJobRun(job); } catch {}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const historyEntry = {
|
|
276
|
+
runAt: job.lastRun.toISOString(),
|
|
277
|
+
success: false,
|
|
278
|
+
result: null,
|
|
279
|
+
error: null,
|
|
280
|
+
duration: 0,
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
const startTime = Date.now();
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
const result = await this.executor(job.agentId, job.taskPrompt, job.id);
|
|
287
|
+
const duration = Date.now() - startTime;
|
|
288
|
+
|
|
289
|
+
job.lastResult = result;
|
|
290
|
+
job.lastError = null;
|
|
291
|
+
job.consecutiveFailures = 0;
|
|
292
|
+
|
|
293
|
+
historyEntry.success = true;
|
|
294
|
+
historyEntry.result = typeof result === 'string' ? result.slice(0, 200) : 'OK';
|
|
295
|
+
historyEntry.duration = duration;
|
|
296
|
+
|
|
297
|
+
// Handle one-shot jobs
|
|
298
|
+
if (job.oneShot) {
|
|
299
|
+
job.status = JobStatus.COMPLETED;
|
|
300
|
+
} else {
|
|
301
|
+
job.status = JobStatus.ACTIVE;
|
|
302
|
+
job.nextRun = getNextRunTime(job.schedule);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Notify: job completed
|
|
306
|
+
if (this.onJobComplete) {
|
|
307
|
+
try { this.onJobComplete(job, result); } catch {}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
} catch (error) {
|
|
311
|
+
const duration = Date.now() - startTime;
|
|
312
|
+
|
|
313
|
+
job.lastError = error.message;
|
|
314
|
+
job.failCount++;
|
|
315
|
+
job.consecutiveFailures++;
|
|
316
|
+
|
|
317
|
+
historyEntry.error = error.message;
|
|
318
|
+
historyEntry.duration = duration;
|
|
319
|
+
|
|
320
|
+
// Check if we should stop the job
|
|
321
|
+
if (job.consecutiveFailures >= job.maxConsecutiveFailures) {
|
|
322
|
+
job.status = JobStatus.ERROR;
|
|
323
|
+
console.error(`⏰ Cron job "${job.name}" stopped after ${job.consecutiveFailures} consecutive failures`);
|
|
324
|
+
} else {
|
|
325
|
+
job.status = JobStatus.ACTIVE;
|
|
326
|
+
job.nextRun = getNextRunTime(job.schedule);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Notify: job error
|
|
330
|
+
if (this.onJobError) {
|
|
331
|
+
try { this.onJobError(job, error); } catch {}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Add to history
|
|
336
|
+
job.history.push(historyEntry);
|
|
337
|
+
if (job.history.length > job.maxHistory) {
|
|
338
|
+
job.history.shift();
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Manually trigger a job (ignoring schedule)
|
|
344
|
+
* @param {string} jobId
|
|
345
|
+
* @returns {Promise}
|
|
346
|
+
*/
|
|
347
|
+
async triggerJob(jobId) {
|
|
348
|
+
const job = this.jobs.get(jobId);
|
|
349
|
+
if (!job) throw new Error(`Job not found: ${jobId}`);
|
|
350
|
+
return this._executeJob(job);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* List all jobs with their status
|
|
355
|
+
* @returns {Array}
|
|
356
|
+
*/
|
|
357
|
+
listJobs() {
|
|
358
|
+
return [...this.jobs.values()].map(job => ({
|
|
359
|
+
id: job.id,
|
|
360
|
+
name: job.name,
|
|
361
|
+
description: job.description,
|
|
362
|
+
cronExpression: job.cronExpression,
|
|
363
|
+
agentId: job.agentId,
|
|
364
|
+
status: job.status,
|
|
365
|
+
nextRun: job.nextRun?.toISOString() || null,
|
|
366
|
+
lastRun: job.lastRun?.toISOString() || null,
|
|
367
|
+
lastError: job.lastError,
|
|
368
|
+
runCount: job.runCount,
|
|
369
|
+
failCount: job.failCount,
|
|
370
|
+
consecutiveFailures: job.consecutiveFailures,
|
|
371
|
+
historyLength: job.history.length,
|
|
372
|
+
}));
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Get execution history for a specific job
|
|
377
|
+
* @param {string} jobId
|
|
378
|
+
* @returns {Array}
|
|
379
|
+
*/
|
|
380
|
+
getJobHistory(jobId) {
|
|
381
|
+
const job = this.jobs.get(jobId);
|
|
382
|
+
if (!job) return [];
|
|
383
|
+
return [...job.history].reverse();
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Get scheduler summary
|
|
388
|
+
* @returns {object}
|
|
389
|
+
*/
|
|
390
|
+
getSummary() {
|
|
391
|
+
const jobs = [...this.jobs.values()];
|
|
392
|
+
return {
|
|
393
|
+
running: this.running,
|
|
394
|
+
totalJobs: jobs.length,
|
|
395
|
+
activeJobs: jobs.filter(j => j.status === JobStatus.ACTIVE).length,
|
|
396
|
+
pausedJobs: jobs.filter(j => j.status === JobStatus.PAUSED).length,
|
|
397
|
+
errorJobs: jobs.filter(j => j.status === JobStatus.ERROR).length,
|
|
398
|
+
totalRuns: jobs.reduce((sum, j) => sum + j.runCount, 0),
|
|
399
|
+
totalFailures: jobs.reduce((sum, j) => sum + j.failCount, 0),
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Serialize scheduler state (for persistence)
|
|
405
|
+
* @returns {object}
|
|
406
|
+
*/
|
|
407
|
+
serialize() {
|
|
408
|
+
const jobs = [];
|
|
409
|
+
for (const [id, job] of this.jobs) {
|
|
410
|
+
jobs.push({
|
|
411
|
+
id: job.id,
|
|
412
|
+
name: job.name,
|
|
413
|
+
description: job.description,
|
|
414
|
+
cronExpression: job.cronExpression,
|
|
415
|
+
agentId: job.agentId,
|
|
416
|
+
taskPrompt: job.taskPrompt,
|
|
417
|
+
oneShot: job.oneShot,
|
|
418
|
+
maxConsecutiveFailures: job.maxConsecutiveFailures,
|
|
419
|
+
status: job.status,
|
|
420
|
+
runCount: job.runCount,
|
|
421
|
+
failCount: job.failCount,
|
|
422
|
+
createdAt: job.createdAt.toISOString(),
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
return { jobs };
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Restore scheduler state from serialized data
|
|
430
|
+
* @param {object} data
|
|
431
|
+
*/
|
|
432
|
+
restore(data) {
|
|
433
|
+
if (!data || !data.jobs) return;
|
|
434
|
+
for (const jobData of data.jobs) {
|
|
435
|
+
try {
|
|
436
|
+
const job = this.addJob(jobData);
|
|
437
|
+
if (jobData.status === JobStatus.PAUSED) job.status = JobStatus.PAUSED;
|
|
438
|
+
if (jobData.status === JobStatus.ERROR) job.status = JobStatus.ERROR;
|
|
439
|
+
job.runCount = jobData.runCount || 0;
|
|
440
|
+
job.failCount = jobData.failCount || 0;
|
|
441
|
+
} catch (err) {
|
|
442
|
+
console.error(`[CronScheduler] Failed to restore job "${jobData.name}":`, err.message);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Global singleton
|
|
449
|
+
export const cronScheduler = new CronScheduler();
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System Module - Infrastructure services
|
|
3
|
+
* Contains Cron scheduling, Plugin system, Security audit
|
|
4
|
+
*/
|
|
5
|
+
export { AuditLogger, SecurityGuard, auditLogger, securityGuard, AuditLevel, AuditCategory } from './audit.js';
|
|
6
|
+
export { PluginRegistry, pluginRegistry, PluginManifest, HookPoint, PluginState, initPluginRuntime } from './plugin.js';
|
|
7
|
+
export { CronScheduler, cronScheduler, JobStatus, parseCronExpression } from './cron.js';
|