herdr-plugin-amq 0.1.9 → 0.1.10

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/protocol.mjs +48 -16
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "herdr-plugin-amq",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "Herdr plugin for AMQ (Agent Message Queue) autonomous bridge, status monitoring, and AGmail dashboard",
5
5
  "type": "module",
6
6
  "main": "src/index.mjs",
package/src/protocol.mjs CHANGED
@@ -83,17 +83,48 @@ function openNoFollow(filePath, flags, mode) {
83
83
  return fs.openSync(filePath, flags | noFollow, mode);
84
84
  }
85
85
 
86
- function descriptorPath(fd) {
86
+ function isDirectoryHandle(handle) {
87
+ return Boolean(handle && typeof handle === "object");
88
+ }
89
+
90
+ function closeDirectory(handle) {
91
+ if (!isDirectoryHandle(handle)) fs.closeSync(handle);
92
+ }
93
+
94
+ function descriptorPath(handle) {
95
+ if (isDirectoryHandle(handle)) return handle.path;
87
96
  const base = process.platform === "darwin" ? "/dev/fd" : "/proc/self/fd";
88
- return path.join(base, String(fd));
97
+ return path.join(base, String(handle));
89
98
  }
90
99
 
91
- function openRelativeNoFollow(dirFd, name, flags, mode) {
100
+ function openRelativeNoFollow(dirHandle, name, flags, mode) {
92
101
  if (!name || path.basename(name) !== name) throw new Error("Invalid relative Maildir name");
93
- return openNoFollow(path.join(descriptorPath(dirFd), name), flags, mode);
102
+ return openNoFollow(path.join(descriptorPath(dirHandle), name), flags, mode);
103
+ }
104
+
105
+ function resolveDarwinDirectory(dirPath) {
106
+ const resolved = path.resolve(dirPath);
107
+ const parsed = path.parse(resolved);
108
+ const segments = resolved.slice(parsed.root.length).split(path.sep).filter(Boolean);
109
+ const systemSymlinkRoots = new Set(["/private", "/tmp", "/var"]);
110
+ let current = parsed.root;
111
+ for (const segment of segments) {
112
+ current = path.join(current, segment);
113
+ const stat = fs.lstatSync(current);
114
+ if (stat.isSymbolicLink()) {
115
+ if (!systemSymlinkRoots.has(current)) throw new Error("Symlinked directory is not allowed");
116
+ current = fs.realpathSync(current);
117
+ continue;
118
+ }
119
+ if (!stat.isDirectory()) throw new Error("Maildir path is not a directory");
120
+ }
121
+ const realPath = fs.realpathSync(current);
122
+ if (!fs.statSync(realPath).isDirectory()) throw new Error("Maildir path is not a directory");
123
+ return realPath;
94
124
  }
95
125
 
96
126
  function openDirectorySecure(dirPath) {
127
+ if (process.platform === "darwin") return { path: resolveDarwinDirectory(dirPath) };
97
128
  const resolved = path.resolve(dirPath);
98
129
  const parsed = path.parse(resolved);
99
130
  const segments = resolved.slice(parsed.root.length).split(path.sep).filter(Boolean);
@@ -102,12 +133,12 @@ function openDirectorySecure(dirPath) {
102
133
  try {
103
134
  for (const segment of segments) {
104
135
  const next = openRelativeNoFollow(current, segment, directoryFlag);
105
- fs.closeSync(current);
136
+ closeDirectory(current);
106
137
  current = next;
107
138
  }
108
139
  return current;
109
140
  } catch (error) {
110
- fs.closeSync(current);
141
+ closeDirectory(current);
111
142
  throw error;
112
143
  }
113
144
  }
@@ -145,7 +176,7 @@ export function writeBoundedFileAtomic(filePath, content, maxBytes = MAX_MAILDIR
145
176
  try {
146
177
  writeFileAtomicBetweenDirectories(path.basename(filePath), dirFd, dirFd, content, maxBytes);
147
178
  } finally {
148
- fs.closeSync(dirFd);
179
+ closeDirectory(dirFd);
149
180
  }
150
181
  }
151
182
 
@@ -154,7 +185,7 @@ export function listMaildirMessageFiles(dirPath) {
154
185
  try {
155
186
  return fs.readdirSync(descriptorPath(dirFd));
156
187
  } finally {
157
- fs.closeSync(dirFd);
188
+ closeDirectory(dirFd);
158
189
  }
159
190
  }
160
191
 
@@ -176,8 +207,8 @@ export function moveMaildirMessage(amqRoot, handle, fromFolder, toFolder, fileNa
176
207
  );
177
208
  return path.join(toDir, fileName);
178
209
  } finally {
179
- fs.closeSync(fromFd);
180
- if (toFd !== undefined) fs.closeSync(toFd);
210
+ closeDirectory(fromFd);
211
+ if (toFd !== undefined) closeDirectory(toFd);
181
212
  }
182
213
  }
183
214
 
@@ -206,7 +237,7 @@ export function readMaildirMessageFile(filePath, maxBytes = MAX_MAILDIR_FILE_BYT
206
237
  return Buffer.concat(chunks, total).toString("utf8");
207
238
  } finally {
208
239
  if (fd !== undefined) fs.closeSync(fd);
209
- fs.closeSync(dirFd);
240
+ closeDirectory(dirFd);
210
241
  }
211
242
  }
212
243
 
@@ -410,8 +441,8 @@ export function sendMaildirMessage(amqRoot, options = {}) {
410
441
  newFd = openDirectorySecure(path.join(recipientDir, "inbox", "new"));
411
442
  writeFileAtomicBetweenDirectories(`${msgId}.md`, tmpFd, newFd, fileText);
412
443
  } finally {
413
- fs.closeSync(tmpFd);
414
- if (newFd !== undefined) fs.closeSync(newFd);
444
+ closeDirectory(tmpFd);
445
+ if (newFd !== undefined) closeDirectory(newFd);
415
446
  }
416
447
  }
417
448
 
@@ -420,7 +451,7 @@ export function sendMaildirMessage(amqRoot, options = {}) {
420
451
  try {
421
452
  writeFileAtomicBetweenDirectories(`${msgId}.md`, sentFd, sentFd, fileText);
422
453
  } finally {
423
- fs.closeSync(sentFd);
454
+ closeDirectory(sentFd);
424
455
  }
425
456
 
426
457
  return {
@@ -449,8 +480,9 @@ export function markMaildirMessageRead(amqRoot, handle, msgId) {
449
480
  if (!agentDir) return { ok: false, error: "Mailbox not found" };
450
481
  const newPath = path.join(agentDir, "inbox", "new", `${msgId}.md`);
451
482
  const curPath = path.join(agentDir, "inbox", "cur", `${msgId}.md`);
483
+ const logicalCurPath = path.join(amqRoot, "agents", handle, "inbox", "cur", `${msgId}.md`);
452
484
  if (!fs.existsSync(newPath)) {
453
- if (fs.existsSync(curPath)) return { ok: true, alreadyRead: true, id: msgId, filePath: curPath };
485
+ if (fs.existsSync(curPath)) return { ok: true, alreadyRead: true, id: msgId, filePath: logicalCurPath };
454
486
  return { ok: false, error: "Message not found" };
455
487
  }
456
488
 
@@ -464,7 +496,7 @@ export function markMaildirMessageRead(amqRoot, handle, msgId) {
464
496
 
465
497
  const filePath = moveMaildirMessage(amqRoot, handle, "new", "cur", `${msgId}.md`);
466
498
  if (!filePath) return { ok: false, error: "Message could not be marked read" };
467
- return { ok: true, alreadyRead: false, id: msgId, filePath };
499
+ return { ok: true, alreadyRead: false, id: msgId, filePath: logicalCurPath };
468
500
  } catch (error) {
469
501
  return { ok: false, error: error.message };
470
502
  }