@vellumai/assistant 0.10.8-dev.202607130516.157ae8a → 0.10.8-dev.202607131107.073396f
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/openapi.yaml +7 -63
- package/package.json +1 -1
- package/src/__tests__/memory-jobs-worker-backoff.test.ts +4 -6
- package/src/__tests__/plugin-import-boundary-guard.test.ts +3 -1
- package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +0 -1
- package/src/__tests__/{scheduler-worker-standdown.test.ts → run-due-schedules.test.ts} +5 -55
- package/src/__tests__/schedule-retry.test.ts +22 -15
- package/src/__tests__/scheduler-recurrence.test.ts +15 -23
- package/src/__tests__/scheduler-reuse-conversation.test.ts +19 -27
- package/src/__tests__/scheduler-wake.test.ts +8 -16
- package/src/background-wake/background-wake-routes.test.ts +2 -3
- package/src/cli/commands/memory/__tests__/worker.test.ts +8 -5
- package/src/cli/commands/memory/index.help.ts +7 -10
- package/src/cli/commands/memory/worker.ts +29 -39
- package/src/cli/commands/schedules-worker.ts +10 -43
- package/src/cli/commands/schedules.help.ts +8 -13
- package/src/config/schema.ts +0 -2
- package/src/config/schemas/memory-lifecycle.ts +0 -12
- package/src/config/schemas/memory.ts +0 -4
- package/src/daemon/shutdown-handlers.ts +3 -16
- package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +8 -7
- package/src/{persistence → plugins/defaults/memory}/__tests__/worker-control.test.ts +8 -10
- package/src/plugins/defaults/memory/hooks/shutdown.ts +8 -11
- package/src/plugins/defaults/memory/jobs-worker.ts +28 -101
- package/src/plugins/defaults/memory/paths.ts +3 -2
- package/src/plugins/defaults/memory/routes/__tests__/memory-worker-routes.test.ts +201 -0
- package/src/plugins/defaults/memory/routes/memory-worker-routes.ts +162 -0
- package/src/plugins/defaults/memory/startup.ts +6 -7
- package/src/{persistence → plugins/defaults/memory}/worker-control.ts +12 -21
- package/src/plugins/defaults/memory/worker.ts +2 -2
- package/src/runtime/routes/__tests__/schedule-worker-routes.test.ts +16 -66
- package/src/runtime/routes/background-wake-routes.ts +0 -1
- package/src/runtime/routes/index.ts +1 -1
- package/src/runtime/routes/schedule-worker-routes.ts +16 -84
- package/src/schedule/__tests__/worker-control.test.ts +3 -3
- package/src/schedule/scheduler.ts +21 -66
- package/src/schedule/worker-control.ts +11 -21
- package/src/schedule/worker.ts +3 -4
- package/src/config/schemas/schedules.ts +0 -35
- package/src/runtime/routes/__tests__/memory-worker-routes.test.ts +0 -318
- package/src/runtime/routes/memory-worker-routes.ts +0 -227
package/openapi.yaml
CHANGED
|
@@ -17607,7 +17607,7 @@ paths:
|
|
|
17607
17607
|
post:
|
|
17608
17608
|
operationId: memory_worker_start_post
|
|
17609
17609
|
summary: Start the memory worker
|
|
17610
|
-
description: Spawns (or reuses) the memory worker process as a child of the daemon
|
|
17610
|
+
description: Spawns (or reuses) the memory worker process as a child of the daemon.
|
|
17611
17611
|
tags:
|
|
17612
17612
|
- system
|
|
17613
17613
|
responses:
|
|
@@ -17622,15 +17622,11 @@ paths:
|
|
|
17622
17622
|
type: number
|
|
17623
17623
|
alreadyRunning:
|
|
17624
17624
|
type: boolean
|
|
17625
|
-
workerEnabled:
|
|
17626
|
-
type: boolean
|
|
17627
|
-
const: true
|
|
17628
17625
|
pidPath:
|
|
17629
17626
|
type: string
|
|
17630
17627
|
required:
|
|
17631
17628
|
- pid
|
|
17632
17629
|
- alreadyRunning
|
|
17633
|
-
- workerEnabled
|
|
17634
17630
|
- pidPath
|
|
17635
17631
|
additionalProperties: false
|
|
17636
17632
|
/v1/memory/worker/status:
|
|
@@ -17638,8 +17634,8 @@ paths:
|
|
|
17638
17634
|
operationId: memory_worker_status_get
|
|
17639
17635
|
summary: Memory worker status
|
|
17640
17636
|
description:
|
|
17641
|
-
Reports the memory worker process
|
|
17642
|
-
|
|
17637
|
+
Reports the memory worker process liveness and the embedding-backend status (including a degraded flag and
|
|
17638
|
+
reason when no backend resolves).
|
|
17643
17639
|
tags:
|
|
17644
17640
|
- system
|
|
17645
17641
|
responses:
|
|
@@ -17657,21 +17653,6 @@ paths:
|
|
|
17657
17653
|
- not_running
|
|
17658
17654
|
pid:
|
|
17659
17655
|
type: number
|
|
17660
|
-
workerEnabled:
|
|
17661
|
-
type: boolean
|
|
17662
|
-
syncRunner:
|
|
17663
|
-
type: object
|
|
17664
|
-
properties:
|
|
17665
|
-
status:
|
|
17666
|
-
type: string
|
|
17667
|
-
enum:
|
|
17668
|
-
- running
|
|
17669
|
-
- not_running
|
|
17670
|
-
pid:
|
|
17671
|
-
type: number
|
|
17672
|
-
required:
|
|
17673
|
-
- status
|
|
17674
|
-
additionalProperties: false
|
|
17675
17656
|
embedding:
|
|
17676
17657
|
type: object
|
|
17677
17658
|
properties:
|
|
@@ -17705,15 +17686,13 @@ paths:
|
|
|
17705
17686
|
additionalProperties: false
|
|
17706
17687
|
required:
|
|
17707
17688
|
- status
|
|
17708
|
-
- workerEnabled
|
|
17709
|
-
- syncRunner
|
|
17710
17689
|
- embedding
|
|
17711
17690
|
additionalProperties: false
|
|
17712
17691
|
/v1/memory/worker/stop:
|
|
17713
17692
|
post:
|
|
17714
17693
|
operationId: memory_worker_stop_post
|
|
17715
17694
|
summary: Stop the memory worker
|
|
17716
|
-
description:
|
|
17695
|
+
description: SIGTERMs the memory worker process if it is running.
|
|
17717
17696
|
tags:
|
|
17718
17697
|
- system
|
|
17719
17698
|
responses:
|
|
@@ -17728,12 +17707,8 @@ paths:
|
|
|
17728
17707
|
type: boolean
|
|
17729
17708
|
pid:
|
|
17730
17709
|
type: number
|
|
17731
|
-
workerEnabled:
|
|
17732
|
-
type: boolean
|
|
17733
|
-
const: false
|
|
17734
17710
|
required:
|
|
17735
17711
|
- workerWasRunning
|
|
17736
|
-
- workerEnabled
|
|
17737
17712
|
additionalProperties: false
|
|
17738
17713
|
/v1/messages:
|
|
17739
17714
|
get:
|
|
@@ -25522,9 +25497,7 @@ paths:
|
|
|
25522
25497
|
post:
|
|
25523
25498
|
operationId: schedules_worker_start_post
|
|
25524
25499
|
summary: Start the schedule worker
|
|
25525
|
-
description:
|
|
25526
|
-
Spawns (or reuses) the schedule worker process as a child of the daemon and enables
|
|
25527
|
-
schedules.worker.enabled, so scheduled jobs run out of process.
|
|
25500
|
+
description: Spawns (or reuses) the schedule worker process as a child of the daemon.
|
|
25528
25501
|
tags:
|
|
25529
25502
|
- system
|
|
25530
25503
|
responses:
|
|
@@ -25539,24 +25512,18 @@ paths:
|
|
|
25539
25512
|
type: number
|
|
25540
25513
|
alreadyRunning:
|
|
25541
25514
|
type: boolean
|
|
25542
|
-
workerEnabled:
|
|
25543
|
-
type: boolean
|
|
25544
|
-
const: true
|
|
25545
25515
|
pidPath:
|
|
25546
25516
|
type: string
|
|
25547
25517
|
required:
|
|
25548
25518
|
- pid
|
|
25549
25519
|
- alreadyRunning
|
|
25550
|
-
- workerEnabled
|
|
25551
25520
|
- pidPath
|
|
25552
25521
|
additionalProperties: false
|
|
25553
25522
|
/v1/schedules/worker/status:
|
|
25554
25523
|
get:
|
|
25555
25524
|
operationId: schedules_worker_status_get
|
|
25556
25525
|
summary: Schedule worker status
|
|
25557
|
-
description:
|
|
25558
|
-
Reports the schedule worker process state, schedules.worker.enabled, and whether the daemon's in-process
|
|
25559
|
-
scheduler is currently executing schedules.
|
|
25526
|
+
description: Reports the schedule worker process liveness.
|
|
25560
25527
|
tags:
|
|
25561
25528
|
- system
|
|
25562
25529
|
responses:
|
|
@@ -25574,33 +25541,14 @@ paths:
|
|
|
25574
25541
|
- not_running
|
|
25575
25542
|
pid:
|
|
25576
25543
|
type: number
|
|
25577
|
-
workerEnabled:
|
|
25578
|
-
type: boolean
|
|
25579
|
-
inProcessScheduler:
|
|
25580
|
-
type: object
|
|
25581
|
-
properties:
|
|
25582
|
-
status:
|
|
25583
|
-
type: string
|
|
25584
|
-
enum:
|
|
25585
|
-
- running
|
|
25586
|
-
- not_running
|
|
25587
|
-
pid:
|
|
25588
|
-
type: number
|
|
25589
|
-
required:
|
|
25590
|
-
- status
|
|
25591
|
-
additionalProperties: false
|
|
25592
25544
|
required:
|
|
25593
25545
|
- status
|
|
25594
|
-
- workerEnabled
|
|
25595
|
-
- inProcessScheduler
|
|
25596
25546
|
additionalProperties: false
|
|
25597
25547
|
/v1/schedules/worker/stop:
|
|
25598
25548
|
post:
|
|
25599
25549
|
operationId: schedules_worker_stop_post
|
|
25600
25550
|
summary: Stop the schedule worker
|
|
25601
|
-
description:
|
|
25602
|
-
Disables schedules.worker.enabled (handing schedule execution back to the in-process scheduler) and
|
|
25603
|
-
SIGTERMs the schedule worker process if it is running.
|
|
25551
|
+
description: SIGTERMs the schedule worker process if it is running.
|
|
25604
25552
|
tags:
|
|
25605
25553
|
- system
|
|
25606
25554
|
responses:
|
|
@@ -25615,12 +25563,8 @@ paths:
|
|
|
25615
25563
|
type: boolean
|
|
25616
25564
|
pid:
|
|
25617
25565
|
type: number
|
|
25618
|
-
workerEnabled:
|
|
25619
|
-
type: boolean
|
|
25620
|
-
const: false
|
|
25621
25566
|
required:
|
|
25622
25567
|
- workerWasRunning
|
|
25623
|
-
- workerEnabled
|
|
25624
25568
|
additionalProperties: false
|
|
25625
25569
|
/v1/search:
|
|
25626
25570
|
get:
|
package/package.json
CHANGED
|
@@ -31,14 +31,12 @@ mock.module("../persistence/checkpoints.js", () => ({
|
|
|
31
31
|
import {
|
|
32
32
|
POLL_INTERVAL_MAX_MS,
|
|
33
33
|
POLL_INTERVAL_MIN_MS,
|
|
34
|
-
|
|
34
|
+
startMemoryJobsWorkerLoop,
|
|
35
35
|
} from "../plugins/defaults/memory/jobs-worker.js";
|
|
36
36
|
import { setConfig } from "./helpers/set-config.js";
|
|
37
37
|
|
|
38
|
-
// Memory disabled so each idle tick stays on the mocked-store fast path
|
|
39
|
-
|
|
40
|
-
// worker nor stands the in-process runner down at the slow-poll cap.
|
|
41
|
-
setConfig("memory", { enabled: false, worker: { enabled: false } });
|
|
38
|
+
// Memory disabled so each idle tick stays on the mocked-store fast path.
|
|
39
|
+
setConfig("memory", { enabled: false });
|
|
42
40
|
|
|
43
41
|
describe("memory jobs worker adaptive poll interval", () => {
|
|
44
42
|
test("exports expected poll interval constants", () => {
|
|
@@ -86,7 +84,7 @@ describe("memory jobs worker adaptive poll interval", () => {
|
|
|
86
84
|
globalThis.clearTimeout = (() => {}) as typeof clearTimeout;
|
|
87
85
|
|
|
88
86
|
try {
|
|
89
|
-
const worker =
|
|
87
|
+
const worker = startMemoryJobsWorkerLoop();
|
|
90
88
|
|
|
91
89
|
// Wait for the initial tick() promise to settle
|
|
92
90
|
await new Promise((resolve) => originalSetTimeout(resolve, 20));
|
|
@@ -135,6 +135,8 @@ const BASELINE: Record<string, readonly string[]> = {
|
|
|
135
135
|
"../../../../skills/catalog-cache.js",
|
|
136
136
|
"../../../../telemetry/watchdog-events-store.js",
|
|
137
137
|
"../../../../tools/skills/delete-managed.js",
|
|
138
|
+
"../../../../util/logger.js",
|
|
139
|
+
"../../../../util/platform.js",
|
|
138
140
|
"../../../channels/types.js",
|
|
139
141
|
"../../../config/loader.js",
|
|
140
142
|
"../../../config/memory-v3-gate.js",
|
|
@@ -180,7 +182,6 @@ const BASELINE: Record<string, readonly string[]> = {
|
|
|
180
182
|
"../../../persistence/jobs-store.js",
|
|
181
183
|
"../../../persistence/raw-query.js",
|
|
182
184
|
"../../../persistence/schema/index.js",
|
|
183
|
-
"../../../persistence/worker-control.js",
|
|
184
185
|
"../../../prompts/persona-resolver.js",
|
|
185
186
|
"../../../prompts/system-prompt.js",
|
|
186
187
|
"../../../prompts/template-detection.js",
|
|
@@ -205,6 +206,7 @@ const BASELINE: Record<string, readonly string[]> = {
|
|
|
205
206
|
"../../../util/strip-comment-lines.js",
|
|
206
207
|
"../../../util/truncate.js",
|
|
207
208
|
"../../../util/worker-memory.js",
|
|
209
|
+
"../../../util/worker-process.js",
|
|
208
210
|
"../../types.js",
|
|
209
211
|
"../injection-presence.js",
|
|
210
212
|
"../injector-order.js",
|
|
@@ -102,7 +102,6 @@ const BASELINE: Record<string, readonly string[]> = {
|
|
|
102
102
|
"src/permissions/checker.ts",
|
|
103
103
|
"src/persistence/conversation-crud.ts",
|
|
104
104
|
"src/persistence/steps.ts",
|
|
105
|
-
"src/persistence/worker-control.ts",
|
|
106
105
|
"src/prompts/system-prompt.ts",
|
|
107
106
|
"src/runtime/routes/consolidation-routes.ts",
|
|
108
107
|
"src/runtime/routes/conversation-query-routes.ts",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* `runDueSchedulesOnce` — the schedule worker's tick — claims and executes
|
|
3
|
+
* every due schedule across modes. Schedule execution is owned by the schedule
|
|
4
|
+
* worker process; the daemon's own scheduler tick runs only watchers and
|
|
5
|
+
* sequences.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
@@ -44,16 +44,7 @@ mock.module("../notifications/emit-signal.js", () => ({
|
|
|
44
44
|
import { getDb } from "../persistence/db-connection.js";
|
|
45
45
|
import { initializeDb } from "../persistence/db-init.js";
|
|
46
46
|
import { createSchedule } from "../schedule/schedule-store.js";
|
|
47
|
-
import {
|
|
48
|
-
runDueSchedulesOnce,
|
|
49
|
-
runScheduleDueWorkOnce,
|
|
50
|
-
} from "../schedule/scheduler.js";
|
|
51
|
-
import { setConfig } from "./helpers/set-config.js";
|
|
52
|
-
|
|
53
|
-
/** Seed `schedules.worker.enabled` for real; the scheduler re-reads it per tick. */
|
|
54
|
-
function setWorkerEnabled(enabled: boolean): void {
|
|
55
|
-
setConfig("schedules", { worker: { enabled } });
|
|
56
|
-
}
|
|
47
|
+
import { runDueSchedulesOnce } from "../schedule/scheduler.js";
|
|
57
48
|
|
|
58
49
|
await initializeDb();
|
|
59
50
|
|
|
@@ -63,7 +54,6 @@ function rawDb(): import("bun:sqlite").Database {
|
|
|
63
54
|
}
|
|
64
55
|
|
|
65
56
|
beforeEach(() => {
|
|
66
|
-
setWorkerEnabled(false);
|
|
67
57
|
mockEmitNotificationSignal.mockClear();
|
|
68
58
|
const db = getDb();
|
|
69
59
|
db.run("DELETE FROM cron_runs");
|
|
@@ -95,48 +85,8 @@ function runsFor(jobId: string): Array<{ status: string }> {
|
|
|
95
85
|
.all(jobId) as Array<{ status: string }>;
|
|
96
86
|
}
|
|
97
87
|
|
|
98
|
-
describe("scheduler stand-down for the schedule worker", () => {
|
|
99
|
-
test("flag on: leaves every due schedule unclaimed and reports none pending", async () => {
|
|
100
|
-
setWorkerEnabled(true);
|
|
101
|
-
const { script } = await createDueScriptAndNotifyJobs();
|
|
102
|
-
const dueBefore = rawDb()
|
|
103
|
-
.query("SELECT id, next_run_at FROM cron_jobs")
|
|
104
|
-
.all() as Array<{ id: string; next_run_at: number }>;
|
|
105
|
-
|
|
106
|
-
const result = await runScheduleDueWorkOnce({
|
|
107
|
-
includeStillPending: true,
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
expect(result.claimed).toBe(0);
|
|
111
|
-
expect(result.stillPending).toBe(0);
|
|
112
|
-
expect(mockEmitNotificationSignal).not.toHaveBeenCalled();
|
|
113
|
-
expect(runsFor(script.id)).toHaveLength(0);
|
|
114
|
-
// Nothing advanced — every schedule stays due for the worker to claim.
|
|
115
|
-
const dueAfter = rawDb()
|
|
116
|
-
.query("SELECT id, next_run_at FROM cron_jobs")
|
|
117
|
-
.all() as Array<{ id: string; next_run_at: number }>;
|
|
118
|
-
expect(dueAfter).toEqual(dueBefore);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
test("flag off: the in-process scheduler claims and runs due schedules itself", async () => {
|
|
122
|
-
setWorkerEnabled(false);
|
|
123
|
-
const { script } = await createDueScriptAndNotifyJobs();
|
|
124
|
-
|
|
125
|
-
const result = await runScheduleDueWorkOnce();
|
|
126
|
-
|
|
127
|
-
expect(result.claimed).toBe(2);
|
|
128
|
-
const scriptRuns = runsFor(script.id);
|
|
129
|
-
expect(scriptRuns).toHaveLength(1);
|
|
130
|
-
expect(scriptRuns[0].status).toBe("ok");
|
|
131
|
-
expect(mockEmitNotificationSignal).toHaveBeenCalled();
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
|
|
135
88
|
describe("runDueSchedulesOnce (the schedule worker's tick)", () => {
|
|
136
89
|
test("claims and executes due schedules across modes", async () => {
|
|
137
|
-
// The worker runs with the flag on; runDueSchedulesOnce itself does not
|
|
138
|
-
// consult the flag — its caller owns schedule execution.
|
|
139
|
-
setWorkerEnabled(true);
|
|
140
90
|
const { script } = await createDueScriptAndNotifyJobs();
|
|
141
91
|
|
|
142
92
|
const result = await runDueSchedulesOnce();
|
|
@@ -56,7 +56,6 @@ mock.module("../notifications/emit-signal.js", () => ({
|
|
|
56
56
|
emitNotificationSignalImpl(payload),
|
|
57
57
|
}));
|
|
58
58
|
|
|
59
|
-
import { loadRawConfig, saveRawConfig } from "../config/loader.js";
|
|
60
59
|
import { getDb } from "../persistence/db-connection.js";
|
|
61
60
|
import { initializeDb } from "../persistence/db-init.js";
|
|
62
61
|
import { applyRetryDecision, decideRetry } from "../schedule/retry-policy.js";
|
|
@@ -72,15 +71,19 @@ import {
|
|
|
72
71
|
scheduleRetry,
|
|
73
72
|
} from "../schedule/schedule-store.js";
|
|
74
73
|
import type { SchedulerHandle } from "../schedule/scheduler.js";
|
|
75
|
-
import {
|
|
74
|
+
import {
|
|
75
|
+
runDueSchedulesOnce,
|
|
76
|
+
runScheduleDueWorkOnce,
|
|
77
|
+
} from "../schedule/scheduler.js";
|
|
76
78
|
import type { ScheduleMessageProcessor } from "../schedule/scheduler-types.js";
|
|
77
79
|
|
|
78
80
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* mocked `
|
|
82
|
-
*
|
|
83
|
-
*
|
|
81
|
+
* Wire a single per-test `processMessage` callback into both schedule dispatch
|
|
82
|
+
* paths — the conversation-reuse path (mocked `daemon/process-message`) and the
|
|
83
|
+
* fresh-bootstrap path (mocked `runBackgroundJob`) — and return a handle that
|
|
84
|
+
* drives schedule execution directly via `runDueSchedulesOnce` (the schedule
|
|
85
|
+
* worker's path). Avoids spawning the real worker process in tests while
|
|
86
|
+
* exercising the exact execution + retry logic.
|
|
84
87
|
*/
|
|
85
88
|
function startScheduler(
|
|
86
89
|
processMessage: ScheduleMessageProcessor,
|
|
@@ -92,18 +95,22 @@ function startScheduler(
|
|
|
92
95
|
};
|
|
93
96
|
processMessageImpl = dispatch;
|
|
94
97
|
injectedProcessMessageForRunner = dispatch;
|
|
95
|
-
|
|
98
|
+
// Mimic the real scheduler's immediate startup tick that fires due
|
|
99
|
+
// schedules once; tests await a short delay for it to settle before
|
|
100
|
+
// asserting or driving further runs via `runOnce`.
|
|
101
|
+
void runDueSchedulesOnce();
|
|
102
|
+
return {
|
|
103
|
+
async runOnce(): Promise<number> {
|
|
104
|
+
const r = await runDueSchedulesOnce();
|
|
105
|
+
return r.completed + r.failed + r.skipped;
|
|
106
|
+
},
|
|
107
|
+
runDueWorkOnce: (options) => runScheduleDueWorkOnce(options),
|
|
108
|
+
stop(): void {},
|
|
109
|
+
};
|
|
96
110
|
}
|
|
97
111
|
|
|
98
112
|
await initializeDb();
|
|
99
113
|
|
|
100
|
-
// The schedule worker is on by default, which stands the daemon's in-process
|
|
101
|
-
// scheduler down. These tests exercise that in-process execution path directly,
|
|
102
|
-
// so pin the worker flag off for this test process.
|
|
103
|
-
const rawConfig = loadRawConfig();
|
|
104
|
-
rawConfig.schedules = { worker: { enabled: false } };
|
|
105
|
-
saveRawConfig(rawConfig);
|
|
106
|
-
|
|
107
114
|
/** Access the underlying bun:sqlite Database for raw parameterized queries. */
|
|
108
115
|
function getRawDb(): import("bun:sqlite").Database {
|
|
109
116
|
return (getDb() as unknown as { $client: import("bun:sqlite").Database })
|
|
@@ -53,7 +53,6 @@ mock.module("../notifications/emit-signal.js", () => ({
|
|
|
53
53
|
},
|
|
54
54
|
}));
|
|
55
55
|
|
|
56
|
-
import { loadRawConfig, saveRawConfig } from "../config/loader.js";
|
|
57
56
|
import { getDb } from "../persistence/db-connection.js";
|
|
58
57
|
import { initializeDb } from "../persistence/db-init.js";
|
|
59
58
|
import {
|
|
@@ -61,17 +60,10 @@ import {
|
|
|
61
60
|
getSchedule,
|
|
62
61
|
getScheduleRuns,
|
|
63
62
|
} from "../schedule/schedule-store.js";
|
|
64
|
-
import {
|
|
63
|
+
import { runDueSchedulesOnce } from "../schedule/scheduler.js";
|
|
65
64
|
|
|
66
65
|
await initializeDb();
|
|
67
66
|
|
|
68
|
-
// The schedule worker is on by default, which stands the daemon's in-process
|
|
69
|
-
// scheduler down. These tests exercise that in-process execution path directly,
|
|
70
|
-
// so pin the worker flag off for this test process.
|
|
71
|
-
const rawConfig = loadRawConfig();
|
|
72
|
-
rawConfig.schedules = { worker: { enabled: false } };
|
|
73
|
-
saveRawConfig(rawConfig);
|
|
74
|
-
|
|
75
67
|
/** Access the underlying bun:sqlite Database for raw parameterized queries. */
|
|
76
68
|
function getRawDb(): import("bun:sqlite").Database {
|
|
77
69
|
return (getDb() as unknown as { $client: import("bun:sqlite").Database })
|
|
@@ -150,7 +142,7 @@ describe("scheduler RRULE execution", () => {
|
|
|
150
142
|
processedMessages.push({ conversationId, message: prompt });
|
|
151
143
|
};
|
|
152
144
|
|
|
153
|
-
await
|
|
145
|
+
await runDueSchedulesOnce();
|
|
154
146
|
|
|
155
147
|
// The runner should have been invoked with the RRULE message
|
|
156
148
|
expect(
|
|
@@ -198,7 +190,7 @@ describe("scheduler RRULE execution", () => {
|
|
|
198
190
|
};
|
|
199
191
|
|
|
200
192
|
// First tick: the expired schedule should fire its final due run
|
|
201
|
-
await
|
|
193
|
+
await runDueSchedulesOnce();
|
|
202
194
|
|
|
203
195
|
// The message IS delivered once
|
|
204
196
|
expect(processedMessages).toContain("Final expired run");
|
|
@@ -216,7 +208,7 @@ describe("scheduler RRULE execution", () => {
|
|
|
216
208
|
|
|
217
209
|
// Second tick: the disabled schedule must NOT fire again
|
|
218
210
|
processedMessages.length = 0;
|
|
219
|
-
await
|
|
211
|
+
await runDueSchedulesOnce();
|
|
220
212
|
|
|
221
213
|
expect(processedMessages).not.toContain("Final expired run");
|
|
222
214
|
// No additional runs
|
|
@@ -244,7 +236,7 @@ describe("scheduler RRULE execution", () => {
|
|
|
244
236
|
processedMessages.push({ conversationId, message: prompt });
|
|
245
237
|
};
|
|
246
238
|
|
|
247
|
-
await
|
|
239
|
+
await runDueSchedulesOnce();
|
|
248
240
|
|
|
249
241
|
// The runner should have been invoked with the cron message
|
|
250
242
|
expect(processedMessages.some((m) => m.message === "Cron message")).toBe(
|
|
@@ -275,7 +267,7 @@ describe("scheduler RRULE execution", () => {
|
|
|
275
267
|
dispatched.push(message);
|
|
276
268
|
};
|
|
277
269
|
|
|
278
|
-
await
|
|
270
|
+
await runDueSchedulesOnce();
|
|
279
271
|
|
|
280
272
|
// The raw `run_task:<id>` string must never reach the agent.
|
|
281
273
|
expect(dispatched).toHaveLength(0);
|
|
@@ -326,7 +318,7 @@ describe("scheduler RRULE execution", () => {
|
|
|
326
318
|
// Force the schedule to be due
|
|
327
319
|
forceScheduleDue(schedule.id);
|
|
328
320
|
|
|
329
|
-
await
|
|
321
|
+
await runDueSchedulesOnce();
|
|
330
322
|
|
|
331
323
|
// The schedule should have been claimed and nextRunAt advanced
|
|
332
324
|
const after = getSchedule(schedule.id);
|
|
@@ -373,7 +365,7 @@ describe("scheduler RRULE execution", () => {
|
|
|
373
365
|
processedMessages.push(prompt);
|
|
374
366
|
};
|
|
375
367
|
|
|
376
|
-
await
|
|
368
|
+
await runDueSchedulesOnce();
|
|
377
369
|
|
|
378
370
|
expect(processedMessages).toContain("Set fire test");
|
|
379
371
|
|
|
@@ -447,7 +439,7 @@ describe("scheduler RRULE execution", () => {
|
|
|
447
439
|
processedMessages.push(prompt);
|
|
448
440
|
};
|
|
449
441
|
|
|
450
|
-
await
|
|
442
|
+
await runDueSchedulesOnce();
|
|
451
443
|
|
|
452
444
|
// The schedule should have fired
|
|
453
445
|
expect(processedMessages).toContain("EXRULE scheduler fire");
|
|
@@ -483,7 +475,7 @@ describe("scheduler RRULE execution", () => {
|
|
|
483
475
|
forceScheduleDue(schedule.id);
|
|
484
476
|
const forcedDueAt = getSchedule(schedule.id)!.nextRunAt;
|
|
485
477
|
|
|
486
|
-
await
|
|
478
|
+
await runDueSchedulesOnce();
|
|
487
479
|
|
|
488
480
|
const after = getSchedule(schedule.id);
|
|
489
481
|
expect(after).not.toBeNull();
|
|
@@ -512,7 +504,7 @@ describe("scheduler RRULE execution", () => {
|
|
|
512
504
|
processedMessages.push({ conversationId, message: prompt });
|
|
513
505
|
};
|
|
514
506
|
|
|
515
|
-
await
|
|
507
|
+
await runDueSchedulesOnce();
|
|
516
508
|
|
|
517
509
|
expect(
|
|
518
510
|
processedMessages.some((m) => m.message === "Execute this once"),
|
|
@@ -541,7 +533,7 @@ describe("scheduler RRULE execution", () => {
|
|
|
541
533
|
|
|
542
534
|
expect(getSchedule(schedule.id)!.status).toBe("active");
|
|
543
535
|
|
|
544
|
-
await
|
|
536
|
+
await runDueSchedulesOnce();
|
|
545
537
|
|
|
546
538
|
expect(notifySignalCalls).toHaveLength(1);
|
|
547
539
|
expect(notifySignalCalls[0]).toMatchObject({
|
|
@@ -574,7 +566,7 @@ describe("scheduler RRULE execution", () => {
|
|
|
574
566
|
|
|
575
567
|
runBackgroundJobShouldFail = true;
|
|
576
568
|
|
|
577
|
-
await
|
|
569
|
+
await runDueSchedulesOnce();
|
|
578
570
|
|
|
579
571
|
const after = getSchedule(schedule.id);
|
|
580
572
|
expect(after).not.toBeNull();
|
|
@@ -597,7 +589,7 @@ describe("scheduler RRULE execution", () => {
|
|
|
597
589
|
|
|
598
590
|
forceScheduleDue(schedule.id);
|
|
599
591
|
|
|
600
|
-
await
|
|
592
|
+
await runDueSchedulesOnce();
|
|
601
593
|
|
|
602
594
|
expect(notifySignalCalls).toHaveLength(1);
|
|
603
595
|
expect(notifySignalCalls[0]).toMatchObject({
|
|
@@ -635,7 +627,7 @@ describe("scheduler RRULE execution", () => {
|
|
|
635
627
|
},
|
|
636
628
|
});
|
|
637
629
|
|
|
638
|
-
await
|
|
630
|
+
await runDueSchedulesOnce();
|
|
639
631
|
|
|
640
632
|
expect(notifySignalCalls).toHaveLength(1);
|
|
641
633
|
expect(notifySignalCalls[0].routingIntent).toBe("all_channels");
|