mercury-agent 0.18.1 → 0.18.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.
@@ -26,6 +26,7 @@ import {
26
26
  newId,
27
27
  readLines,
28
28
  } from "./manifest.js";
29
+ import { readPending } from "./queue.js";
29
30
 
30
31
  export interface GcLog {
31
32
  info: (msg: string, meta?: unknown) => void;
@@ -172,6 +173,22 @@ export async function reconcileSpace(
172
173
  live.map((l) => l.sha256).filter((h): h is string => typeof h === "string"),
173
174
  );
174
175
 
176
+ // Spooled records count as wanted even though no manifest line names them
177
+ // yet. `drainSpace` uploads the bytes first and appends the line after, so
178
+ // between those two awaits the hash is on the backend and absent from the
179
+ // manifest — indistinguishable, from here, from an orphan. Deleting then
180
+ // leaves a live line whose bytes are gone, which is the one state this
181
+ // module can report and never repair. The window is a whole `put` (a Drive
182
+ // upload), and both jobs fire together on every startup: `jobs.ts` runs each
183
+ // interval job immediately, so drain and reconcile overlap exactly when the
184
+ // spool is non-empty — after a crash or a backend outage.
185
+ //
186
+ // This also covers a record backing off after a crash between `put` and the
187
+ // append: its bytes must survive until the retry records them.
188
+ for (const record of readPending(dataDir, spaceId)) {
189
+ wanted.add(record.sha256);
190
+ }
191
+
175
192
  let stored: Array<{ sha256: string; ref?: string }>;
176
193
  try {
177
194
  stored = await backend.list(spaceId);
@@ -190,6 +207,11 @@ export async function reconcileSpace(
190
207
 
191
208
  for (const object of stored) {
192
209
  if (wanted.has(object.sha256)) continue;
210
+ // Claim the hash like `collectHash` does, so a drain that resolves its
211
+ // `put` while this sweep runs observes the same protocol rather than
212
+ // racing a bare delete. The set above already spares spooled hashes; this
213
+ // closes the record that appears mid-listing.
214
+ if (!beginDelete(spaceId, object.sha256)) continue;
193
215
  try {
194
216
  await backend.delete(spaceId, object.sha256, object.ref);
195
217
  result.orphansDeleted++;
@@ -199,6 +221,8 @@ export async function reconcileSpace(
199
221
  err instanceof Error ? err.message : String(err)
200
222
  }`,
201
223
  );
224
+ } finally {
225
+ endDelete(spaceId, object.sha256);
202
226
  }
203
227
  }
204
228
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mercury-agent",
3
- "version": "0.18.1",
3
+ "version": "0.18.2",
4
4
  "description": "Personal AI assistant for chat platforms (WhatsApp, Slack, Discord, Telegram)",
5
5
  "license": "MIT",
6
6
  "author": "Avishai Tsabari",