cipher-security 5.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/bin/cipher.js +465 -0
- package/lib/api/billing.js +321 -0
- package/lib/api/compliance.js +693 -0
- package/lib/api/controls.js +1401 -0
- package/lib/api/index.js +49 -0
- package/lib/api/marketplace.js +467 -0
- package/lib/api/openai-proxy.js +383 -0
- package/lib/api/server.js +685 -0
- package/lib/autonomous/feedback-loop.js +554 -0
- package/lib/autonomous/framework.js +512 -0
- package/lib/autonomous/index.js +97 -0
- package/lib/autonomous/leaderboard.js +594 -0
- package/lib/autonomous/modes/architect.js +412 -0
- package/lib/autonomous/modes/blue.js +386 -0
- package/lib/autonomous/modes/incident.js +684 -0
- package/lib/autonomous/modes/privacy.js +369 -0
- package/lib/autonomous/modes/purple.js +294 -0
- package/lib/autonomous/modes/recon.js +250 -0
- package/lib/autonomous/parallel.js +587 -0
- package/lib/autonomous/researcher.js +583 -0
- package/lib/autonomous/runner.js +955 -0
- package/lib/autonomous/scheduler.js +615 -0
- package/lib/autonomous/task-parser.js +127 -0
- package/lib/autonomous/validators/forensic.js +266 -0
- package/lib/autonomous/validators/osint.js +216 -0
- package/lib/autonomous/validators/privacy.js +296 -0
- package/lib/autonomous/validators/purple.js +298 -0
- package/lib/autonomous/validators/sigma.js +248 -0
- package/lib/autonomous/validators/threat-model.js +363 -0
- package/lib/benchmark/agent.js +119 -0
- package/lib/benchmark/baselines.js +43 -0
- package/lib/benchmark/builder.js +143 -0
- package/lib/benchmark/config.js +35 -0
- package/lib/benchmark/coordinator.js +91 -0
- package/lib/benchmark/index.js +20 -0
- package/lib/benchmark/llm.js +58 -0
- package/lib/benchmark/models.js +137 -0
- package/lib/benchmark/reporter.js +103 -0
- package/lib/benchmark/runner.js +103 -0
- package/lib/benchmark/sandbox.js +96 -0
- package/lib/benchmark/scorer.js +32 -0
- package/lib/benchmark/solver.js +166 -0
- package/lib/benchmark/tools.js +62 -0
- package/lib/bot/bot.js +130 -0
- package/lib/commands.js +99 -0
- package/lib/complexity.js +377 -0
- package/lib/config.js +213 -0
- package/lib/gateway/client.js +309 -0
- package/lib/gateway/commands.js +830 -0
- package/lib/gateway/config-validate.js +109 -0
- package/lib/gateway/gateway.js +367 -0
- package/lib/gateway/index.js +62 -0
- package/lib/gateway/mode.js +309 -0
- package/lib/gateway/plugins.js +222 -0
- package/lib/gateway/prompt.js +214 -0
- package/lib/mcp/server.js +262 -0
- package/lib/memory/compressor.js +425 -0
- package/lib/memory/engine.js +763 -0
- package/lib/memory/evolution.js +668 -0
- package/lib/memory/index.js +58 -0
- package/lib/memory/orchestrator.js +506 -0
- package/lib/memory/retriever.js +515 -0
- package/lib/memory/synthesizer.js +333 -0
- package/lib/pipeline/async-scanner.js +510 -0
- package/lib/pipeline/binary-analysis.js +1043 -0
- package/lib/pipeline/dom-xss-scanner.js +435 -0
- package/lib/pipeline/github-actions.js +792 -0
- package/lib/pipeline/index.js +124 -0
- package/lib/pipeline/osint.js +498 -0
- package/lib/pipeline/sarif.js +373 -0
- package/lib/pipeline/scanner.js +880 -0
- package/lib/pipeline/template-manager.js +525 -0
- package/lib/pipeline/xss-scanner.js +353 -0
- package/lib/setup-wizard.js +229 -0
- package/package.json +30 -0
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
// Copyright (c) 2026 defconxt. All rights reserved.
|
|
2
|
+
// Licensed under AGPL-3.0 — see LICENSE file for details.
|
|
3
|
+
// CIPHER is a trademark of defconxt.
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* CIPHER Autonomous Scheduler — Idle-aware background task execution.
|
|
7
|
+
*
|
|
8
|
+
* Detects system idle state via platform-specific APIs, then runs prioritized
|
|
9
|
+
* background tasks during idle windows.
|
|
10
|
+
*
|
|
11
|
+
* State machine:
|
|
12
|
+
* IDLE_WAIT → WINDOW_OPEN → RUNNING → PAUSING → IDLE_WAIT
|
|
13
|
+
*
|
|
14
|
+
* @module autonomous/scheduler
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { execSync } from 'node:child_process';
|
|
18
|
+
import { randomUUID } from 'node:crypto';
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Task Priority
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
/** @type {Readonly<{CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3, BACKGROUND: 4}>} */
|
|
25
|
+
export const TaskPriority = Object.freeze({
|
|
26
|
+
CRITICAL: 0,
|
|
27
|
+
HIGH: 1,
|
|
28
|
+
MEDIUM: 2,
|
|
29
|
+
LOW: 3,
|
|
30
|
+
BACKGROUND: 4,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Scheduler State
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
/** @type {Readonly<{IDLE_WAIT: 'idle_wait', WINDOW_OPEN: 'window_open', RUNNING: 'running', PAUSING: 'pausing'}>} */
|
|
38
|
+
export const SchedulerState = Object.freeze({
|
|
39
|
+
IDLE_WAIT: 'idle_wait',
|
|
40
|
+
WINDOW_OPEN: 'window_open',
|
|
41
|
+
RUNNING: 'running',
|
|
42
|
+
PAUSING: 'pausing',
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// ScheduledTask
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
export class ScheduledTask {
|
|
50
|
+
/**
|
|
51
|
+
* @param {object} opts
|
|
52
|
+
* @param {string} opts.name
|
|
53
|
+
* @param {string} opts.taskType
|
|
54
|
+
* @param {number} opts.priority - TaskPriority value
|
|
55
|
+
* @param {Function} opts.callableFn
|
|
56
|
+
* @param {Array} [opts.args]
|
|
57
|
+
* @param {object} [opts.kwargs]
|
|
58
|
+
* @param {string} [opts.taskId]
|
|
59
|
+
* @param {number} [opts.intervalSeconds]
|
|
60
|
+
* @param {number} [opts.lastRun]
|
|
61
|
+
* @param {number} [opts.nextRun]
|
|
62
|
+
* @param {number} [opts.runCount]
|
|
63
|
+
* @param {number} [opts.maxRuns]
|
|
64
|
+
* @param {boolean} [opts.enabled]
|
|
65
|
+
* @param {boolean} [opts.requiresIdle]
|
|
66
|
+
* @param {string} [opts.createdAt]
|
|
67
|
+
*/
|
|
68
|
+
constructor({
|
|
69
|
+
name,
|
|
70
|
+
taskType,
|
|
71
|
+
priority,
|
|
72
|
+
callableFn,
|
|
73
|
+
args = [],
|
|
74
|
+
kwargs = {},
|
|
75
|
+
taskId = randomUUID().replace(/-/g, ''),
|
|
76
|
+
intervalSeconds = 0,
|
|
77
|
+
lastRun = 0,
|
|
78
|
+
nextRun = 0,
|
|
79
|
+
runCount = 0,
|
|
80
|
+
maxRuns = 0,
|
|
81
|
+
enabled = true,
|
|
82
|
+
requiresIdle = true,
|
|
83
|
+
createdAt = new Date().toISOString(),
|
|
84
|
+
}) {
|
|
85
|
+
this.name = name;
|
|
86
|
+
this.taskType = taskType;
|
|
87
|
+
this.priority = priority;
|
|
88
|
+
this.callableFn = callableFn;
|
|
89
|
+
this.args = args;
|
|
90
|
+
this.kwargs = kwargs;
|
|
91
|
+
this.taskId = taskId;
|
|
92
|
+
this.intervalSeconds = intervalSeconds;
|
|
93
|
+
this.lastRun = lastRun;
|
|
94
|
+
this.nextRun = nextRun;
|
|
95
|
+
this.runCount = runCount;
|
|
96
|
+
this.maxRuns = maxRuns;
|
|
97
|
+
this.enabled = enabled;
|
|
98
|
+
this.requiresIdle = requiresIdle;
|
|
99
|
+
this.createdAt = createdAt;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Return true if the task should run at the given epoch time.
|
|
104
|
+
* @param {number} [now]
|
|
105
|
+
* @returns {boolean}
|
|
106
|
+
*/
|
|
107
|
+
isDue(now) {
|
|
108
|
+
if (!this.enabled) return false;
|
|
109
|
+
if (this.maxRuns > 0 && this.runCount >= this.maxRuns) return false;
|
|
110
|
+
now = now ?? Date.now() / 1000;
|
|
111
|
+
return now >= this.nextRun;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Mark the task as having just executed.
|
|
116
|
+
* @param {number} [now]
|
|
117
|
+
*/
|
|
118
|
+
recordRun(now) {
|
|
119
|
+
now = now ?? Date.now() / 1000;
|
|
120
|
+
this.lastRun = now;
|
|
121
|
+
this.runCount += 1;
|
|
122
|
+
if (this.intervalSeconds > 0) {
|
|
123
|
+
this.nextRun = now + this.intervalSeconds;
|
|
124
|
+
} else {
|
|
125
|
+
// One-shot — disable after run
|
|
126
|
+
this.enabled = false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
// TaskResult
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
|
|
135
|
+
export class TaskResult {
|
|
136
|
+
/**
|
|
137
|
+
* @param {object} opts
|
|
138
|
+
* @param {string} opts.taskId
|
|
139
|
+
* @param {string} opts.taskName
|
|
140
|
+
* @param {number} opts.startedAt
|
|
141
|
+
* @param {number} opts.finishedAt
|
|
142
|
+
* @param {boolean} opts.success
|
|
143
|
+
* @param {string|null} [opts.error]
|
|
144
|
+
* @param {*} [opts.result]
|
|
145
|
+
*/
|
|
146
|
+
constructor({ taskId, taskName, startedAt, finishedAt, success, error = null, result = null }) {
|
|
147
|
+
this.taskId = taskId;
|
|
148
|
+
this.taskName = taskName;
|
|
149
|
+
this.startedAt = startedAt;
|
|
150
|
+
this.finishedAt = finishedAt;
|
|
151
|
+
this.success = success;
|
|
152
|
+
this.error = error;
|
|
153
|
+
this.result = result;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
get durationMs() {
|
|
157
|
+
return (this.finishedAt - this.startedAt) * 1000;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
// LastRequestTracker
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
export class LastRequestTracker {
|
|
166
|
+
constructor() {
|
|
167
|
+
this._lastTouch = Date.now() / 1000;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Record an interaction right now. */
|
|
171
|
+
touch() {
|
|
172
|
+
this._lastTouch = Date.now() / 1000;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Seconds elapsed since the last recorded interaction. */
|
|
176
|
+
secondsSinceLast() {
|
|
177
|
+
return Date.now() / 1000 - this._lastTouch;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ---------------------------------------------------------------------------
|
|
182
|
+
// IdleDetector
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
|
|
185
|
+
export class IdleDetector {
|
|
186
|
+
/**
|
|
187
|
+
* @param {LastRequestTracker} [fallbackTracker]
|
|
188
|
+
*/
|
|
189
|
+
constructor(fallbackTracker) {
|
|
190
|
+
this._platform = process.platform; // 'darwin', 'linux', 'win32'
|
|
191
|
+
this._tracker = fallbackTracker || new LastRequestTracker();
|
|
192
|
+
this._method = this._detectMethod();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Return estimated seconds the system has been idle. */
|
|
196
|
+
idleSeconds() {
|
|
197
|
+
if (this._method === 'ioreg') return this._idleMacos();
|
|
198
|
+
if (this._method === 'xprintidle') return this._idleLinux();
|
|
199
|
+
return this._idleFallback();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Return true if the system has been idle for at least thresholdMinutes.
|
|
204
|
+
* @param {number} [thresholdMinutes=15]
|
|
205
|
+
*/
|
|
206
|
+
isIdle(thresholdMinutes = 15) {
|
|
207
|
+
return this.idleSeconds() >= thresholdMinutes * 60;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Expose the underlying request tracker. */
|
|
211
|
+
get tracker() {
|
|
212
|
+
return this._tracker;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** @returns {string} */
|
|
216
|
+
_detectMethod() {
|
|
217
|
+
if (this._platform === 'darwin') {
|
|
218
|
+
try {
|
|
219
|
+
execSync('ioreg -c IOHIDSystem', { timeout: 2000, stdio: 'pipe' });
|
|
220
|
+
return 'ioreg';
|
|
221
|
+
} catch { /* fall through */ }
|
|
222
|
+
} else if (this._platform === 'linux') {
|
|
223
|
+
try {
|
|
224
|
+
execSync('xprintidle', { timeout: 2000, stdio: 'pipe' });
|
|
225
|
+
return 'xprintidle';
|
|
226
|
+
} catch { /* fall through */ }
|
|
227
|
+
}
|
|
228
|
+
return 'fallback';
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** macOS: read HIDIdleTime from IOKit registry (nanoseconds). */
|
|
232
|
+
_idleMacos() {
|
|
233
|
+
try {
|
|
234
|
+
const result = execSync('ioreg -c IOHIDSystem -d 4', {
|
|
235
|
+
timeout: 5000,
|
|
236
|
+
encoding: 'utf-8',
|
|
237
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
238
|
+
});
|
|
239
|
+
for (const line of result.split('\n')) {
|
|
240
|
+
if (line.includes('HIDIdleTime')) {
|
|
241
|
+
const parts = line.split('=');
|
|
242
|
+
if (parts.length === 2) {
|
|
243
|
+
const ns = parseInt(parts[1].trim(), 10);
|
|
244
|
+
if (!isNaN(ns)) return Math.floor(ns / 1_000_000_000);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
} catch { /* fall through */ }
|
|
249
|
+
return this._idleFallback();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** Linux: use xprintidle (milliseconds). */
|
|
253
|
+
_idleLinux() {
|
|
254
|
+
try {
|
|
255
|
+
const result = execSync('xprintidle', {
|
|
256
|
+
timeout: 5000,
|
|
257
|
+
encoding: 'utf-8',
|
|
258
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
259
|
+
});
|
|
260
|
+
const ms = parseInt(result.trim(), 10);
|
|
261
|
+
if (!isNaN(ms)) return Math.floor(ms / 1000);
|
|
262
|
+
} catch { /* fall through */ }
|
|
263
|
+
return this._idleFallback();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Fallback: use LastRequestTracker as idle proxy. */
|
|
267
|
+
_idleFallback() {
|
|
268
|
+
return Math.floor(this._tracker.secondsSinceLast());
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// ---------------------------------------------------------------------------
|
|
273
|
+
// CipherScheduler
|
|
274
|
+
// ---------------------------------------------------------------------------
|
|
275
|
+
|
|
276
|
+
export class CipherScheduler {
|
|
277
|
+
static TICK_INTERVAL = 30.0; // seconds
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* @param {object} [opts]
|
|
281
|
+
* @param {number} [opts.idleThresholdMinutes=15]
|
|
282
|
+
* @param {string} [opts.sleepStart='23:00']
|
|
283
|
+
* @param {string} [opts.sleepEnd='07:00']
|
|
284
|
+
*/
|
|
285
|
+
constructor({
|
|
286
|
+
idleThresholdMinutes = 15,
|
|
287
|
+
sleepStart = '23:00',
|
|
288
|
+
sleepEnd = '07:00',
|
|
289
|
+
} = {}) {
|
|
290
|
+
this._idleThreshold = idleThresholdMinutes;
|
|
291
|
+
this._sleepStart = CipherScheduler._parseTime(sleepStart);
|
|
292
|
+
this._sleepEnd = CipherScheduler._parseTime(sleepEnd);
|
|
293
|
+
this._idleDetector = new IdleDetector();
|
|
294
|
+
this._state = SchedulerState.IDLE_WAIT;
|
|
295
|
+
/** @type {Map<string, ScheduledTask>} */
|
|
296
|
+
this._tasks = new Map();
|
|
297
|
+
/** @type {TaskResult[]} */
|
|
298
|
+
this._history = [];
|
|
299
|
+
this._maxHistory = 200;
|
|
300
|
+
this._running = false;
|
|
301
|
+
this._stopRequested = false;
|
|
302
|
+
this._tickTimer = null;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// -- task management ---------------------------------------------------
|
|
306
|
+
|
|
307
|
+
/** @param {ScheduledTask} task */
|
|
308
|
+
addTask(task) {
|
|
309
|
+
this._tasks.set(task.taskId, task);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Remove a task by ID. Returns true if found and removed.
|
|
314
|
+
* @param {string} taskId
|
|
315
|
+
* @returns {boolean}
|
|
316
|
+
*/
|
|
317
|
+
removeTask(taskId) {
|
|
318
|
+
return this._tasks.delete(taskId);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Retrieve a task by ID.
|
|
323
|
+
* @param {string} taskId
|
|
324
|
+
* @returns {ScheduledTask|undefined}
|
|
325
|
+
*/
|
|
326
|
+
getTask(taskId) {
|
|
327
|
+
return this._tasks.get(taskId);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// -- idle window detection ---------------------------------------------
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* @param {string} t - 'HH:MM'
|
|
334
|
+
* @returns {[number, number]}
|
|
335
|
+
*/
|
|
336
|
+
static _parseTime(t) {
|
|
337
|
+
const parts = t.trim().split(':');
|
|
338
|
+
return [parseInt(parts[0], 10), parseInt(parts[1], 10)];
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/** Return true if current local time is within the sleep window. */
|
|
342
|
+
_inSleepWindow() {
|
|
343
|
+
const now = new Date();
|
|
344
|
+
const currentMinutes = now.getHours() * 60 + now.getMinutes();
|
|
345
|
+
const startMinutes = this._sleepStart[0] * 60 + this._sleepStart[1];
|
|
346
|
+
const endMinutes = this._sleepEnd[0] * 60 + this._sleepEnd[1];
|
|
347
|
+
|
|
348
|
+
if (startMinutes <= endMinutes) {
|
|
349
|
+
return currentMinutes >= startMinutes && currentMinutes < endMinutes;
|
|
350
|
+
}
|
|
351
|
+
// Overnight window
|
|
352
|
+
return currentMinutes >= startMinutes || currentMinutes < endMinutes;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** Return true if conditions are right for background work. */
|
|
356
|
+
_isIdleWindow() {
|
|
357
|
+
if (this._inSleepWindow()) return true;
|
|
358
|
+
if (this._idleDetector.isIdle(this._idleThreshold)) {
|
|
359
|
+
const trackerIdle = this._idleDetector.tracker.secondsSinceLast();
|
|
360
|
+
return trackerIdle >= this._idleThreshold * 60;
|
|
361
|
+
}
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// -- state machine -----------------------------------------------------
|
|
366
|
+
|
|
367
|
+
/** Single iteration of the scheduler state machine. */
|
|
368
|
+
_tick() {
|
|
369
|
+
const idleNow = this._isIdleWindow();
|
|
370
|
+
|
|
371
|
+
if (this._state === SchedulerState.IDLE_WAIT) {
|
|
372
|
+
if (idleNow) {
|
|
373
|
+
this._state = SchedulerState.WINDOW_OPEN;
|
|
374
|
+
}
|
|
375
|
+
} else if (this._state === SchedulerState.WINDOW_OPEN) {
|
|
376
|
+
this._state = SchedulerState.RUNNING;
|
|
377
|
+
this._executeDueTasks();
|
|
378
|
+
if (!idleNow) {
|
|
379
|
+
this._state = SchedulerState.PAUSING;
|
|
380
|
+
} else if (!this._hasDueTasks()) {
|
|
381
|
+
this._state = SchedulerState.IDLE_WAIT;
|
|
382
|
+
}
|
|
383
|
+
} else if (this._state === SchedulerState.RUNNING) {
|
|
384
|
+
if (!idleNow) {
|
|
385
|
+
this._state = SchedulerState.PAUSING;
|
|
386
|
+
} else if (this._hasDueTasks()) {
|
|
387
|
+
this._executeDueTasks();
|
|
388
|
+
} else {
|
|
389
|
+
this._state = SchedulerState.IDLE_WAIT;
|
|
390
|
+
}
|
|
391
|
+
} else if (this._state === SchedulerState.PAUSING) {
|
|
392
|
+
this._state = SchedulerState.IDLE_WAIT;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// -- task execution ----------------------------------------------------
|
|
397
|
+
|
|
398
|
+
_hasDueTasks() {
|
|
399
|
+
const now = Date.now() / 1000;
|
|
400
|
+
for (const t of this._tasks.values()) {
|
|
401
|
+
if (t.isDue(now)) return true;
|
|
402
|
+
}
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/** @returns {ScheduledTask[]} */
|
|
407
|
+
_getDueTasksSorted() {
|
|
408
|
+
const now = Date.now() / 1000;
|
|
409
|
+
const due = [];
|
|
410
|
+
for (const t of this._tasks.values()) {
|
|
411
|
+
if (t.isDue(now)) due.push(t);
|
|
412
|
+
}
|
|
413
|
+
due.sort((a, b) => a.priority - b.priority || a.nextRun - b.nextRun);
|
|
414
|
+
return due;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
_executeDueTasks() {
|
|
418
|
+
const dueTasks = this._getDueTasksSorted();
|
|
419
|
+
for (const task of dueTasks) {
|
|
420
|
+
if (this._stopRequested) break;
|
|
421
|
+
if (task.requiresIdle && !this._isIdleWindow()) break;
|
|
422
|
+
this._runSingleTask(task);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* @param {ScheduledTask} task
|
|
428
|
+
* @returns {TaskResult}
|
|
429
|
+
*/
|
|
430
|
+
_runSingleTask(task) {
|
|
431
|
+
const started = Date.now() / 1000;
|
|
432
|
+
let result;
|
|
433
|
+
|
|
434
|
+
try {
|
|
435
|
+
const resultValue = task.callableFn(...task.args);
|
|
436
|
+
const finished = Date.now() / 1000;
|
|
437
|
+
task.recordRun(finished);
|
|
438
|
+
result = new TaskResult({
|
|
439
|
+
taskId: task.taskId,
|
|
440
|
+
taskName: task.name,
|
|
441
|
+
startedAt: started,
|
|
442
|
+
finishedAt: finished,
|
|
443
|
+
success: true,
|
|
444
|
+
result: resultValue,
|
|
445
|
+
});
|
|
446
|
+
} catch (exc) {
|
|
447
|
+
const finished = Date.now() / 1000;
|
|
448
|
+
task.recordRun(finished);
|
|
449
|
+
result = new TaskResult({
|
|
450
|
+
taskId: task.taskId,
|
|
451
|
+
taskName: task.name,
|
|
452
|
+
startedAt: started,
|
|
453
|
+
finishedAt: finished,
|
|
454
|
+
success: false,
|
|
455
|
+
error: `${exc.constructor.name}: ${exc.message}`,
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
this._recordHistory(result);
|
|
460
|
+
return result;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/** @param {TaskResult} result */
|
|
464
|
+
_recordHistory(result) {
|
|
465
|
+
this._history.push(result);
|
|
466
|
+
if (this._history.length > this._maxHistory) {
|
|
467
|
+
this._history = this._history.slice(-this._maxHistory);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// -- main loop ---------------------------------------------------------
|
|
472
|
+
|
|
473
|
+
/** Start the scheduler tick loop using setInterval. */
|
|
474
|
+
start() {
|
|
475
|
+
if (this._running) return;
|
|
476
|
+
this._running = true;
|
|
477
|
+
this._stopRequested = false;
|
|
478
|
+
this._tickTimer = setInterval(() => this._tick(), CipherScheduler.TICK_INTERVAL * 1000);
|
|
479
|
+
// Unref so it doesn't keep the process alive
|
|
480
|
+
if (this._tickTimer.unref) this._tickTimer.unref();
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/** Stop the scheduler. */
|
|
484
|
+
stop() {
|
|
485
|
+
this._stopRequested = true;
|
|
486
|
+
if (this._tickTimer) {
|
|
487
|
+
clearInterval(this._tickTimer);
|
|
488
|
+
this._tickTimer = null;
|
|
489
|
+
}
|
|
490
|
+
this._running = false;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// -- status & introspection --------------------------------------------
|
|
494
|
+
|
|
495
|
+
getStatus() {
|
|
496
|
+
const now = Date.now() / 1000;
|
|
497
|
+
const taskSummaries = [];
|
|
498
|
+
for (const t of this._tasks.values()) {
|
|
499
|
+
taskSummaries.push({
|
|
500
|
+
taskId: t.taskId,
|
|
501
|
+
name: t.name,
|
|
502
|
+
type: t.taskType,
|
|
503
|
+
priority: t.priority,
|
|
504
|
+
enabled: t.enabled,
|
|
505
|
+
runCount: t.runCount,
|
|
506
|
+
isDue: t.isDue(now),
|
|
507
|
+
nextRunIn: t.nextRun > 0 ? Math.max(0, t.nextRun - now) : 0,
|
|
508
|
+
requiresIdle: t.requiresIdle,
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
return {
|
|
512
|
+
state: this._state,
|
|
513
|
+
running: this._running,
|
|
514
|
+
idleThresholdMinutes: this._idleThreshold,
|
|
515
|
+
sleepWindow: `${String(this._sleepStart[0]).padStart(2, '0')}:${String(this._sleepStart[1]).padStart(2, '0')}→${String(this._sleepEnd[0]).padStart(2, '0')}:${String(this._sleepEnd[1]).padStart(2, '0')}`,
|
|
516
|
+
isIdleWindow: this._isIdleWindow(),
|
|
517
|
+
systemIdleSeconds: this._idleDetector.idleSeconds(),
|
|
518
|
+
tasksTotal: taskSummaries.length,
|
|
519
|
+
tasksDue: taskSummaries.filter(t => t.isDue).length,
|
|
520
|
+
tasks: taskSummaries,
|
|
521
|
+
historySize: this._history.length,
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Return recent task execution results.
|
|
527
|
+
* @param {number} [limit=50]
|
|
528
|
+
*/
|
|
529
|
+
getTaskHistory(limit = 50) {
|
|
530
|
+
const recent = this._history.slice(-limit);
|
|
531
|
+
return recent.map(r => ({
|
|
532
|
+
taskId: r.taskId,
|
|
533
|
+
taskName: r.taskName,
|
|
534
|
+
startedAt: new Date(r.startedAt * 1000).toISOString(),
|
|
535
|
+
durationMs: Math.round(r.durationMs * 100) / 100,
|
|
536
|
+
success: r.success,
|
|
537
|
+
error: r.error,
|
|
538
|
+
}));
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
get idleDetector() {
|
|
542
|
+
return this._idleDetector;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
get state() {
|
|
546
|
+
return this._state;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Expose for testing — force state transition
|
|
550
|
+
set state(val) {
|
|
551
|
+
this._state = val;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// ---------------------------------------------------------------------------
|
|
556
|
+
// Default Tasks Factory
|
|
557
|
+
// ---------------------------------------------------------------------------
|
|
558
|
+
|
|
559
|
+
function _noopMemoryConsolidation() {
|
|
560
|
+
return 'memory_consolidation: complete';
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function _noopSkillMetricUpdate() {
|
|
564
|
+
return 'skill_metric_update: complete';
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function _noopMemoryDecay() {
|
|
568
|
+
return 'memory_decay: complete';
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function _noopStaleEngagementCleanup() {
|
|
572
|
+
return 'stale_engagement_cleanup: complete';
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Create the standard set of background self-improvement tasks.
|
|
577
|
+
* @returns {ScheduledTask[]}
|
|
578
|
+
*/
|
|
579
|
+
export function createDefaultTasks() {
|
|
580
|
+
const now = Date.now() / 1000;
|
|
581
|
+
return [
|
|
582
|
+
new ScheduledTask({
|
|
583
|
+
name: 'memory_consolidation',
|
|
584
|
+
taskType: 'memory_consolidation',
|
|
585
|
+
priority: TaskPriority.MEDIUM,
|
|
586
|
+
callableFn: _noopMemoryConsolidation,
|
|
587
|
+
intervalSeconds: 3600,
|
|
588
|
+
nextRun: now + 3600,
|
|
589
|
+
}),
|
|
590
|
+
new ScheduledTask({
|
|
591
|
+
name: 'skill_metric_update',
|
|
592
|
+
taskType: 'metric_update',
|
|
593
|
+
priority: TaskPriority.LOW,
|
|
594
|
+
callableFn: _noopSkillMetricUpdate,
|
|
595
|
+
intervalSeconds: 1800,
|
|
596
|
+
nextRun: now + 1800,
|
|
597
|
+
}),
|
|
598
|
+
new ScheduledTask({
|
|
599
|
+
name: 'memory_decay',
|
|
600
|
+
taskType: 'memory_consolidation',
|
|
601
|
+
priority: TaskPriority.BACKGROUND,
|
|
602
|
+
callableFn: _noopMemoryDecay,
|
|
603
|
+
intervalSeconds: 21600,
|
|
604
|
+
nextRun: now + 21600,
|
|
605
|
+
}),
|
|
606
|
+
new ScheduledTask({
|
|
607
|
+
name: 'stale_engagement_cleanup',
|
|
608
|
+
taskType: 'memory_consolidation',
|
|
609
|
+
priority: TaskPriority.BACKGROUND,
|
|
610
|
+
callableFn: _noopStaleEngagementCleanup,
|
|
611
|
+
intervalSeconds: 86400,
|
|
612
|
+
nextRun: now + 86400,
|
|
613
|
+
}),
|
|
614
|
+
];
|
|
615
|
+
}
|