capacitor-mobile-claw 1.4.1 → 1.4.2
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 +7 -4
- package/dist/esm/definitions.d.ts +152 -2
- package/dist/esm/definitions.d.ts.map +1 -1
- package/dist/esm/engine.d.ts +21 -1
- package/dist/esm/engine.d.ts.map +1 -1
- package/dist/esm/engine.js +160 -0
- package/dist/esm/engine.js.map +1 -1
- package/dist/esm/plugin.d.ts +18 -1
- package/dist/esm/plugin.d.ts.map +1 -1
- package/dist/esm/plugin.js +42 -0
- package/dist/esm/plugin.js.map +1 -1
- package/dist/esm/services/bridge-protocol.d.ts +178 -2
- package/dist/esm/services/bridge-protocol.d.ts.map +1 -1
- package/nodejs-assets/nodejs-project/main.js +213 -12
- package/nodejs-assets/nodejs-project/worker-db.js +773 -3
- package/nodejs-assets/nodejs-project/worker-heartbeat.js +764 -0
- package/package.json +3 -2
|
@@ -38,7 +38,7 @@ let _ready = false;
|
|
|
38
38
|
|
|
39
39
|
const FLUSH_INTERVAL_MS = 5000;
|
|
40
40
|
const FLUSH_MAX_RETRIES = 3;
|
|
41
|
-
const SCHEMA_VERSION =
|
|
41
|
+
const SCHEMA_VERSION = 2;
|
|
42
42
|
|
|
43
43
|
// ── Schema ──────────────────────────────────────────────────────────────
|
|
44
44
|
|
|
@@ -304,8 +304,130 @@ function _migrate() {
|
|
|
304
304
|
console.log(`[worker-db] Migrated to v1`);
|
|
305
305
|
}
|
|
306
306
|
|
|
307
|
-
|
|
308
|
-
|
|
307
|
+
if (currentVersion < 2) {
|
|
308
|
+
run(
|
|
309
|
+
`CREATE TABLE IF NOT EXISTS cron_skills (
|
|
310
|
+
id TEXT PRIMARY KEY,
|
|
311
|
+
name TEXT NOT NULL,
|
|
312
|
+
allowed_tools TEXT,
|
|
313
|
+
system_prompt TEXT,
|
|
314
|
+
model TEXT,
|
|
315
|
+
max_turns INTEGER DEFAULT 3,
|
|
316
|
+
timeout_ms INTEGER DEFAULT 60000,
|
|
317
|
+
created_at INTEGER NOT NULL,
|
|
318
|
+
updated_at INTEGER NOT NULL
|
|
319
|
+
)`
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
run(
|
|
323
|
+
`CREATE TABLE IF NOT EXISTS cron_jobs (
|
|
324
|
+
id TEXT PRIMARY KEY,
|
|
325
|
+
name TEXT NOT NULL,
|
|
326
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
327
|
+
session_target TEXT NOT NULL DEFAULT 'isolated',
|
|
328
|
+
wake_mode TEXT DEFAULT 'next-heartbeat',
|
|
329
|
+
schedule_kind TEXT NOT NULL,
|
|
330
|
+
schedule_every_ms INTEGER,
|
|
331
|
+
schedule_anchor_ms INTEGER,
|
|
332
|
+
schedule_at_ms INTEGER,
|
|
333
|
+
skill_id TEXT NOT NULL,
|
|
334
|
+
prompt TEXT NOT NULL,
|
|
335
|
+
delivery_mode TEXT NOT NULL DEFAULT 'notification',
|
|
336
|
+
delivery_webhook_url TEXT,
|
|
337
|
+
delivery_notification_title TEXT,
|
|
338
|
+
active_hours_start TEXT,
|
|
339
|
+
active_hours_end TEXT,
|
|
340
|
+
active_hours_tz TEXT,
|
|
341
|
+
last_run_at INTEGER,
|
|
342
|
+
next_run_at INTEGER,
|
|
343
|
+
last_run_status TEXT,
|
|
344
|
+
last_error TEXT,
|
|
345
|
+
last_duration_ms INTEGER,
|
|
346
|
+
last_response_hash TEXT,
|
|
347
|
+
last_response_sent_at INTEGER,
|
|
348
|
+
consecutive_errors INTEGER DEFAULT 0,
|
|
349
|
+
created_at INTEGER NOT NULL,
|
|
350
|
+
updated_at INTEGER NOT NULL
|
|
351
|
+
)`
|
|
352
|
+
);
|
|
353
|
+
run('CREATE INDEX IF NOT EXISTS idx_cron_jobs_next_run_at ON cron_jobs(enabled, next_run_at)');
|
|
354
|
+
|
|
355
|
+
run(
|
|
356
|
+
`CREATE TABLE IF NOT EXISTS cron_runs (
|
|
357
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
358
|
+
job_id TEXT NOT NULL,
|
|
359
|
+
started_at INTEGER NOT NULL,
|
|
360
|
+
ended_at INTEGER,
|
|
361
|
+
status TEXT,
|
|
362
|
+
duration_ms INTEGER,
|
|
363
|
+
error TEXT,
|
|
364
|
+
response_text TEXT,
|
|
365
|
+
was_heartbeat_ok INTEGER DEFAULT 0,
|
|
366
|
+
was_deduped INTEGER DEFAULT 0,
|
|
367
|
+
delivered INTEGER DEFAULT 0,
|
|
368
|
+
wake_source TEXT
|
|
369
|
+
)`
|
|
370
|
+
);
|
|
371
|
+
run('CREATE INDEX IF NOT EXISTS idx_cron_runs_job_started ON cron_runs(job_id, started_at DESC)');
|
|
372
|
+
|
|
373
|
+
run(
|
|
374
|
+
`CREATE TABLE IF NOT EXISTS heartbeat_config (
|
|
375
|
+
id INTEGER PRIMARY KEY DEFAULT 1,
|
|
376
|
+
enabled INTEGER NOT NULL DEFAULT 0,
|
|
377
|
+
every_ms INTEGER NOT NULL DEFAULT 1800000,
|
|
378
|
+
prompt TEXT,
|
|
379
|
+
skill_id TEXT,
|
|
380
|
+
active_hours_start TEXT,
|
|
381
|
+
active_hours_end TEXT,
|
|
382
|
+
active_hours_tz TEXT,
|
|
383
|
+
next_run_at INTEGER,
|
|
384
|
+
last_heartbeat_hash TEXT,
|
|
385
|
+
last_heartbeat_sent_at INTEGER,
|
|
386
|
+
updated_at INTEGER NOT NULL
|
|
387
|
+
)`
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
run(
|
|
391
|
+
`CREATE TABLE IF NOT EXISTS scheduler_config (
|
|
392
|
+
id INTEGER PRIMARY KEY DEFAULT 1,
|
|
393
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
394
|
+
scheduling_mode TEXT NOT NULL DEFAULT 'balanced',
|
|
395
|
+
run_on_charging INTEGER NOT NULL DEFAULT 1,
|
|
396
|
+
global_active_hours_start TEXT,
|
|
397
|
+
global_active_hours_end TEXT,
|
|
398
|
+
global_active_hours_tz TEXT,
|
|
399
|
+
updated_at INTEGER NOT NULL
|
|
400
|
+
)`
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
run(
|
|
404
|
+
`CREATE TABLE IF NOT EXISTS system_events (
|
|
405
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
406
|
+
session_key TEXT NOT NULL,
|
|
407
|
+
context_key TEXT,
|
|
408
|
+
text TEXT NOT NULL,
|
|
409
|
+
created_at INTEGER NOT NULL,
|
|
410
|
+
consumed INTEGER NOT NULL DEFAULT 0
|
|
411
|
+
)`
|
|
412
|
+
);
|
|
413
|
+
run('CREATE INDEX IF NOT EXISTS idx_system_events_pending ON system_events(session_key, consumed, created_at)');
|
|
414
|
+
|
|
415
|
+
const now = Date.now();
|
|
416
|
+
run(
|
|
417
|
+
`INSERT OR IGNORE INTO heartbeat_config
|
|
418
|
+
(id, enabled, every_ms, updated_at)
|
|
419
|
+
VALUES (1, 0, 1800000, ?)`,
|
|
420
|
+
[now]
|
|
421
|
+
);
|
|
422
|
+
run(
|
|
423
|
+
`INSERT OR IGNORE INTO scheduler_config
|
|
424
|
+
(id, enabled, scheduling_mode, run_on_charging, updated_at)
|
|
425
|
+
VALUES (1, 1, 'balanced', 1, ?)`,
|
|
426
|
+
[now]
|
|
427
|
+
);
|
|
428
|
+
run('INSERT OR REPLACE INTO schema_version (version) VALUES (?)', [2]);
|
|
429
|
+
console.log('[worker-db] Migrated to v2');
|
|
430
|
+
}
|
|
309
431
|
}
|
|
310
432
|
|
|
311
433
|
// ── Session eviction + size management ──────────────────────────────────
|
|
@@ -474,3 +596,651 @@ function _rebuildIndexFromFiles(sessionsDir) {
|
|
|
474
596
|
} catch { /* empty index */ }
|
|
475
597
|
return index;
|
|
476
598
|
}
|
|
599
|
+
|
|
600
|
+
// ── Cron/Scheduler/Heartbeat store (Phase 2) ─────────────────────────────
|
|
601
|
+
|
|
602
|
+
function _toBool(value) {
|
|
603
|
+
return Number(value) === 1;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function _toIntBool(value) {
|
|
607
|
+
return value ? 1 : 0;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function _parseJsonArray(value) {
|
|
611
|
+
if (!value) return undefined;
|
|
612
|
+
try {
|
|
613
|
+
const parsed = JSON.parse(value);
|
|
614
|
+
return Array.isArray(parsed) ? parsed : undefined;
|
|
615
|
+
} catch {
|
|
616
|
+
return undefined;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function _genId(prefix) {
|
|
621
|
+
return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function _mapActiveHours(start, end, tz) {
|
|
625
|
+
if (!start && !end && !tz) return undefined;
|
|
626
|
+
return {
|
|
627
|
+
...(start ? { start } : {}),
|
|
628
|
+
...(end ? { end } : {}),
|
|
629
|
+
...(tz ? { tz } : {}),
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function _toSchedulerConfig(row) {
|
|
634
|
+
if (!row) return null;
|
|
635
|
+
return {
|
|
636
|
+
enabled: _toBool(row.enabled),
|
|
637
|
+
schedulingMode: row.scheduling_mode || 'balanced',
|
|
638
|
+
runOnCharging: _toBool(row.run_on_charging),
|
|
639
|
+
globalActiveHours: _mapActiveHours(
|
|
640
|
+
row.global_active_hours_start,
|
|
641
|
+
row.global_active_hours_end,
|
|
642
|
+
row.global_active_hours_tz
|
|
643
|
+
),
|
|
644
|
+
updatedAt: row.updated_at,
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function _toHeartbeatConfig(row) {
|
|
649
|
+
if (!row) return null;
|
|
650
|
+
return {
|
|
651
|
+
enabled: _toBool(row.enabled),
|
|
652
|
+
everyMs: row.every_ms ?? 1800000,
|
|
653
|
+
prompt: row.prompt || undefined,
|
|
654
|
+
skillId: row.skill_id || undefined,
|
|
655
|
+
activeHours: _mapActiveHours(row.active_hours_start, row.active_hours_end, row.active_hours_tz),
|
|
656
|
+
nextRunAt: row.next_run_at ?? undefined,
|
|
657
|
+
lastHash: row.last_heartbeat_hash || undefined,
|
|
658
|
+
lastSentAt: row.last_heartbeat_sent_at ?? undefined,
|
|
659
|
+
updatedAt: row.updated_at,
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function _toCronSkillRecord(row) {
|
|
664
|
+
return {
|
|
665
|
+
id: row.id,
|
|
666
|
+
name: row.name,
|
|
667
|
+
allowedTools: _parseJsonArray(row.allowed_tools),
|
|
668
|
+
systemPrompt: row.system_prompt || undefined,
|
|
669
|
+
model: row.model || undefined,
|
|
670
|
+
maxTurns: row.max_turns ?? 3,
|
|
671
|
+
timeoutMs: row.timeout_ms ?? 60000,
|
|
672
|
+
createdAt: row.created_at,
|
|
673
|
+
updatedAt: row.updated_at,
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function _toCronJobRecord(row) {
|
|
678
|
+
return {
|
|
679
|
+
id: row.id,
|
|
680
|
+
name: row.name,
|
|
681
|
+
enabled: _toBool(row.enabled),
|
|
682
|
+
sessionTarget: row.session_target || 'isolated',
|
|
683
|
+
wakeMode: row.wake_mode || 'next-heartbeat',
|
|
684
|
+
schedule: {
|
|
685
|
+
kind: row.schedule_kind,
|
|
686
|
+
everyMs: row.schedule_every_ms ?? undefined,
|
|
687
|
+
anchorMs: row.schedule_anchor_ms ?? undefined,
|
|
688
|
+
atMs: row.schedule_at_ms ?? undefined,
|
|
689
|
+
},
|
|
690
|
+
skillId: row.skill_id,
|
|
691
|
+
prompt: row.prompt,
|
|
692
|
+
deliveryMode: row.delivery_mode || 'notification',
|
|
693
|
+
deliveryWebhookUrl: row.delivery_webhook_url || undefined,
|
|
694
|
+
deliveryNotificationTitle: row.delivery_notification_title || undefined,
|
|
695
|
+
activeHours: _mapActiveHours(row.active_hours_start, row.active_hours_end, row.active_hours_tz),
|
|
696
|
+
lastRunAt: row.last_run_at ?? undefined,
|
|
697
|
+
nextRunAt: row.next_run_at ?? undefined,
|
|
698
|
+
lastRunStatus: row.last_run_status || undefined,
|
|
699
|
+
lastError: row.last_error || undefined,
|
|
700
|
+
lastDurationMs: row.last_duration_ms ?? undefined,
|
|
701
|
+
lastResponseHash: row.last_response_hash || undefined,
|
|
702
|
+
lastResponseSentAt: row.last_response_sent_at ?? undefined,
|
|
703
|
+
consecutiveErrors: row.consecutive_errors ?? 0,
|
|
704
|
+
createdAt: row.created_at,
|
|
705
|
+
updatedAt: row.updated_at,
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
function _toCronRunRecord(row) {
|
|
710
|
+
return {
|
|
711
|
+
id: row.id,
|
|
712
|
+
jobId: row.job_id,
|
|
713
|
+
startedAt: row.started_at,
|
|
714
|
+
endedAt: row.ended_at ?? undefined,
|
|
715
|
+
status: row.status,
|
|
716
|
+
durationMs: row.duration_ms ?? undefined,
|
|
717
|
+
error: row.error || undefined,
|
|
718
|
+
responseText: row.response_text || undefined,
|
|
719
|
+
wasHeartbeatOk: _toBool(row.was_heartbeat_ok || 0),
|
|
720
|
+
wasDeduped: _toBool(row.was_deduped || 0),
|
|
721
|
+
delivered: _toBool(row.delivered || 0),
|
|
722
|
+
wakeSource: row.wake_source || undefined,
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
function _ensureSchedulerConfigRow() {
|
|
727
|
+
const now = Date.now();
|
|
728
|
+
run(
|
|
729
|
+
`INSERT OR IGNORE INTO scheduler_config
|
|
730
|
+
(id, enabled, scheduling_mode, run_on_charging, updated_at)
|
|
731
|
+
VALUES (1, 1, 'balanced', 1, ?)`,
|
|
732
|
+
[now]
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
function _ensureHeartbeatConfigRow() {
|
|
737
|
+
const now = Date.now();
|
|
738
|
+
run(
|
|
739
|
+
`INSERT OR IGNORE INTO heartbeat_config
|
|
740
|
+
(id, enabled, every_ms, updated_at)
|
|
741
|
+
VALUES (1, 0, 1800000, ?)`,
|
|
742
|
+
[now]
|
|
743
|
+
);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
function _resolveNextRunAt(schedule, now = Date.now()) {
|
|
747
|
+
if (!schedule || !schedule.kind) return null;
|
|
748
|
+
if (schedule.kind === 'at') return Number(schedule.atMs) || null;
|
|
749
|
+
if (schedule.kind === 'every') {
|
|
750
|
+
const everyMs = Number(schedule.everyMs) || 0;
|
|
751
|
+
if (everyMs <= 0) return null;
|
|
752
|
+
return now + everyMs;
|
|
753
|
+
}
|
|
754
|
+
return null;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
export function getSchedulerConfig() {
|
|
758
|
+
_ensureSchedulerConfigRow();
|
|
759
|
+
const row = queryOne('SELECT * FROM scheduler_config WHERE id = 1');
|
|
760
|
+
return _toSchedulerConfig(row);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
export function setSchedulerConfig(patch = {}) {
|
|
764
|
+
_ensureSchedulerConfigRow();
|
|
765
|
+
const sets = [];
|
|
766
|
+
const params = [];
|
|
767
|
+
|
|
768
|
+
if (patch.enabled !== undefined) {
|
|
769
|
+
sets.push('enabled = ?');
|
|
770
|
+
params.push(_toIntBool(!!patch.enabled));
|
|
771
|
+
}
|
|
772
|
+
if (patch.schedulingMode !== undefined || patch.scheduling_mode !== undefined) {
|
|
773
|
+
sets.push('scheduling_mode = ?');
|
|
774
|
+
params.push((patch.schedulingMode ?? patch.scheduling_mode) || 'balanced');
|
|
775
|
+
}
|
|
776
|
+
if (patch.runOnCharging !== undefined || patch.run_on_charging !== undefined) {
|
|
777
|
+
sets.push('run_on_charging = ?');
|
|
778
|
+
params.push(_toIntBool(!!(patch.runOnCharging ?? patch.run_on_charging)));
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
const globalActiveHours = patch.globalActiveHours || patch.global_active_hours;
|
|
782
|
+
if (globalActiveHours) {
|
|
783
|
+
sets.push('global_active_hours_start = ?');
|
|
784
|
+
params.push(globalActiveHours.start || null);
|
|
785
|
+
sets.push('global_active_hours_end = ?');
|
|
786
|
+
params.push(globalActiveHours.end || null);
|
|
787
|
+
sets.push('global_active_hours_tz = ?');
|
|
788
|
+
params.push(globalActiveHours.tz || globalActiveHours.timezone || null);
|
|
789
|
+
} else {
|
|
790
|
+
if (patch.global_active_hours_start !== undefined) {
|
|
791
|
+
sets.push('global_active_hours_start = ?');
|
|
792
|
+
params.push(patch.global_active_hours_start || null);
|
|
793
|
+
}
|
|
794
|
+
if (patch.global_active_hours_end !== undefined) {
|
|
795
|
+
sets.push('global_active_hours_end = ?');
|
|
796
|
+
params.push(patch.global_active_hours_end || null);
|
|
797
|
+
}
|
|
798
|
+
if (patch.global_active_hours_tz !== undefined) {
|
|
799
|
+
sets.push('global_active_hours_tz = ?');
|
|
800
|
+
params.push(patch.global_active_hours_tz || null);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
sets.push('updated_at = ?');
|
|
805
|
+
params.push(Date.now());
|
|
806
|
+
params.push(1);
|
|
807
|
+
|
|
808
|
+
run(`UPDATE scheduler_config SET ${sets.join(', ')} WHERE id = ?`, params);
|
|
809
|
+
flush();
|
|
810
|
+
return getSchedulerConfig();
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
export function getHeartbeatConfig() {
|
|
814
|
+
_ensureHeartbeatConfigRow();
|
|
815
|
+
const row = queryOne('SELECT * FROM heartbeat_config WHERE id = 1');
|
|
816
|
+
return _toHeartbeatConfig(row);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
export function setHeartbeatConfig(patch = {}) {
|
|
820
|
+
_ensureHeartbeatConfigRow();
|
|
821
|
+
const sets = [];
|
|
822
|
+
const params = [];
|
|
823
|
+
|
|
824
|
+
if (patch.enabled !== undefined) {
|
|
825
|
+
sets.push('enabled = ?');
|
|
826
|
+
params.push(_toIntBool(!!patch.enabled));
|
|
827
|
+
}
|
|
828
|
+
if (patch.everyMs !== undefined || patch.every_ms !== undefined) {
|
|
829
|
+
sets.push('every_ms = ?');
|
|
830
|
+
params.push(Number(patch.everyMs ?? patch.every_ms) || 1800000);
|
|
831
|
+
}
|
|
832
|
+
if (patch.prompt !== undefined) {
|
|
833
|
+
sets.push('prompt = ?');
|
|
834
|
+
params.push(patch.prompt || null);
|
|
835
|
+
}
|
|
836
|
+
if (patch.skillId !== undefined || patch.skill_id !== undefined) {
|
|
837
|
+
sets.push('skill_id = ?');
|
|
838
|
+
params.push((patch.skillId ?? patch.skill_id) || null);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
const activeHours = patch.activeHours || patch.active_hours;
|
|
842
|
+
if (activeHours) {
|
|
843
|
+
sets.push('active_hours_start = ?');
|
|
844
|
+
params.push(activeHours.start || null);
|
|
845
|
+
sets.push('active_hours_end = ?');
|
|
846
|
+
params.push(activeHours.end || null);
|
|
847
|
+
sets.push('active_hours_tz = ?');
|
|
848
|
+
params.push(activeHours.tz || activeHours.timezone || null);
|
|
849
|
+
} else {
|
|
850
|
+
if (patch.active_hours_start !== undefined) {
|
|
851
|
+
sets.push('active_hours_start = ?');
|
|
852
|
+
params.push(patch.active_hours_start || null);
|
|
853
|
+
}
|
|
854
|
+
if (patch.active_hours_end !== undefined) {
|
|
855
|
+
sets.push('active_hours_end = ?');
|
|
856
|
+
params.push(patch.active_hours_end || null);
|
|
857
|
+
}
|
|
858
|
+
if (patch.active_hours_tz !== undefined) {
|
|
859
|
+
sets.push('active_hours_tz = ?');
|
|
860
|
+
params.push(patch.active_hours_tz || null);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
if (patch.nextRunAt !== undefined || patch.next_run_at !== undefined) {
|
|
865
|
+
sets.push('next_run_at = ?');
|
|
866
|
+
params.push(patch.nextRunAt ?? patch.next_run_at ?? null);
|
|
867
|
+
}
|
|
868
|
+
if (patch.lastHash !== undefined || patch.last_heartbeat_hash !== undefined) {
|
|
869
|
+
sets.push('last_heartbeat_hash = ?');
|
|
870
|
+
params.push((patch.lastHash ?? patch.last_heartbeat_hash) || null);
|
|
871
|
+
}
|
|
872
|
+
if (patch.lastSentAt !== undefined || patch.last_heartbeat_sent_at !== undefined) {
|
|
873
|
+
sets.push('last_heartbeat_sent_at = ?');
|
|
874
|
+
params.push(patch.lastSentAt ?? patch.last_heartbeat_sent_at ?? null);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
sets.push('updated_at = ?');
|
|
878
|
+
params.push(Date.now());
|
|
879
|
+
params.push(1);
|
|
880
|
+
|
|
881
|
+
run(`UPDATE heartbeat_config SET ${sets.join(', ')} WHERE id = ?`, params);
|
|
882
|
+
flush();
|
|
883
|
+
return getHeartbeatConfig();
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
export function addCronSkill(skill) {
|
|
887
|
+
const now = Date.now();
|
|
888
|
+
const id = skill.id || _genId('skill');
|
|
889
|
+
run(
|
|
890
|
+
`INSERT INTO cron_skills
|
|
891
|
+
(id, name, allowed_tools, system_prompt, model, max_turns, timeout_ms, created_at, updated_at)
|
|
892
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
893
|
+
[
|
|
894
|
+
id,
|
|
895
|
+
skill.name,
|
|
896
|
+
skill.allowedTools == null ? null : JSON.stringify(skill.allowedTools),
|
|
897
|
+
skill.systemPrompt || null,
|
|
898
|
+
skill.model || null,
|
|
899
|
+
Number(skill.maxTurns ?? 3),
|
|
900
|
+
Number(skill.timeoutMs ?? 60000),
|
|
901
|
+
now,
|
|
902
|
+
now,
|
|
903
|
+
]
|
|
904
|
+
);
|
|
905
|
+
flush();
|
|
906
|
+
return _toCronSkillRecord(queryOne('SELECT * FROM cron_skills WHERE id = ?', [id]));
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
export function updateCronSkill(id, patch = {}) {
|
|
910
|
+
const sets = [];
|
|
911
|
+
const params = [];
|
|
912
|
+
|
|
913
|
+
if (patch.name !== undefined) {
|
|
914
|
+
sets.push('name = ?');
|
|
915
|
+
params.push(patch.name);
|
|
916
|
+
}
|
|
917
|
+
if (patch.allowedTools !== undefined || patch.allowed_tools !== undefined) {
|
|
918
|
+
const value = patch.allowedTools ?? patch.allowed_tools;
|
|
919
|
+
sets.push('allowed_tools = ?');
|
|
920
|
+
params.push(value == null ? null : JSON.stringify(value));
|
|
921
|
+
}
|
|
922
|
+
if (patch.systemPrompt !== undefined || patch.system_prompt !== undefined) {
|
|
923
|
+
sets.push('system_prompt = ?');
|
|
924
|
+
params.push((patch.systemPrompt ?? patch.system_prompt) || null);
|
|
925
|
+
}
|
|
926
|
+
if (patch.model !== undefined) {
|
|
927
|
+
sets.push('model = ?');
|
|
928
|
+
params.push(patch.model || null);
|
|
929
|
+
}
|
|
930
|
+
if (patch.maxTurns !== undefined || patch.max_turns !== undefined) {
|
|
931
|
+
sets.push('max_turns = ?');
|
|
932
|
+
params.push(Number(patch.maxTurns ?? patch.max_turns ?? 3));
|
|
933
|
+
}
|
|
934
|
+
if (patch.timeoutMs !== undefined || patch.timeout_ms !== undefined) {
|
|
935
|
+
sets.push('timeout_ms = ?');
|
|
936
|
+
params.push(Number(patch.timeoutMs ?? patch.timeout_ms ?? 60000));
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
sets.push('updated_at = ?');
|
|
940
|
+
params.push(Date.now());
|
|
941
|
+
params.push(id);
|
|
942
|
+
|
|
943
|
+
run(`UPDATE cron_skills SET ${sets.join(', ')} WHERE id = ?`, params);
|
|
944
|
+
flush();
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
export function removeCronSkill(id) {
|
|
948
|
+
run('DELETE FROM cron_skills WHERE id = ?', [id]);
|
|
949
|
+
flush();
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
export function listCronSkills() {
|
|
953
|
+
return query('SELECT * FROM cron_skills ORDER BY updated_at DESC').map(_toCronSkillRecord);
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
export function addCronJob(job) {
|
|
957
|
+
const now = Date.now();
|
|
958
|
+
const id = job.id || _genId('job');
|
|
959
|
+
const schedule = job.schedule || {};
|
|
960
|
+
const activeHours = job.activeHours || {};
|
|
961
|
+
const nextRunAt = job.nextRunAt ?? _resolveNextRunAt(schedule, now);
|
|
962
|
+
run(
|
|
963
|
+
`INSERT INTO cron_jobs
|
|
964
|
+
(id, name, enabled, session_target, wake_mode, schedule_kind, schedule_every_ms, schedule_anchor_ms, schedule_at_ms,
|
|
965
|
+
skill_id, prompt, delivery_mode, delivery_webhook_url, delivery_notification_title,
|
|
966
|
+
active_hours_start, active_hours_end, active_hours_tz,
|
|
967
|
+
last_run_at, next_run_at, last_run_status, last_error, last_duration_ms,
|
|
968
|
+
last_response_hash, last_response_sent_at, consecutive_errors, created_at, updated_at)
|
|
969
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
970
|
+
[
|
|
971
|
+
id,
|
|
972
|
+
job.name,
|
|
973
|
+
_toIntBool(job.enabled !== false),
|
|
974
|
+
job.sessionTarget || 'isolated',
|
|
975
|
+
job.wakeMode || 'next-heartbeat',
|
|
976
|
+
schedule.kind,
|
|
977
|
+
schedule.kind === 'every' ? Number(schedule.everyMs) || null : null,
|
|
978
|
+
Number(schedule.anchorMs ?? job.scheduleAnchorMs) || null,
|
|
979
|
+
schedule.kind === 'at' ? Number(schedule.atMs) || null : null,
|
|
980
|
+
job.skillId,
|
|
981
|
+
job.prompt,
|
|
982
|
+
job.deliveryMode || 'notification',
|
|
983
|
+
job.deliveryWebhookUrl || null,
|
|
984
|
+
job.deliveryNotificationTitle || null,
|
|
985
|
+
activeHours.start || null,
|
|
986
|
+
activeHours.end || null,
|
|
987
|
+
activeHours.tz || activeHours.timezone || null,
|
|
988
|
+
job.lastRunAt || null,
|
|
989
|
+
nextRunAt,
|
|
990
|
+
job.lastRunStatus || null,
|
|
991
|
+
job.lastError || null,
|
|
992
|
+
job.lastDurationMs || null,
|
|
993
|
+
job.lastResponseHash || null,
|
|
994
|
+
job.lastResponseSentAt || null,
|
|
995
|
+
Number(job.consecutiveErrors || 0),
|
|
996
|
+
now,
|
|
997
|
+
now,
|
|
998
|
+
]
|
|
999
|
+
);
|
|
1000
|
+
flush();
|
|
1001
|
+
return _toCronJobRecord(queryOne('SELECT * FROM cron_jobs WHERE id = ?', [id]));
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
export function updateCronJob(id, patch = {}) {
|
|
1005
|
+
const sets = [];
|
|
1006
|
+
const params = [];
|
|
1007
|
+
|
|
1008
|
+
if (patch.name !== undefined) {
|
|
1009
|
+
sets.push('name = ?');
|
|
1010
|
+
params.push(patch.name);
|
|
1011
|
+
}
|
|
1012
|
+
if (patch.enabled !== undefined) {
|
|
1013
|
+
sets.push('enabled = ?');
|
|
1014
|
+
params.push(_toIntBool(!!patch.enabled));
|
|
1015
|
+
}
|
|
1016
|
+
if (patch.sessionTarget !== undefined || patch.session_target !== undefined) {
|
|
1017
|
+
sets.push('session_target = ?');
|
|
1018
|
+
params.push((patch.sessionTarget ?? patch.session_target) || 'isolated');
|
|
1019
|
+
}
|
|
1020
|
+
if (patch.wakeMode !== undefined || patch.wake_mode !== undefined) {
|
|
1021
|
+
sets.push('wake_mode = ?');
|
|
1022
|
+
params.push((patch.wakeMode ?? patch.wake_mode) || 'next-heartbeat');
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
if (patch.schedule) {
|
|
1026
|
+
const schedule = patch.schedule;
|
|
1027
|
+
if (schedule.kind !== undefined) {
|
|
1028
|
+
sets.push('schedule_kind = ?');
|
|
1029
|
+
params.push(schedule.kind);
|
|
1030
|
+
}
|
|
1031
|
+
if (schedule.everyMs !== undefined || schedule.every_ms !== undefined) {
|
|
1032
|
+
sets.push('schedule_every_ms = ?');
|
|
1033
|
+
params.push(Number(schedule.everyMs ?? schedule.every_ms) || null);
|
|
1034
|
+
}
|
|
1035
|
+
if (schedule.anchorMs !== undefined || schedule.anchor_ms !== undefined) {
|
|
1036
|
+
sets.push('schedule_anchor_ms = ?');
|
|
1037
|
+
params.push(Number(schedule.anchorMs ?? schedule.anchor_ms) || null);
|
|
1038
|
+
}
|
|
1039
|
+
if (schedule.atMs !== undefined || schedule.at_ms !== undefined) {
|
|
1040
|
+
sets.push('schedule_at_ms = ?');
|
|
1041
|
+
params.push(Number(schedule.atMs ?? schedule.at_ms) || null);
|
|
1042
|
+
}
|
|
1043
|
+
if (patch.nextRunAt === undefined && patch.next_run_at === undefined) {
|
|
1044
|
+
sets.push('next_run_at = ?');
|
|
1045
|
+
params.push(_resolveNextRunAt(schedule, Date.now()));
|
|
1046
|
+
}
|
|
1047
|
+
} else {
|
|
1048
|
+
if (patch.scheduleKind !== undefined || patch.schedule_kind !== undefined) {
|
|
1049
|
+
sets.push('schedule_kind = ?');
|
|
1050
|
+
params.push(patch.scheduleKind ?? patch.schedule_kind);
|
|
1051
|
+
}
|
|
1052
|
+
if (patch.scheduleEveryMs !== undefined || patch.schedule_every_ms !== undefined) {
|
|
1053
|
+
sets.push('schedule_every_ms = ?');
|
|
1054
|
+
params.push(Number(patch.scheduleEveryMs ?? patch.schedule_every_ms) || null);
|
|
1055
|
+
}
|
|
1056
|
+
if (patch.scheduleAnchorMs !== undefined || patch.schedule_anchor_ms !== undefined) {
|
|
1057
|
+
sets.push('schedule_anchor_ms = ?');
|
|
1058
|
+
params.push(Number(patch.scheduleAnchorMs ?? patch.schedule_anchor_ms) || null);
|
|
1059
|
+
}
|
|
1060
|
+
if (patch.scheduleAtMs !== undefined || patch.schedule_at_ms !== undefined) {
|
|
1061
|
+
sets.push('schedule_at_ms = ?');
|
|
1062
|
+
params.push(Number(patch.scheduleAtMs ?? patch.schedule_at_ms) || null);
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
if (patch.skillId !== undefined || patch.skill_id !== undefined) {
|
|
1067
|
+
sets.push('skill_id = ?');
|
|
1068
|
+
params.push((patch.skillId ?? patch.skill_id) || null);
|
|
1069
|
+
}
|
|
1070
|
+
if (patch.prompt !== undefined) {
|
|
1071
|
+
sets.push('prompt = ?');
|
|
1072
|
+
params.push(patch.prompt || '');
|
|
1073
|
+
}
|
|
1074
|
+
if (patch.deliveryMode !== undefined || patch.delivery_mode !== undefined) {
|
|
1075
|
+
sets.push('delivery_mode = ?');
|
|
1076
|
+
params.push((patch.deliveryMode ?? patch.delivery_mode) || 'notification');
|
|
1077
|
+
}
|
|
1078
|
+
if (patch.deliveryWebhookUrl !== undefined || patch.delivery_webhook_url !== undefined) {
|
|
1079
|
+
sets.push('delivery_webhook_url = ?');
|
|
1080
|
+
params.push((patch.deliveryWebhookUrl ?? patch.delivery_webhook_url) || null);
|
|
1081
|
+
}
|
|
1082
|
+
if (
|
|
1083
|
+
patch.deliveryNotificationTitle !== undefined ||
|
|
1084
|
+
patch.delivery_notification_title !== undefined
|
|
1085
|
+
) {
|
|
1086
|
+
sets.push('delivery_notification_title = ?');
|
|
1087
|
+
params.push((patch.deliveryNotificationTitle ?? patch.delivery_notification_title) || null);
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
const activeHours = patch.activeHours || patch.active_hours;
|
|
1091
|
+
if (activeHours) {
|
|
1092
|
+
sets.push('active_hours_start = ?');
|
|
1093
|
+
params.push(activeHours.start || null);
|
|
1094
|
+
sets.push('active_hours_end = ?');
|
|
1095
|
+
params.push(activeHours.end || null);
|
|
1096
|
+
sets.push('active_hours_tz = ?');
|
|
1097
|
+
params.push(activeHours.tz || activeHours.timezone || null);
|
|
1098
|
+
} else {
|
|
1099
|
+
if (patch.active_hours_start !== undefined) {
|
|
1100
|
+
sets.push('active_hours_start = ?');
|
|
1101
|
+
params.push(patch.active_hours_start || null);
|
|
1102
|
+
}
|
|
1103
|
+
if (patch.active_hours_end !== undefined) {
|
|
1104
|
+
sets.push('active_hours_end = ?');
|
|
1105
|
+
params.push(patch.active_hours_end || null);
|
|
1106
|
+
}
|
|
1107
|
+
if (patch.active_hours_tz !== undefined) {
|
|
1108
|
+
sets.push('active_hours_tz = ?');
|
|
1109
|
+
params.push(patch.active_hours_tz || null);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
if (patch.lastRunAt !== undefined || patch.last_run_at !== undefined) {
|
|
1114
|
+
sets.push('last_run_at = ?');
|
|
1115
|
+
params.push(patch.lastRunAt ?? patch.last_run_at ?? null);
|
|
1116
|
+
}
|
|
1117
|
+
if (patch.nextRunAt !== undefined || patch.next_run_at !== undefined) {
|
|
1118
|
+
sets.push('next_run_at = ?');
|
|
1119
|
+
params.push(patch.nextRunAt ?? patch.next_run_at ?? null);
|
|
1120
|
+
}
|
|
1121
|
+
if (patch.lastRunStatus !== undefined || patch.last_run_status !== undefined) {
|
|
1122
|
+
sets.push('last_run_status = ?');
|
|
1123
|
+
params.push((patch.lastRunStatus ?? patch.last_run_status) || null);
|
|
1124
|
+
}
|
|
1125
|
+
if (patch.lastError !== undefined || patch.last_error !== undefined) {
|
|
1126
|
+
sets.push('last_error = ?');
|
|
1127
|
+
params.push((patch.lastError ?? patch.last_error) || null);
|
|
1128
|
+
}
|
|
1129
|
+
if (patch.lastDurationMs !== undefined || patch.last_duration_ms !== undefined) {
|
|
1130
|
+
sets.push('last_duration_ms = ?');
|
|
1131
|
+
params.push(patch.lastDurationMs ?? patch.last_duration_ms ?? null);
|
|
1132
|
+
}
|
|
1133
|
+
if (patch.lastResponseHash !== undefined || patch.last_response_hash !== undefined) {
|
|
1134
|
+
sets.push('last_response_hash = ?');
|
|
1135
|
+
params.push((patch.lastResponseHash ?? patch.last_response_hash) || null);
|
|
1136
|
+
}
|
|
1137
|
+
if (patch.lastResponseSentAt !== undefined || patch.last_response_sent_at !== undefined) {
|
|
1138
|
+
sets.push('last_response_sent_at = ?');
|
|
1139
|
+
params.push(patch.lastResponseSentAt ?? patch.last_response_sent_at ?? null);
|
|
1140
|
+
}
|
|
1141
|
+
if (patch.consecutiveErrors !== undefined || patch.consecutive_errors !== undefined) {
|
|
1142
|
+
sets.push('consecutive_errors = ?');
|
|
1143
|
+
params.push(Number(patch.consecutiveErrors ?? patch.consecutive_errors) || 0);
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
sets.push('updated_at = ?');
|
|
1147
|
+
params.push(Date.now());
|
|
1148
|
+
params.push(id);
|
|
1149
|
+
|
|
1150
|
+
run(`UPDATE cron_jobs SET ${sets.join(', ')} WHERE id = ?`, params);
|
|
1151
|
+
flush();
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
export function removeCronJob(id) {
|
|
1155
|
+
run('DELETE FROM cron_jobs WHERE id = ?', [id]);
|
|
1156
|
+
run('DELETE FROM cron_runs WHERE job_id = ?', [id]);
|
|
1157
|
+
flush();
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
export function listCronJobs() {
|
|
1161
|
+
return query('SELECT * FROM cron_jobs ORDER BY updated_at DESC').map(_toCronJobRecord);
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
export function getDueJobs(nowMs = Date.now()) {
|
|
1165
|
+
return query(
|
|
1166
|
+
`SELECT * FROM cron_jobs
|
|
1167
|
+
WHERE enabled = 1
|
|
1168
|
+
AND next_run_at IS NOT NULL
|
|
1169
|
+
AND next_run_at <= ?
|
|
1170
|
+
ORDER BY next_run_at ASC`,
|
|
1171
|
+
[nowMs]
|
|
1172
|
+
).map(_toCronJobRecord);
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
export function insertCronRun(runData) {
|
|
1176
|
+
run(
|
|
1177
|
+
`INSERT INTO cron_runs
|
|
1178
|
+
(job_id, started_at, ended_at, status, duration_ms, error, response_text, was_heartbeat_ok, was_deduped, delivered, wake_source)
|
|
1179
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
1180
|
+
[
|
|
1181
|
+
runData.jobId,
|
|
1182
|
+
runData.startedAt,
|
|
1183
|
+
runData.endedAt || null,
|
|
1184
|
+
runData.status || null,
|
|
1185
|
+
runData.durationMs || null,
|
|
1186
|
+
runData.error || null,
|
|
1187
|
+
runData.responseText || null,
|
|
1188
|
+
_toIntBool(!!runData.wasHeartbeatOk),
|
|
1189
|
+
_toIntBool(!!runData.wasDeduped),
|
|
1190
|
+
_toIntBool(!!runData.delivered),
|
|
1191
|
+
runData.wakeSource || null,
|
|
1192
|
+
]
|
|
1193
|
+
);
|
|
1194
|
+
const row = queryOne('SELECT last_insert_rowid() as id');
|
|
1195
|
+
flush();
|
|
1196
|
+
return row?.id || null;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
export function listCronRuns(jobId = null, limit = 50) {
|
|
1200
|
+
const safeLimit = Math.max(1, Math.min(500, Number(limit) || 50));
|
|
1201
|
+
if (jobId) {
|
|
1202
|
+
return query(
|
|
1203
|
+
'SELECT * FROM cron_runs WHERE job_id = ? ORDER BY started_at DESC LIMIT ?',
|
|
1204
|
+
[jobId, safeLimit]
|
|
1205
|
+
).map(_toCronRunRecord);
|
|
1206
|
+
}
|
|
1207
|
+
return query(
|
|
1208
|
+
'SELECT * FROM cron_runs ORDER BY started_at DESC LIMIT ?',
|
|
1209
|
+
[safeLimit]
|
|
1210
|
+
).map(_toCronRunRecord);
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
export function enqueueSystemEvent(sessionKey, contextKey, text) {
|
|
1214
|
+
const createdAt = Date.now();
|
|
1215
|
+
run(
|
|
1216
|
+
`INSERT INTO system_events
|
|
1217
|
+
(session_key, context_key, text, created_at, consumed)
|
|
1218
|
+
VALUES (?, ?, ?, ?, 0)`,
|
|
1219
|
+
[sessionKey, contextKey || null, text, createdAt]
|
|
1220
|
+
);
|
|
1221
|
+
flush();
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
export function peekPendingEvents(sessionKey) {
|
|
1225
|
+
return query(
|
|
1226
|
+
`SELECT id, session_key, context_key, text, created_at, consumed
|
|
1227
|
+
FROM system_events
|
|
1228
|
+
WHERE session_key = ? AND consumed = 0
|
|
1229
|
+
ORDER BY created_at ASC, id ASC`,
|
|
1230
|
+
[sessionKey]
|
|
1231
|
+
).map((row) => ({
|
|
1232
|
+
id: row.id,
|
|
1233
|
+
sessionKey: row.session_key,
|
|
1234
|
+
contextKey: row.context_key || undefined,
|
|
1235
|
+
text: row.text,
|
|
1236
|
+
createdAt: row.created_at,
|
|
1237
|
+
consumed: _toBool(row.consumed),
|
|
1238
|
+
}));
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
export function consumePendingEvents(ids = []) {
|
|
1242
|
+
if (!Array.isArray(ids) || ids.length === 0) return;
|
|
1243
|
+
const placeholders = ids.map(() => '?').join(', ');
|
|
1244
|
+
run(`UPDATE system_events SET consumed = 1 WHERE id IN (${placeholders})`, ids);
|
|
1245
|
+
flush();
|
|
1246
|
+
}
|