diff options
| author | Adam Malczewski <[email protected]> | 2026-06-01 09:53:12 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-01 09:53:12 +0900 |
| commit | 1c800eb35b48b28a82a64e8b2b3ce666323f2c42 (patch) | |
| tree | c26044a38f0099fbbe98680a02b73bc2417f1eb0 /packages/core/src | |
| parent | c351719ec81a1b87565abc0656756f4aa9e473d0 (diff) | |
| download | dispatch-1c800eb35b48b28a82a64e8b2b3ce666323f2c42.tar.gz dispatch-1c800eb35b48b28a82a64e8b2b3ce666323f2c42.zip | |
feat(wake): probe 4 times per marked hour (:00 :15 :30 :45), coalesce same-tick fires
Marking an hour on the Claude Wake Schedule panel now schedules FOUR probes
within that hour instead of one. Rate-window edges are unforgiving — a
single probe at :15 can miss the actual reset moment by up to 14 minutes;
hitting :00 / :15 / :30 / :45 puts us within ~7 minutes of any reset that
happens during that hour.
When multiple slots come due in the same 30s scheduler tick (or recover
together at boot), they coalesce into a SINGLE upstream wake call — no
point hitting Anthropic 4× in the same window.
DB schema
- wake_schedule is now (hour, slot_minute, next_wake_at) PK (hour,
slot_minute). Destructive migration: detect old single-row-per-hour
schema by absence of the slot_minute column and DROP TABLE. No other
table is touched. Per user direction: no back-compat for old rows.
API
- POST /models/wake-schedule/toggle add: { hour, timestamps: { '0': ms,
'15': ms, '30': ms, '45': ms } } — all 4 slots required, all must be
future Unix ms. Delete shape unchanged ({ hour }).
- GET /models/wake-schedule shape:
schedule: { '9': { '0': ts, '15': ts, '30': ts, '45': ts }, ... }
probeSlotMinutes: [0, 15, 30, 45]
resetOffsetHours, lastWake, pendingRetry (unchanged from prior commit)
Frontend
- Computes 4 timestamps client-side (next occurrence of HH:MM in local TZ)
and sends them in one request.
- markedHours summary now says 'Probes :00 :15 :30 :45 → reset by ~Xh later'.
- Same in-flight tracking / current-hour ring / status row as before.
Tests
- wake-scheduler.test.ts unchanged (pure helpers still correct; added
PROBE_SLOT_MINUTES + isProbeSlotMinute exports).
- routes.test.ts rewritten for the new payload shape: 12 wake-schedule
tests covering snapshot shape, add/remove (full 4-slot round-trip),
validation (range, integer, past-slot, missing slot, non-object,
missing timestamps), independent multi-hour scheduling, and
re-toggle replacement. 417 tests total (was 414).
Diffstat (limited to 'packages/core/src')
| -rw-r--r-- | packages/core/src/db/index.ts | 23 |
1 files changed, 21 insertions, 2 deletions
diff --git a/packages/core/src/db/index.ts b/packages/core/src/db/index.ts index 29448bc..93ec1f9 100644 --- a/packages/core/src/db/index.ts +++ b/packages/core/src/db/index.ts @@ -54,9 +54,28 @@ export function getDatabase(): Database { updated_at INTEGER NOT NULL )`); + // Wake schedule: 4 rows per marked hour (one per :00 / :15 / :30 / :45 probe + // slot). The PK is (hour, slot_minute). Destructive migration off the legacy + // single-row-per-hour schema: detect by absence of the `slot_minute` column + // and drop the old table. Other tables (credentials, api_keys, usage_cache, + // settings, tabs, chunks) are NOT touched. + const legacyWakeSchema = (() => { + try { + const cols = _db.query("PRAGMA table_info(wake_schedule)").all() as Array<{ name: string }>; + if (cols.length === 0) return false; // table doesn't exist yet + return !cols.some((c) => c.name === "slot_minute"); + } catch { + return false; + } + })(); + if (legacyWakeSchema) { + _db.run("DROP TABLE IF EXISTS wake_schedule"); + } _db.run(`CREATE TABLE IF NOT EXISTS wake_schedule ( - hour INTEGER PRIMARY KEY CHECK (hour BETWEEN 0 AND 23), - next_wake_at INTEGER NOT NULL + hour INTEGER NOT NULL CHECK (hour BETWEEN 0 AND 23), + slot_minute INTEGER NOT NULL CHECK (slot_minute IN (0, 15, 30, 45)), + next_wake_at INTEGER NOT NULL, + PRIMARY KEY (hour, slot_minute) )`); _db.run(`CREATE TABLE IF NOT EXISTS usage_cache ( |
