context-mode 1.0.104 → 1.0.105

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.
@@ -87,6 +87,7 @@ const S = {
87
87
  upsertResume: "upsertResume",
88
88
  getResume: "getResume",
89
89
  markResumeConsumed: "markResumeConsumed",
90
+ claimLatestUnconsumedResume: "claimLatestUnconsumedResume",
90
91
  deleteEvents: "deleteEvents",
91
92
  deleteMeta: "deleteMeta",
92
93
  deleteResume: "deleteResume",
@@ -251,6 +252,19 @@ export class SessionDB extends SQLiteBase {
251
252
  consumed = 0`);
252
253
  p(S.getResume, `SELECT snapshot, event_count, consumed FROM session_resume WHERE session_id = ?`);
253
254
  p(S.markResumeConsumed, `UPDATE session_resume SET consumed = 1 WHERE session_id = ?`);
255
+ // Atomic "pick newest unconsumed snapshot AND mark it consumed in one
256
+ // statement". Required for race-safe cross-session resume injection
257
+ // (Mickey / PR #376) — two parallel chat-turn hooks must not both read
258
+ // the same row before either one writes consumed=1.
259
+ p(S.claimLatestUnconsumedResume, `UPDATE session_resume
260
+ SET consumed = 1
261
+ WHERE id = (
262
+ SELECT id FROM session_resume
263
+ WHERE consumed = 0
264
+ ORDER BY created_at DESC, id DESC
265
+ LIMIT 1
266
+ )
267
+ RETURNING session_id, snapshot`);
254
268
  // ── Delete ──
255
269
  p(S.deleteEvents, `DELETE FROM session_events WHERE session_id = ?`);
256
270
  p(S.deleteMeta, `DELETE FROM session_meta WHERE session_id = ?`);
@@ -478,6 +492,22 @@ export class SessionDB extends SQLiteBase {
478
492
  markResumeConsumed(sessionId) {
479
493
  this.stmt(S.markResumeConsumed).run(sessionId);
480
494
  }
495
+ /**
496
+ * Atomically claim the most recent unconsumed resume snapshot in this DB.
497
+ *
498
+ * `SessionDB` is sharded per project (see `getSessionDBPath` — SHA-256 of
499
+ * project dir), so "this DB" already implies "this project". The atomic
500
+ * `UPDATE … RETURNING` ensures concurrent processes for the same project
501
+ * cannot both inject the same snapshot (Mickey / PR #376 race).
502
+ *
503
+ * Returns null when no unconsumed snapshot exists.
504
+ */
505
+ claimLatestUnconsumedResume() {
506
+ const row = this.stmt(S.claimLatestUnconsumedResume).get();
507
+ if (!row)
508
+ return null;
509
+ return { sessionId: row.session_id, snapshot: row.snapshot };
510
+ }
481
511
  /**
482
512
  * Return the most recent session_id from session_meta, or null if none.
483
513
  * Used by the runtime to attach persistent counters to the right session