1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
|
import type { StorageNamespace } from "@dispatch/kernel";
import { describe, expect, it } from "vitest";
import {
applyConfigUpdate,
createHeartbeatConfigStore,
DEFAULT_HEARTBEAT_CONFIG,
} from "./config-store.js";
function createMemoryStorage(): StorageNamespace {
const data = new Map<string, string>();
return {
get: async (key) => data.get(key) ?? null,
set: async (key, value) => {
data.set(key, value);
},
delete: async (key) => {
data.delete(key);
},
has: async (key) => data.has(key),
keys: async (prefix) => {
const all = [...data.keys()];
if (prefix === undefined) return all;
return all.filter((k) => k.startsWith(prefix));
},
};
}
describe("applyConfigUpdate (pure)", () => {
it("leaves omitted fields unchanged from the default", () => {
const next = applyConfigUpdate(DEFAULT_HEARTBEAT_CONFIG, {});
expect(next).toEqual(DEFAULT_HEARTBEAT_CONFIG);
});
it("applies provided fields", () => {
const next = applyConfigUpdate(DEFAULT_HEARTBEAT_CONFIG, {
enabled: true,
systemPrompt: "you are a monitor",
taskPrompt: "check for stuck chats",
model: "opencode/gpt-4o",
});
expect(next.enabled).toBe(true);
expect(next.systemPrompt).toBe("you are a monitor");
expect(next.taskPrompt).toBe("check for stuck chats");
expect(next.model).toBe("opencode/gpt-4o");
// Untouched fields keep defaults.
expect(next.intervalMinutes).toBe(30);
expect(next.reasoningEffort).toBeNull();
});
it("clamps intervalMinutes to a minimum of 1", () => {
expect(
applyConfigUpdate(DEFAULT_HEARTBEAT_CONFIG, { intervalMinutes: 0 }).intervalMinutes,
).toBe(1);
expect(
applyConfigUpdate(DEFAULT_HEARTBEAT_CONFIG, { intervalMinutes: -5 }).intervalMinutes,
).toBe(1);
expect(
applyConfigUpdate(DEFAULT_HEARTBEAT_CONFIG, { intervalMinutes: 7 }).intervalMinutes,
).toBe(7);
});
it("truncates a non-integer interval to an integer", () => {
expect(
applyConfigUpdate(DEFAULT_HEARTBEAT_CONFIG, { intervalMinutes: 12.9 }).intervalMinutes,
).toBe(12);
});
it("clears reasoningEffort when null is passed", () => {
const withEffort = applyConfigUpdate(DEFAULT_HEARTBEAT_CONFIG, { reasoningEffort: "high" });
expect(withEffort.reasoningEffort).toBe("high");
const cleared = applyConfigUpdate(withEffort, { reasoningEffort: null });
expect(cleared.reasoningEffort).toBeNull();
});
it("treats an absent reasoningEffort as unchanged (distinct from null)", () => {
const withEffort = applyConfigUpdate(DEFAULT_HEARTBEAT_CONFIG, { reasoningEffort: "low" });
const untouched = applyConfigUpdate(withEffort, { enabled: true });
expect(untouched.reasoningEffort).toBe("low");
});
it("defaults inactiveOnly to true (the heartbeat is quiet by default while the workspace is busy)", () => {
expect(DEFAULT_HEARTBEAT_CONFIG.inactiveOnly).toBe(true);
});
it("applies an inactiveOnly update", () => {
const next = applyConfigUpdate(DEFAULT_HEARTBEAT_CONFIG, { inactiveOnly: false });
expect(next.inactiveOnly).toBe(false);
});
it("leaves inactiveOnly unchanged when absent from the update", () => {
const off = applyConfigUpdate(DEFAULT_HEARTBEAT_CONFIG, { inactiveOnly: false });
const untouched = applyConfigUpdate(off, { enabled: true });
expect(untouched.inactiveOnly).toBe(false);
});
});
describe("createHeartbeatConfigStore", () => {
it("returns the default config for an unknown workspace", async () => {
const store = createHeartbeatConfigStore(createMemoryStorage());
expect(await store.get("ws-1")).toEqual(DEFAULT_HEARTBEAT_CONFIG);
});
it("persists and round-trips an update", async () => {
const storage = createMemoryStorage();
const store = createHeartbeatConfigStore(storage);
const next = await store.update("ws-1", { enabled: true, intervalMinutes: 5 });
expect(next.enabled).toBe(true);
expect(next.intervalMinutes).toBe(5);
// A fresh store over the same storage reads the persisted value.
const store2 = createHeartbeatConfigStore(storage);
expect(await store2.get("ws-1")).toEqual(next);
});
it("applies a partial update on top of existing config", async () => {
const storage = createMemoryStorage();
const store = createHeartbeatConfigStore(storage);
await store.update("ws-1", { enabled: true, systemPrompt: "a", taskPrompt: "b" });
const next = await store.update("ws-1", { taskPrompt: "c" });
expect(next.enabled).toBe(true);
expect(next.systemPrompt).toBe("a");
expect(next.taskPrompt).toBe("c");
});
it("round-trips inactiveOnly through persistence", async () => {
const storage = createMemoryStorage();
const store = createHeartbeatConfigStore(storage);
const next = await store.update("ws-1", { inactiveOnly: false });
expect(next.inactiveOnly).toBe(false);
const store2 = createHeartbeatConfigStore(storage);
expect((await store2.get("ws-1")).inactiveOnly).toBe(false);
});
it("defaults inactiveOnly to true for legacy configs persisted before the field existed", async () => {
const storage = createMemoryStorage();
// Simulate a config written by an older build that had no inactiveOnly
// field (a pre-inactive-only heartbeat config).
await storage.set("config:ws-1", JSON.stringify({ enabled: true, intervalMinutes: 5 }));
const store = createHeartbeatConfigStore(storage);
const config = await store.get("ws-1");
expect(config.enabled).toBe(true);
expect(config.intervalMinutes).toBe(5);
// The missing field defaults ON (feature on by default) — never `undefined`.
expect(config.inactiveOnly).toBe(true);
});
it("lists persisted workspace ids", async () => {
const store = createHeartbeatConfigStore(createMemoryStorage());
await store.update("ws-b", { enabled: true });
await store.update("ws-a", { enabled: false });
expect(await store.listWorkspaceIds()).toEqual(["ws-a", "ws-b"]);
});
});
|