


























@@ -1,10 +1,18 @@
1-import fs from "node:fs";
21import path from "node:path";
2+import {
3+ackJsonDurableQueueEntry,
4+ensureJsonDurableQueueDirs,
5+loadJsonDurableQueueEntry,
6+loadPendingJsonDurableQueueEntries,
7+moveJsonDurableQueueEntryToFailed,
8+readJsonDurableQueueEntry,
9+resolveJsonDurableQueueEntryPaths,
10+writeJsonDurableQueueEntry,
11+} from "@openclaw/fs-safe/store";
312import type { ReplyPayload } from "../../auto-reply/types.js";
413import type { RenderedMessageBatchPlanItem } from "../../channels/message/types.js";
514import { resolveStateDir } from "../../config/paths.js";
615import type { ReplyToMode } from "../../config/types.js";
7-import { replaceFileAtomic } from "../replace-file.js";
816import { generateSecureUuid } from "../secure-random.js";
917import type { OutboundDeliveryFormattingOptions } from "./formatting.js";
1018import type { OutboundIdentity } from "./identity.js";
@@ -14,6 +22,7 @@ import type { OutboundChannel } from "./targets.js";
14221523const QUEUE_DIRNAME = "delivery-queue";
1624const FAILED_DIRNAME = "failed";
25+const QUEUE_TEMP_PREFIX = ".delivery-queue";
17261827export type QueuedRenderedMessageBatchPlan = {
1928payloadCount: number;
@@ -80,38 +89,19 @@ function resolveQueueEntryPaths(
8089jsonPath: string;
8190deliveredPath: string;
8291} {
83-const queueDir = resolveQueueDir(stateDir);
84-return {
85-jsonPath: path.join(queueDir, `${id}.json`),
86-deliveredPath: path.join(queueDir, `${id}.delivered`),
87-};
88-}
89-90-function getErrnoCode(err: unknown): string | null {
91-return err && typeof err === "object" && "code" in err
92- ? String((err as { code?: unknown }).code)
93- : null;
94-}
95-96-async function unlinkBestEffort(filePath: string): Promise<void> {
97-try {
98-await fs.promises.unlink(filePath);
99-} catch {
100-// Best-effort cleanup.
101-}
92+return resolveJsonDurableQueueEntryPaths(resolveQueueDir(stateDir), id);
10293}
1039410495async function writeQueueEntry(filePath: string, entry: QueuedDelivery): Promise<void> {
105-await replaceFileAtomic({
96+await writeJsonDurableQueueEntry({
10697 filePath,
107-content: JSON.stringify(entry, null, 2),
108-mode: 0o600,
109-tempPrefix: ".delivery-queue",
98+ entry,
99+tempPrefix: QUEUE_TEMP_PREFIX,
110100});
111101}
112102113103async function readQueueEntry(filePath: string): Promise<QueuedDelivery> {
114-return JSON.parse(await fs.promises.readFile(filePath, "utf-8")) as QueuedDelivery;
104+return await readJsonDurableQueueEntry<QueuedDelivery>(filePath);
115105}
116106117107function normalizeLegacyQueuedDeliveryEntry(entry: QueuedDelivery): {
@@ -144,8 +134,10 @@ function normalizeLegacyQueuedDeliveryEntry(entry: QueuedDelivery): {
144134/** Ensure the queue directory (and failed/ subdirectory) exist. */
145135export async function ensureQueueDir(stateDir?: string): Promise<string> {
146136const queueDir = resolveQueueDir(stateDir);
147-await fs.promises.mkdir(queueDir, { recursive: true, mode: 0o700 });
148-await fs.promises.mkdir(resolveFailedDir(stateDir), { recursive: true, mode: 0o700 });
137+await ensureJsonDurableQueueDirs({
138+ queueDir,
139+failedDir: resolveFailedDir(stateDir),
140+});
149141return queueDir;
150142}
151143@@ -191,22 +183,7 @@ export async function enqueueDelivery(
191183 * by {@link loadPendingDeliveries} on the next startup without re-sending.
192184 */
193185export async function ackDelivery(id: string, stateDir?: string): Promise<void> {
194-const { jsonPath, deliveredPath } = resolveQueueEntryPaths(id, stateDir);
195-try {
196-// Phase 1: atomic rename marks the delivery as complete.
197-await fs.promises.rename(jsonPath, deliveredPath);
198-} catch (err) {
199-const code = getErrnoCode(err);
200-if (code === "ENOENT") {
201-// .json already gone — may have been renamed by a previous ack attempt.
202-// Try to clean up a leftover .delivered marker if present.
203-await unlinkBestEffort(deliveredPath);
204-return;
205-}
206-throw err;
207-}
208-// Phase 2: remove the marker file.
209-await unlinkBestEffort(deliveredPath);
186+await ackJsonDurableQueueEntry(resolveQueueEntryPaths(id, stateDir));
210187}
211188212189/** Update a queue entry after a failed delivery attempt. */
@@ -246,76 +223,28 @@ export async function loadPendingDelivery(
246223id: string,
247224stateDir?: string,
248225): Promise<QueuedDelivery | null> {
249-const { jsonPath } = resolveQueueEntryPaths(id, stateDir);
250-try {
251-const stat = await fs.promises.stat(jsonPath);
252-if (!stat.isFile()) {
253-return null;
254-}
255-const { entry, migrated } = normalizeLegacyQueuedDeliveryEntry(await readQueueEntry(jsonPath));
256-if (migrated) {
257-await writeQueueEntry(jsonPath, entry);
258-}
259-return entry;
260-} catch (err) {
261-if (getErrnoCode(err) === "ENOENT") {
262-return null;
263-}
264-throw err;
265-}
226+return await loadJsonDurableQueueEntry({
227+paths: resolveQueueEntryPaths(id, stateDir),
228+tempPrefix: QUEUE_TEMP_PREFIX,
229+read: async (entry) => normalizeLegacyQueuedDeliveryEntry(entry),
230+});
266231}
267232268233/** Load all pending delivery entries from the queue directory. */
269234export async function loadPendingDeliveries(stateDir?: string): Promise<QueuedDelivery[]> {
270235const queueDir = resolveQueueDir(stateDir);
271-let files: string[];
272-try {
273-files = await fs.promises.readdir(queueDir);
274-} catch (err) {
275-const code = getErrnoCode(err);
276-if (code === "ENOENT") {
277-return [];
278-}
279-throw err;
280-}
281-282-// Clean up .delivered markers left by ackDelivery if the process crashed
283-// between the rename and the unlink.
284-for (const file of files) {
285-if (file.endsWith(".delivered")) {
286-await unlinkBestEffort(path.join(queueDir, file));
287-}
288-}
289-290-const entries: QueuedDelivery[] = [];
291-for (const file of files) {
292-if (!file.endsWith(".json")) {
293-continue;
294-}
295-const filePath = path.join(queueDir, file);
296-try {
297-const stat = await fs.promises.stat(filePath);
298-if (!stat.isFile()) {
299-continue;
300-}
301-const { entry, migrated } = normalizeLegacyQueuedDeliveryEntry(
302-await readQueueEntry(filePath),
303-);
304-if (migrated) {
305-await writeQueueEntry(filePath, entry);
306-}
307-entries.push(entry);
308-} catch {
309-// Skip malformed or inaccessible entries.
310-}
311-}
312-return entries;
236+return await loadPendingJsonDurableQueueEntries({
237+ queueDir,
238+tempPrefix: QUEUE_TEMP_PREFIX,
239+read: async (entry) => normalizeLegacyQueuedDeliveryEntry(entry),
240+});
313241}
314242315243/** Move a queue entry to the failed/ subdirectory. */
316244export async function moveToFailed(id: string, stateDir?: string): Promise<void> {
317-const queueDir = resolveQueueDir(stateDir);
318-const failedDir = resolveFailedDir(stateDir);
319-await fs.promises.mkdir(failedDir, { recursive: true, mode: 0o700 });
320-await fs.promises.rename(path.join(queueDir, `${id}.json`), path.join(failedDir, `${id}.json`));
245+await moveJsonDurableQueueEntryToFailed({
246+queueDir: resolveQueueDir(stateDir),
247+failedDir: resolveFailedDir(stateDir),
248+ id,
249+});
321250}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。