@zaclinley/dsh-mailharbor 0.20.0 → 0.21.0
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.
- package/CHANGELOG.md +14 -0
- package/README.zh.md +17 -17
- package/lib/client.js +154 -7
- package/lib/client.js.map +2 -2
- package/lib/index.js +209 -37
- package/lib/index.js.map +4 -4
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -989,7 +989,8 @@ async function openPath(path) {
|
|
|
989
989
|
}
|
|
990
990
|
|
|
991
991
|
// src/host/api.ts
|
|
992
|
-
import { existsSync as existsSync6, rmSync } from "node:fs";
|
|
992
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, rmSync, writeFileSync as writeFileSync5 } from "node:fs";
|
|
993
|
+
import { basename, join as join6 } from "node:path";
|
|
993
994
|
|
|
994
995
|
// src/host/mail-write.ts
|
|
995
996
|
import { DatabaseSync } from "node:sqlite";
|
|
@@ -3123,7 +3124,7 @@ ${conversation.trim()}
|
|
|
3123
3124
|
}
|
|
3124
3125
|
|
|
3125
3126
|
// src/host/version.ts
|
|
3126
|
-
var VERSION = "0.
|
|
3127
|
+
var VERSION = "0.21.0";
|
|
3127
3128
|
|
|
3128
3129
|
// src/host/api.ts
|
|
3129
3130
|
init_smtp();
|
|
@@ -3277,6 +3278,13 @@ function buildRouter(runtime) {
|
|
|
3277
3278
|
try {
|
|
3278
3279
|
await runtime.provider(account).setFlag(detail.mailbox, detail.uid, "\\Flagged", starred);
|
|
3279
3280
|
} catch {
|
|
3281
|
+
runtime.actionQueue(account).enqueue({
|
|
3282
|
+
type: "flag",
|
|
3283
|
+
mailbox: detail.mailbox,
|
|
3284
|
+
uid: detail.uid,
|
|
3285
|
+
flag: "\\Flagged",
|
|
3286
|
+
value: starred
|
|
3287
|
+
});
|
|
3280
3288
|
}
|
|
3281
3289
|
}
|
|
3282
3290
|
return { starred };
|
|
@@ -3292,6 +3300,12 @@ function buildRouter(runtime) {
|
|
|
3292
3300
|
try {
|
|
3293
3301
|
await runtime.provider(account).move(detail.mailbox, detail.uid, target);
|
|
3294
3302
|
} catch {
|
|
3303
|
+
runtime.actionQueue(account).enqueue({
|
|
3304
|
+
type: "move",
|
|
3305
|
+
mailbox: detail.mailbox,
|
|
3306
|
+
uid: detail.uid,
|
|
3307
|
+
target
|
|
3308
|
+
});
|
|
3295
3309
|
}
|
|
3296
3310
|
}
|
|
3297
3311
|
moveLocalMessage(runtime.paths(account).mailDb, Number(params.id), target);
|
|
@@ -3317,6 +3331,13 @@ function buildRouter(runtime) {
|
|
|
3317
3331
|
try {
|
|
3318
3332
|
await runtime.provider(account).setSeen(detail.mailbox, detail.uid, action === "read");
|
|
3319
3333
|
} catch {
|
|
3334
|
+
runtime.actionQueue(account).enqueue({
|
|
3335
|
+
type: "flag",
|
|
3336
|
+
mailbox: detail.mailbox,
|
|
3337
|
+
uid: detail.uid,
|
|
3338
|
+
flag: "\\Seen",
|
|
3339
|
+
value: action === "read"
|
|
3340
|
+
});
|
|
3320
3341
|
}
|
|
3321
3342
|
}
|
|
3322
3343
|
count += 1;
|
|
@@ -3327,6 +3348,12 @@ function buildRouter(runtime) {
|
|
|
3327
3348
|
try {
|
|
3328
3349
|
await runtime.provider(account).move(detail.mailbox, detail.uid, target);
|
|
3329
3350
|
} catch {
|
|
3351
|
+
runtime.actionQueue(account).enqueue({
|
|
3352
|
+
type: "move",
|
|
3353
|
+
mailbox: detail.mailbox,
|
|
3354
|
+
uid: detail.uid,
|
|
3355
|
+
target
|
|
3356
|
+
});
|
|
3330
3357
|
}
|
|
3331
3358
|
}
|
|
3332
3359
|
moveLocalMessage(runtime.paths(account).mailDb, Number(detail.id), target);
|
|
@@ -3338,6 +3365,13 @@ function buildRouter(runtime) {
|
|
|
3338
3365
|
try {
|
|
3339
3366
|
await runtime.provider(account).setFlag(detail.mailbox, detail.uid, "\\Flagged", starred);
|
|
3340
3367
|
} catch {
|
|
3368
|
+
runtime.actionQueue(account).enqueue({
|
|
3369
|
+
type: "flag",
|
|
3370
|
+
mailbox: detail.mailbox,
|
|
3371
|
+
uid: detail.uid,
|
|
3372
|
+
flag: "\\Flagged",
|
|
3373
|
+
value: starred
|
|
3374
|
+
});
|
|
3341
3375
|
}
|
|
3342
3376
|
}
|
|
3343
3377
|
count += 1;
|
|
@@ -3959,7 +3993,49 @@ function buildRouter(runtime) {
|
|
|
3959
3993
|
throw aiHttpError(error);
|
|
3960
3994
|
}
|
|
3961
3995
|
});
|
|
3962
|
-
router.
|
|
3996
|
+
router.post("/projects/:id/attachment", async ({ params, body }) => {
|
|
3997
|
+
const payload = await body();
|
|
3998
|
+
const account = accountFromPayload(runtime, payload);
|
|
3999
|
+
const project = runtime.projects().get(String(params.id ?? ""));
|
|
4000
|
+
if (project === void 0) throw new HttpError(404, "\u9879\u76EE\u4E0D\u5B58\u5728");
|
|
4001
|
+
const emailId = Number(payload.emailId ?? 0);
|
|
4002
|
+
const attachmentId = Number(payload.attachmentId ?? 0);
|
|
4003
|
+
const file = runtime.database(account).attachment(emailId, attachmentId);
|
|
4004
|
+
if (file === void 0) throw new HttpError(404, "\u9644\u4EF6\u4E0D\u5B58\u5728");
|
|
4005
|
+
const dir = join6(project.path, "attachments");
|
|
4006
|
+
mkdirSync5(dir, { recursive: true });
|
|
4007
|
+
const safe = basename(file.filename).replace(/[/\\:*?"<>|]/g, "_") || "attachment";
|
|
4008
|
+
let target = join6(dir, safe);
|
|
4009
|
+
let index = 1;
|
|
4010
|
+
while (existsSync6(target)) {
|
|
4011
|
+
const dot = safe.lastIndexOf(".");
|
|
4012
|
+
const stem = dot > 0 ? safe.slice(0, dot) : safe;
|
|
4013
|
+
const ext = dot > 0 ? safe.slice(dot) : "";
|
|
4014
|
+
target = join6(dir, `${stem}-${String(index)}${ext}`);
|
|
4015
|
+
index += 1;
|
|
4016
|
+
}
|
|
4017
|
+
writeFileSync5(target, Buffer.from(file.data));
|
|
4018
|
+
return { ok: true, path: target, filename: basename(target) };
|
|
4019
|
+
});
|
|
4020
|
+
router.get("/projects", () => {
|
|
4021
|
+
const counts = /* @__PURE__ */ new Map();
|
|
4022
|
+
for (const account of runtime.current.accounts) {
|
|
4023
|
+
for (const task of runtime.tasks().list({ accountId: account.id })) {
|
|
4024
|
+
const projectId = task.projectId ?? "";
|
|
4025
|
+
if (projectId === "") continue;
|
|
4026
|
+
const entry = counts.get(projectId) ?? { total: 0, done: 0 };
|
|
4027
|
+
entry.total += 1;
|
|
4028
|
+
if (task.status === "COMPLETED") entry.done += 1;
|
|
4029
|
+
counts.set(projectId, entry);
|
|
4030
|
+
}
|
|
4031
|
+
}
|
|
4032
|
+
return {
|
|
4033
|
+
items: runtime.projects().list().map((project) => {
|
|
4034
|
+
const stat = counts.get(project.id);
|
|
4035
|
+
return { ...projectView(project), taskTotal: stat?.total ?? 0, taskDone: stat?.done ?? 0 };
|
|
4036
|
+
})
|
|
4037
|
+
};
|
|
4038
|
+
});
|
|
3963
4039
|
router.get("/projects/for", ({ url }) => {
|
|
3964
4040
|
const account = accountFromPayload(runtime, { accountId: url.searchParams.get("accountId") ?? void 0 });
|
|
3965
4041
|
const key = url.searchParams.get("key") ?? "";
|
|
@@ -4472,7 +4548,7 @@ ${signature}`;
|
|
|
4472
4548
|
|
|
4473
4549
|
// src/host/runtime.ts
|
|
4474
4550
|
import { existsSync as existsSync8, statSync } from "node:fs";
|
|
4475
|
-
import { join as
|
|
4551
|
+
import { join as join8 } from "node:path";
|
|
4476
4552
|
|
|
4477
4553
|
// src/host/secrets.ts
|
|
4478
4554
|
import { spawnSync } from "node:child_process";
|
|
@@ -4662,7 +4738,7 @@ var CredentialManager = class {
|
|
|
4662
4738
|
};
|
|
4663
4739
|
|
|
4664
4740
|
// src/host/ai-store.ts
|
|
4665
|
-
import { existsSync as existsSync7, mkdirSync as
|
|
4741
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync5, renameSync as renameSync4, writeFileSync as writeFileSync6 } from "node:fs";
|
|
4666
4742
|
import { dirname as dirname2 } from "node:path";
|
|
4667
4743
|
var MAX_THREADS = 50;
|
|
4668
4744
|
var MAX_CARDS_PER_THREAD = 50;
|
|
@@ -4690,9 +4766,9 @@ var AiThreadStore = class {
|
|
|
4690
4766
|
prune(data);
|
|
4691
4767
|
const text2 = `${JSON.stringify(data, null, 2)}
|
|
4692
4768
|
`;
|
|
4693
|
-
|
|
4769
|
+
mkdirSync6(dirname2(this.file), { recursive: true });
|
|
4694
4770
|
const temp = `${this.file}.${String(process.pid)}.tmp`;
|
|
4695
|
-
|
|
4771
|
+
writeFileSync6(temp, text2, { mode: 384 });
|
|
4696
4772
|
renameSync4(temp, this.file);
|
|
4697
4773
|
}
|
|
4698
4774
|
get(key) {
|
|
@@ -4802,7 +4878,7 @@ function prune(data) {
|
|
|
4802
4878
|
}
|
|
4803
4879
|
|
|
4804
4880
|
// src/host/mail-flags.ts
|
|
4805
|
-
import { mkdirSync as
|
|
4881
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync6, renameSync as renameSync5, writeFileSync as writeFileSync7 } from "node:fs";
|
|
4806
4882
|
import { dirname as dirname3 } from "node:path";
|
|
4807
4883
|
var MailFlagsStore = class {
|
|
4808
4884
|
file;
|
|
@@ -4822,9 +4898,9 @@ var MailFlagsStore = class {
|
|
|
4822
4898
|
return this.keys;
|
|
4823
4899
|
}
|
|
4824
4900
|
flush() {
|
|
4825
|
-
|
|
4901
|
+
mkdirSync7(dirname3(this.file), { recursive: true });
|
|
4826
4902
|
const temp = `${this.file}.${String(process.pid)}.tmp`;
|
|
4827
|
-
|
|
4903
|
+
writeFileSync7(temp, `${JSON.stringify({ starred: [...this.load()] }, null, 2)}
|
|
4828
4904
|
`, {
|
|
4829
4905
|
encoding: "utf8",
|
|
4830
4906
|
mode: 384
|
|
@@ -4853,10 +4929,87 @@ var MailFlagsStore = class {
|
|
|
4853
4929
|
}
|
|
4854
4930
|
};
|
|
4855
4931
|
|
|
4856
|
-
// src/host/
|
|
4932
|
+
// src/host/action-queue.ts
|
|
4857
4933
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
4858
|
-
import { mkdirSync as
|
|
4934
|
+
import { mkdirSync as mkdirSync8, readFileSync as readFileSync7, renameSync as renameSync6, writeFileSync as writeFileSync8 } from "node:fs";
|
|
4859
4935
|
import { dirname as dirname4 } from "node:path";
|
|
4936
|
+
var ActionQueueStore = class {
|
|
4937
|
+
file;
|
|
4938
|
+
items = null;
|
|
4939
|
+
constructor(file) {
|
|
4940
|
+
this.file = file;
|
|
4941
|
+
}
|
|
4942
|
+
load() {
|
|
4943
|
+
if (this.items !== null) return this.items;
|
|
4944
|
+
try {
|
|
4945
|
+
const parsed = JSON.parse(readFileSync7(this.file, "utf8"));
|
|
4946
|
+
this.items = Array.isArray(parsed) ? parsed.filter((item) => typeof item?.id === "string") : [];
|
|
4947
|
+
} catch {
|
|
4948
|
+
this.items = [];
|
|
4949
|
+
}
|
|
4950
|
+
return this.items;
|
|
4951
|
+
}
|
|
4952
|
+
flush() {
|
|
4953
|
+
mkdirSync8(dirname4(this.file), { recursive: true });
|
|
4954
|
+
const temp = `${this.file}.${String(process.pid)}.tmp`;
|
|
4955
|
+
writeFileSync8(temp, `${JSON.stringify(this.load(), null, 2)}
|
|
4956
|
+
`, { encoding: "utf8", mode: 384 });
|
|
4957
|
+
renameSync6(temp, this.file);
|
|
4958
|
+
}
|
|
4959
|
+
list() {
|
|
4960
|
+
return [...this.load()];
|
|
4961
|
+
}
|
|
4962
|
+
enqueue(action) {
|
|
4963
|
+
const items = this.load();
|
|
4964
|
+
const filtered = items.filter(
|
|
4965
|
+
(item) => !(item.type === action.type && item.mailbox === action.mailbox && item.uid === action.uid && (action.type === "move" || item.flag === action.flag))
|
|
4966
|
+
);
|
|
4967
|
+
filtered.push({ ...action, id: randomUUID4(), createdAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
4968
|
+
this.items = filtered;
|
|
4969
|
+
this.flush();
|
|
4970
|
+
}
|
|
4971
|
+
remove(ids) {
|
|
4972
|
+
if (ids.length === 0) return;
|
|
4973
|
+
const set = new Set(ids);
|
|
4974
|
+
this.items = this.load().filter((item) => !set.has(item.id));
|
|
4975
|
+
this.flush();
|
|
4976
|
+
}
|
|
4977
|
+
/** Try every queued action; keep the ones the server still rejects. */
|
|
4978
|
+
async flushTo(executor) {
|
|
4979
|
+
const pending = this.list();
|
|
4980
|
+
const done = [];
|
|
4981
|
+
let failed = 0;
|
|
4982
|
+
for (const action of pending) {
|
|
4983
|
+
try {
|
|
4984
|
+
if (action.type === "flag") {
|
|
4985
|
+
await executor.setFlag(
|
|
4986
|
+
action.mailbox,
|
|
4987
|
+
action.uid,
|
|
4988
|
+
action.flag ?? "\\Flagged",
|
|
4989
|
+
action.value !== false
|
|
4990
|
+
);
|
|
4991
|
+
} else if (action.target !== void 0) {
|
|
4992
|
+
await executor.move(action.mailbox, action.uid, action.target);
|
|
4993
|
+
}
|
|
4994
|
+
done.push(action.id);
|
|
4995
|
+
} catch {
|
|
4996
|
+
failed += 1;
|
|
4997
|
+
const current = this.load().find((item) => item.id === action.id);
|
|
4998
|
+
if (current !== void 0) {
|
|
4999
|
+
current.attempts = (current.attempts ?? 0) + 1;
|
|
5000
|
+
if (current.attempts >= 5) done.push(action.id);
|
|
5001
|
+
}
|
|
5002
|
+
}
|
|
5003
|
+
}
|
|
5004
|
+
this.remove(done);
|
|
5005
|
+
return { sent: done.length, failed };
|
|
5006
|
+
}
|
|
5007
|
+
};
|
|
5008
|
+
|
|
5009
|
+
// src/host/tasks.ts
|
|
5010
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
5011
|
+
import { mkdirSync as mkdirSync9 } from "node:fs";
|
|
5012
|
+
import { dirname as dirname5 } from "node:path";
|
|
4860
5013
|
import { DatabaseSync as DatabaseSync5 } from "node:sqlite";
|
|
4861
5014
|
var STATUSES = /* @__PURE__ */ new Set(["NEEDS-ACTION", "IN-PROCESS", "COMPLETED", "CANCELLED"]);
|
|
4862
5015
|
function now() {
|
|
@@ -4905,7 +5058,7 @@ var TaskStore = class {
|
|
|
4905
5058
|
}
|
|
4906
5059
|
open() {
|
|
4907
5060
|
if (this.db !== null) return this.db;
|
|
4908
|
-
|
|
5061
|
+
mkdirSync9(dirname5(this.file), { recursive: true });
|
|
4909
5062
|
const db = new DatabaseSync5(this.file);
|
|
4910
5063
|
db.exec("PRAGMA journal_mode=WAL");
|
|
4911
5064
|
db.exec("PRAGMA busy_timeout=10000");
|
|
@@ -4982,7 +5135,7 @@ var TaskStore = class {
|
|
|
4982
5135
|
if (title === "") throw new Error("\u4EFB\u52A1\u6807\u9898\u4E0D\u80FD\u4E3A\u7A7A");
|
|
4983
5136
|
if (title.length > 300) throw new Error("\u4EFB\u52A1\u6807\u9898\u8FC7\u957F");
|
|
4984
5137
|
const status = statusOf(input.status);
|
|
4985
|
-
const id = `task_${
|
|
5138
|
+
const id = `task_${randomUUID5()}`;
|
|
4986
5139
|
const stamp3 = now();
|
|
4987
5140
|
this.open().prepare(
|
|
4988
5141
|
`INSERT INTO tasks (id, account_id, project_id, uid, title, notes, due, status, priority, owner,
|
|
@@ -4992,7 +5145,7 @@ var TaskStore = class {
|
|
|
4992
5145
|
id,
|
|
4993
5146
|
input.accountId,
|
|
4994
5147
|
input.projectId ?? null,
|
|
4995
|
-
`${
|
|
5148
|
+
`${randomUUID5()}@dsh-mailharbor`,
|
|
4996
5149
|
title,
|
|
4997
5150
|
String(input.notes ?? ""),
|
|
4998
5151
|
String(input.due ?? ""),
|
|
@@ -5088,7 +5241,7 @@ var TaskStore = class {
|
|
|
5088
5241
|
);
|
|
5089
5242
|
return this.get(String(existing.id));
|
|
5090
5243
|
}
|
|
5091
|
-
const id = `task_${
|
|
5244
|
+
const id = `task_${randomUUID5()}`;
|
|
5092
5245
|
const stamp3 = now();
|
|
5093
5246
|
db.prepare(
|
|
5094
5247
|
`INSERT INTO tasks (id, account_id, calendar_href, href, etag, uid, title, notes, due, status, priority,
|
|
@@ -5100,7 +5253,7 @@ var TaskStore = class {
|
|
|
5100
5253
|
input.calendarHref,
|
|
5101
5254
|
input.href,
|
|
5102
5255
|
input.etag,
|
|
5103
|
-
input.todo.uid || `${
|
|
5256
|
+
input.todo.uid || `${randomUUID5()}@dsh-mailharbor`,
|
|
5104
5257
|
String(input.todo.title || "(\u65E0\u6807\u9898)"),
|
|
5105
5258
|
String(input.todo.notes ?? ""),
|
|
5106
5259
|
String(input.todo.due ?? ""),
|
|
@@ -5144,9 +5297,9 @@ var TaskStore = class {
|
|
|
5144
5297
|
};
|
|
5145
5298
|
|
|
5146
5299
|
// src/host/drafts.ts
|
|
5147
|
-
import { randomUUID as
|
|
5148
|
-
import { mkdirSync as
|
|
5149
|
-
import { dirname as
|
|
5300
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
5301
|
+
import { mkdirSync as mkdirSync10, readFileSync as readFileSync8, renameSync as renameSync7, writeFileSync as writeFileSync9 } from "node:fs";
|
|
5302
|
+
import { dirname as dirname6, join as join7 } from "node:path";
|
|
5150
5303
|
function text(value) {
|
|
5151
5304
|
return value === void 0 || value === null ? "" : String(value);
|
|
5152
5305
|
}
|
|
@@ -5157,7 +5310,7 @@ function isDraft(value) {
|
|
|
5157
5310
|
return value !== null && typeof value === "object" && typeof value.id === "string";
|
|
5158
5311
|
}
|
|
5159
5312
|
function draftPath(root) {
|
|
5160
|
-
return
|
|
5313
|
+
return join7(root, "drafts.json");
|
|
5161
5314
|
}
|
|
5162
5315
|
var DraftStore = class {
|
|
5163
5316
|
file;
|
|
@@ -5170,18 +5323,18 @@ var DraftStore = class {
|
|
|
5170
5323
|
}
|
|
5171
5324
|
load() {
|
|
5172
5325
|
try {
|
|
5173
|
-
const parsed = JSON.parse(
|
|
5326
|
+
const parsed = JSON.parse(readFileSync8(this.file, "utf8"));
|
|
5174
5327
|
this.items = Array.isArray(parsed) ? parsed.filter(isDraft) : [];
|
|
5175
5328
|
} catch {
|
|
5176
5329
|
this.items = [];
|
|
5177
5330
|
}
|
|
5178
5331
|
}
|
|
5179
5332
|
flush() {
|
|
5180
|
-
|
|
5333
|
+
mkdirSync10(dirname6(this.file), { recursive: true });
|
|
5181
5334
|
const temp = `${this.file}.${String(process.pid)}.${String(Date.now())}.tmp`;
|
|
5182
|
-
|
|
5335
|
+
writeFileSync9(temp, `${JSON.stringify(this.items, null, 2)}
|
|
5183
5336
|
`, { encoding: "utf8", mode: 384 });
|
|
5184
|
-
|
|
5337
|
+
renameSync7(temp, this.file);
|
|
5185
5338
|
}
|
|
5186
5339
|
list() {
|
|
5187
5340
|
return [...this.items].sort((left, right) => right.updatedAt.localeCompare(left.updatedAt)).map((draft) => ({ ...draft }));
|
|
@@ -5215,7 +5368,7 @@ var DraftStore = class {
|
|
|
5215
5368
|
return { ...existing };
|
|
5216
5369
|
}
|
|
5217
5370
|
const draft = {
|
|
5218
|
-
id:
|
|
5371
|
+
id: randomUUID6(),
|
|
5219
5372
|
accountId: this.accountId,
|
|
5220
5373
|
...fields,
|
|
5221
5374
|
updatedAt: now2
|
|
@@ -5240,8 +5393,8 @@ var DraftStore = class {
|
|
|
5240
5393
|
init_imap();
|
|
5241
5394
|
|
|
5242
5395
|
// src/host/read-state.ts
|
|
5243
|
-
import { mkdirSync as
|
|
5244
|
-
import { dirname as
|
|
5396
|
+
import { mkdirSync as mkdirSync11, readFileSync as readFileSync9, renameSync as renameSync8, writeFileSync as writeFileSync10 } from "node:fs";
|
|
5397
|
+
import { dirname as dirname7 } from "node:path";
|
|
5245
5398
|
var ReadStateStore = class {
|
|
5246
5399
|
file;
|
|
5247
5400
|
keys = null;
|
|
@@ -5251,7 +5404,7 @@ var ReadStateStore = class {
|
|
|
5251
5404
|
load() {
|
|
5252
5405
|
if (this.keys !== null) return this.keys;
|
|
5253
5406
|
try {
|
|
5254
|
-
const parsed = JSON.parse(
|
|
5407
|
+
const parsed = JSON.parse(readFileSync9(this.file, "utf8"));
|
|
5255
5408
|
const list = Array.isArray(parsed) ? parsed : Array.isArray(parsed?.unread) ? parsed.unread : [];
|
|
5256
5409
|
this.keys = new Set(list.filter((key) => typeof key === "string" && key !== ""));
|
|
5257
5410
|
} catch {
|
|
@@ -5260,14 +5413,14 @@ var ReadStateStore = class {
|
|
|
5260
5413
|
return this.keys;
|
|
5261
5414
|
}
|
|
5262
5415
|
flush() {
|
|
5263
|
-
|
|
5416
|
+
mkdirSync11(dirname7(this.file), { recursive: true });
|
|
5264
5417
|
const temp = `${this.file}.${String(process.pid)}.tmp`;
|
|
5265
|
-
|
|
5418
|
+
writeFileSync10(temp, `${JSON.stringify({ unread: [...this.load()] }, null, 2)}
|
|
5266
5419
|
`, {
|
|
5267
5420
|
encoding: "utf8",
|
|
5268
5421
|
mode: 384
|
|
5269
5422
|
});
|
|
5270
|
-
|
|
5423
|
+
renameSync8(temp, this.file);
|
|
5271
5424
|
}
|
|
5272
5425
|
isUnread(key) {
|
|
5273
5426
|
return key !== "" && this.load().has(key);
|
|
@@ -5332,6 +5485,7 @@ var MailHarborRuntime = class {
|
|
|
5332
5485
|
projectStore = null;
|
|
5333
5486
|
taskStore = null;
|
|
5334
5487
|
flagStores = /* @__PURE__ */ new Map();
|
|
5488
|
+
actionQueues = /* @__PURE__ */ new Map();
|
|
5335
5489
|
referenceJobs = /* @__PURE__ */ new Map();
|
|
5336
5490
|
disposed = false;
|
|
5337
5491
|
constructor(options = {}) {
|
|
@@ -5346,13 +5500,13 @@ var MailHarborRuntime = class {
|
|
|
5346
5500
|
}
|
|
5347
5501
|
projects() {
|
|
5348
5502
|
if (this.projectStore === null) {
|
|
5349
|
-
this.projectStore = new ProjectStore(
|
|
5503
|
+
this.projectStore = new ProjectStore(join8(this.config.dataDir, "projects.json"));
|
|
5350
5504
|
}
|
|
5351
5505
|
return this.projectStore;
|
|
5352
5506
|
}
|
|
5353
5507
|
tasks() {
|
|
5354
5508
|
if (this.taskStore === null) {
|
|
5355
|
-
this.taskStore = new TaskStore(
|
|
5509
|
+
this.taskStore = new TaskStore(join8(this.config.dataDir, "state", "state.db"));
|
|
5356
5510
|
}
|
|
5357
5511
|
return this.taskStore;
|
|
5358
5512
|
}
|
|
@@ -5432,6 +5586,22 @@ var MailHarborRuntime = class {
|
|
|
5432
5586
|
paths(account) {
|
|
5433
5587
|
return accountRuntime(account);
|
|
5434
5588
|
}
|
|
5589
|
+
actionQueue(account) {
|
|
5590
|
+
const file = join8(this.paths(account).root, "actions-queue.json");
|
|
5591
|
+
const existing = this.actionQueues.get(account.id);
|
|
5592
|
+
if (existing !== void 0 && existing.signature === file) return existing.value;
|
|
5593
|
+
const created = new ActionQueueStore(file);
|
|
5594
|
+
this.actionQueues.set(account.id, { signature: file, value: created });
|
|
5595
|
+
return created;
|
|
5596
|
+
}
|
|
5597
|
+
async flushActions(account) {
|
|
5598
|
+
if (!this.hasCredentials(account)) return { sent: 0, failed: 0 };
|
|
5599
|
+
try {
|
|
5600
|
+
return await this.actionQueue(account).flushTo(this.provider(account));
|
|
5601
|
+
} catch {
|
|
5602
|
+
return { sent: 0, failed: 0 };
|
|
5603
|
+
}
|
|
5604
|
+
}
|
|
5435
5605
|
referencesStatus(account) {
|
|
5436
5606
|
return this.referenceJobs.get(account.id) ?? {
|
|
5437
5607
|
running: false,
|
|
@@ -5521,7 +5691,7 @@ var MailHarborRuntime = class {
|
|
|
5521
5691
|
return created;
|
|
5522
5692
|
}
|
|
5523
5693
|
aiThreads(account) {
|
|
5524
|
-
const file =
|
|
5694
|
+
const file = join8(this.paths(account).root, "ai-threads.json");
|
|
5525
5695
|
const existing = this.aiStores.get(account.id);
|
|
5526
5696
|
if (existing !== void 0 && existing.signature === file) return existing.value;
|
|
5527
5697
|
const created = new AiThreadStore(file);
|
|
@@ -5529,7 +5699,7 @@ var MailHarborRuntime = class {
|
|
|
5529
5699
|
return created;
|
|
5530
5700
|
}
|
|
5531
5701
|
flags(account) {
|
|
5532
|
-
const file =
|
|
5702
|
+
const file = join8(this.paths(account).root, "starred.json");
|
|
5533
5703
|
const existing = this.flagStores.get(account.id);
|
|
5534
5704
|
if (existing !== void 0 && existing.signature === file) return existing.value;
|
|
5535
5705
|
const created = new MailFlagsStore(file);
|
|
@@ -5537,7 +5707,7 @@ var MailHarborRuntime = class {
|
|
|
5537
5707
|
return created;
|
|
5538
5708
|
}
|
|
5539
5709
|
readState(account) {
|
|
5540
|
-
const file =
|
|
5710
|
+
const file = join8(this.paths(account).root, "read-state.json");
|
|
5541
5711
|
const existing = this.readStores.get(account.id);
|
|
5542
5712
|
if (existing !== void 0 && existing.signature === file) return existing.value;
|
|
5543
5713
|
const created = new ReadStateStore(file);
|
|
@@ -5603,6 +5773,7 @@ var MailHarborRuntime = class {
|
|
|
5603
5773
|
if (status.exitCode === 0) status.lastSuccessAt = status.finishedAt;
|
|
5604
5774
|
this.databases.get(account.id)?.value.close();
|
|
5605
5775
|
this.databases.delete(account.id);
|
|
5776
|
+
if (status.exitCode === 0) void this.flushActions(account).catch(() => void 0);
|
|
5606
5777
|
}
|
|
5607
5778
|
})();
|
|
5608
5779
|
this.backups.set(account.id, job);
|
|
@@ -6047,6 +6218,7 @@ function apply(ctx) {
|
|
|
6047
6218
|
const disposers = [];
|
|
6048
6219
|
if (activeAccount2 !== void 0) {
|
|
6049
6220
|
setTimeout(() => runtime.startReferencesBackfill(activeAccount2), 3e3);
|
|
6221
|
+
setTimeout(() => void runtime.flushActions(activeAccount2).catch(() => void 0), 6e3);
|
|
6050
6222
|
}
|
|
6051
6223
|
const start = () => {
|
|
6052
6224
|
disposers.push(ctx.webServer.register({ kind: "prefix", path: ROUTE, handler }));
|