@wirecat/cli-core 0.555.0 → 0.556.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 +38 -0
- package/dist/calendar/mcp/index.d.ts +12 -12
- package/dist/cli/config-command.js +64 -2
- package/dist/cli/config-issues.d.ts +2 -0
- package/dist/cli/config-issues.js +11 -3
- package/dist/cli/config-repair.d.ts +26 -0
- package/dist/cli/config-repair.js +252 -0
- package/dist/cli/messenger/store-maintenance-command.d.ts +1 -1
- package/dist/cli/messenger/tasks-analysis-command.d.ts +8 -0
- package/dist/cli/messenger/tasks-analysis-command.js +184 -0
- package/dist/cli/messenger/tasks-command.js +11 -2
- package/dist/cli/steps.js +10 -0
- package/dist/entities/index.d.ts +64 -2
- package/dist/entities/index.js +5 -2
- package/dist/entities/task-results.d.ts +72 -0
- package/dist/entities/task-results.js +40 -1
- package/dist/errors/reasons.d.ts +1 -1
- package/dist/errors/reasons.js +9 -0
- package/dist/mail/cli/index.d.ts +1 -0
- package/dist/mail/cli/index.js +3 -0
- package/dist/mail/cli/marks.d.ts +26 -0
- package/dist/mail/cli/marks.js +51 -0
- package/dist/mail/cli/review.js +70 -0
- package/dist/mail/index.d.ts +1 -0
- package/dist/mail/index.js +1 -0
- package/dist/mail/marks.d.ts +56 -0
- package/dist/mail/marks.js +178 -0
- package/dist/sends/permissions.js +1 -0
- package/dist/services/task-analysis.d.ts +58 -0
- package/dist/services/task-analysis.js +125 -0
- package/dist/store/exports.d.ts +2 -2
- package/dist/store/index.d.ts +1 -1
- package/dist/store/sqlite/email-activity.d.ts +31 -0
- package/dist/store/sqlite/email-activity.js +38 -0
- package/dist/store/sqlite/emails.d.ts +2 -0
- package/dist/store/sqlite/emails.js +1 -0
- package/dist/store/sqlite/task-analysis-marks.d.ts +7 -0
- package/dist/store/sqlite/task-analysis-marks.js +27 -0
- package/dist/store/sync.d.ts +5 -1
- package/dist/store/sync.js +4 -0
- package/dist/store/types.d.ts +25 -0
- package/package.json +1 -1
- package/parity.json +29 -0
- package/skills/follow-ups/SKILL.md +22 -2
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { annotate } from "@wirecat/cli-base/commands";
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import * as v from "valibot";
|
|
4
|
+
import { answerEntity } from "../../cli/answer-entity.js";
|
|
5
|
+
import { outputFor } from "../../cli/context.js";
|
|
6
|
+
import { destroysNothing, LOCAL_WRITE } from "../../cli/effects.js";
|
|
7
|
+
import { itemOf, renderAnswer } from "../../cli/envelope.js";
|
|
8
|
+
import { acknowledgeMark } from "../marks.js";
|
|
9
|
+
const iso = (ms) => (ms === null ? null : new Date(ms).toISOString());
|
|
10
|
+
/** A holder's mark as `--json`, `mail status` and MCP show it. */
|
|
11
|
+
export const mailMark = v.strictObject({
|
|
12
|
+
holder: v.string(),
|
|
13
|
+
through_id: v.number(),
|
|
14
|
+
through_received_at: v.nullable(v.string()),
|
|
15
|
+
held: v.number(),
|
|
16
|
+
read: v.number(),
|
|
17
|
+
skipped: v.strictObject({ bulk: v.number(), envelope: v.number(), partial: v.number(), draft: v.number() }),
|
|
18
|
+
updated_at: v.string(),
|
|
19
|
+
});
|
|
20
|
+
export const markItem = (mark) => ({
|
|
21
|
+
holder: mark.holder,
|
|
22
|
+
through_id: mark.throughId,
|
|
23
|
+
through_received_at: iso(mark.throughAt),
|
|
24
|
+
held: mark.held.length,
|
|
25
|
+
read: mark.read,
|
|
26
|
+
skipped: mark.skipped,
|
|
27
|
+
updated_at: iso(mark.updatedAt),
|
|
28
|
+
});
|
|
29
|
+
export const markText = (mark) => `${mark.holder} through ${mark.throughId}${mark.throughAt === null ? "" : ` (${iso(mark.throughAt)})`} ` +
|
|
30
|
+
`read ${mark.read} held ${mark.held.length} skipped bulk ${mark.skipped.bulk}, envelope ${mark.skipped.envelope}, ` +
|
|
31
|
+
`partial ${mark.skipped.partial}, draft ${mark.skipped.draft} updated ${iso(mark.updatedAt)}\n`;
|
|
32
|
+
/**
|
|
33
|
+
* `messages acknowledge`: a holder says it is done with what `review --holder` gave it, and its mark moves there. It
|
|
34
|
+
* only moves the holder's own bookkeeping forward, so a read-only scheduled run may do it.
|
|
35
|
+
*/
|
|
36
|
+
export const acknowledgeCommand = (mail) => answerEntity(destroysNothing(annotate(new Command("acknowledge"), {
|
|
37
|
+
effects: LOCAL_WRITE,
|
|
38
|
+
examples: [
|
|
39
|
+
`${mail.app.command} personal messages acknowledge --holder mail-triage --cursor 9123.1762090000000`,
|
|
40
|
+
],
|
|
41
|
+
})), "mail_mark", mailMark)
|
|
42
|
+
.summary("move a holder's processing mark to what it has read")
|
|
43
|
+
.description("record that a holder — an agent or a pipeline — processed the mail `review --holder` gave it, with the cursor " +
|
|
44
|
+
"that review gave; the mark only moves forward, and mail still partial waits for the next review")
|
|
45
|
+
.requiredOption("--holder <name>", "whose mark: lower-case letters, digits and dashes, such as mail-triage")
|
|
46
|
+
.requiredOption("--cursor <cursor>", "the cursor `review --holder` gave")
|
|
47
|
+
.action(async function () {
|
|
48
|
+
const { holder, cursor } = this.opts();
|
|
49
|
+
const mark = await mail.read(this, (store, mailbox) => acknowledgeMark(store, mailbox, holder, cursor));
|
|
50
|
+
renderAnswer(this, outputFor(this), itemOf(markItem(mark)), () => markText(mark));
|
|
51
|
+
});
|
package/dist/mail/cli/review.js
CHANGED
|
@@ -16,6 +16,7 @@ import { parseDuration } from "../../domain/duration.js";
|
|
|
16
16
|
import { formatReference } from "../../domain/references.js";
|
|
17
17
|
import { AGENDA_DEFAULTS } from "../../services/agenda.js";
|
|
18
18
|
import { REVIEW_DAYS, reviewStart } from "../../services/inbox.js";
|
|
19
|
+
import { readPastMark } from "../marks.js";
|
|
19
20
|
import { hitText } from "../read.js";
|
|
20
21
|
import { OWED_DAYS, REVIEW_THREADS, reviewMail } from "../review.js";
|
|
21
22
|
const DAY = 86_400_000;
|
|
@@ -30,6 +31,15 @@ export const MAIL_REVIEW_QUERY = defineQuery({
|
|
|
30
31
|
});
|
|
31
32
|
/** The mail review's steps; a host lists them beside its own so its step checks know them. */
|
|
32
33
|
export const MAIL_REVIEW_STEPS = defineSteps([
|
|
34
|
+
{
|
|
35
|
+
id: "mail-messages.acknowledge",
|
|
36
|
+
command: "messages acknowledge",
|
|
37
|
+
args: [
|
|
38
|
+
{ name: "holder", required: true },
|
|
39
|
+
{ name: "cursor", required: true },
|
|
40
|
+
],
|
|
41
|
+
why: "once these are processed, this moves the holder's mark past them",
|
|
42
|
+
},
|
|
33
43
|
{
|
|
34
44
|
id: "mail-review.next",
|
|
35
45
|
command: "review",
|
|
@@ -113,6 +123,62 @@ const reviewAnswer = (found, mailbox, fresh) => {
|
|
|
113
123
|
meta: found.complete ? {} : { complete: false },
|
|
114
124
|
});
|
|
115
125
|
};
|
|
126
|
+
const holderReview = async (command, mail, holder, query) => {
|
|
127
|
+
const { unanswered, waiting } = command.opts();
|
|
128
|
+
if (typedQuery(query).trim() || unanswered !== undefined || waiting !== undefined)
|
|
129
|
+
throw new CliError("validation_error", "--holder reads past its own mark — not with a query, --unanswered or --waiting");
|
|
130
|
+
const { read, mailbox, marks } = await mail.read(command, async (store, mailbox) => {
|
|
131
|
+
const read = await readPastMark(store, mailbox, holder);
|
|
132
|
+
const account = read.emails.length ? await store.accounts.storedAccount(mailbox) : undefined;
|
|
133
|
+
const marks = account
|
|
134
|
+
? await store.mail.emailMarks(account.id, read.emails.map((one) => one.id))
|
|
135
|
+
: new Map();
|
|
136
|
+
return { read, mailbox, marks };
|
|
137
|
+
});
|
|
138
|
+
const chats = new Map();
|
|
139
|
+
for (const email of read.emails) {
|
|
140
|
+
const chat = chats.get(email.chatId) ?? {
|
|
141
|
+
id: email.chatId,
|
|
142
|
+
title: email.chatTitle,
|
|
143
|
+
kind: "dialog",
|
|
144
|
+
messages: [],
|
|
145
|
+
more: false,
|
|
146
|
+
};
|
|
147
|
+
const mark = marks.get(email.id);
|
|
148
|
+
chat.messages.push(mark ? { ...email, marks: mark } : email);
|
|
149
|
+
chats.set(email.chatId, chat);
|
|
150
|
+
}
|
|
151
|
+
const now = new Date().toISOString();
|
|
152
|
+
const found = {
|
|
153
|
+
since: read.mark?.throughAt ? new Date(read.mark.throughAt).toISOString() : now,
|
|
154
|
+
until: now,
|
|
155
|
+
complete: !read.more,
|
|
156
|
+
chats: [...chats.values()],
|
|
157
|
+
skipped: [],
|
|
158
|
+
partial: false,
|
|
159
|
+
quiet: 0,
|
|
160
|
+
};
|
|
161
|
+
const answer = reviewAnswer(found, mailbox, true);
|
|
162
|
+
const skipped = Object.entries(read.skipped).filter(([, count]) => count > 0);
|
|
163
|
+
answer.next = [
|
|
164
|
+
stepOf(step("mail-messages.acknowledge"), { args: { holder, cursor: read.cursor } }),
|
|
165
|
+
...(answer.next ?? []),
|
|
166
|
+
];
|
|
167
|
+
answer.missing = [
|
|
168
|
+
...(answer.missing ?? []),
|
|
169
|
+
...(read.held > 0 ? [{ field: "items", why: `${read.held} partial emails held until they are whole` }] : []),
|
|
170
|
+
...(skipped.length
|
|
171
|
+
? [{ field: "items", why: `left out: ${skipped.map(([reason, count]) => `${count} ${reason}`).join(", ")}` }]
|
|
172
|
+
: []),
|
|
173
|
+
];
|
|
174
|
+
const output = outputFor(command);
|
|
175
|
+
renderAnswer(command, output, answer, () => {
|
|
176
|
+
output.renderer.note(read.mark
|
|
177
|
+
? `${holder}: past ${read.mark.throughId}; acknowledge with --cursor ${read.cursor}${read.more ? "; more after these" : ""}`
|
|
178
|
+
: `${holder} has no mark yet: it starts from now — acknowledge with --cursor ${read.cursor}`);
|
|
179
|
+
return read.emails.map((hit) => `${hitText(hit)}${markText(marks.get(hit.id))}\n`).join("");
|
|
180
|
+
});
|
|
181
|
+
};
|
|
116
182
|
/**
|
|
117
183
|
* `review` over the stored mail of the profile's mailbox, with tg's words: what is new, what the owner owes, what
|
|
118
184
|
* waits for an answer. It reads the store only and opens no task.
|
|
@@ -136,6 +202,8 @@ export const mailReviewCommand = (mail) => annotate(new Command("review"), {
|
|
|
136
202
|
.argument("[query...]", `where the last review ended: date>2h; ${REVIEW_DAYS} days ago if no date`)
|
|
137
203
|
.option("--unanswered [duration]", "only threads whose newest email came to you, in To or Cc, with no answer for this long — 4h, 1d; 24h if not given")
|
|
138
204
|
.option("--waiting [duration]", "only threads where you wrote last and nobody answered for this long — 3d; 7d if not given")
|
|
205
|
+
.option("--holder <name>", "read for this holder — an agent or a pipeline — what is stored past its processing mark, by store order; " +
|
|
206
|
+
"bulk, envelopes, drafts and partial mail left out; `messages acknowledge` moves the mark")
|
|
139
207
|
.option("--new", "what changed since the last `review --new` of this mailbox, a point per thread — for scheduled runs")
|
|
140
208
|
.addHelpText("after", `
|
|
141
209
|
Without a date, --unanswered and --waiting look at threads active in the last ${OWED_DAYS} days.
|
|
@@ -145,6 +213,8 @@ Mail newer than the last finished sync waits for the next review. Each reader ke
|
|
|
145
213
|
an agent over MCP, and each scheduled run.`)
|
|
146
214
|
.action(async function (query) {
|
|
147
215
|
const options = this.opts();
|
|
216
|
+
if (options.holder !== undefined)
|
|
217
|
+
return holderReview(this, mail, options.holder, query);
|
|
148
218
|
const { since: from, meta } = catchUpQuery(query, MAIL_REVIEW_QUERY, "review");
|
|
149
219
|
const bulk = nativeTerms(listQuery(typedQuery(query), MAIL_REVIEW_QUERY, "review"), "review").get("bulk")?.value;
|
|
150
220
|
if (bulk !== undefined && bulk !== "false")
|
package/dist/mail/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export { dropMissing, type Gone, tooManyGone } from "./gone.js";
|
|
|
6
6
|
export { type AuthVerdicts, HEADER_FIELDS, headersOf, type MailHeaders, mailHeadersOf } from "./headers.js";
|
|
7
7
|
export { type MailIdsInput, mailIds } from "./ids.js";
|
|
8
8
|
export { decodedLabelName, decodeMailboxName, labelName, systemLabel } from "./labels.js";
|
|
9
|
+
export { acknowledgeMark, cursorOf, HOLD_PARTIAL_MS, HOLDER, MARK_BATCH, type MailMark, type MarkRead, markOf, marksOf, readPastMark, type SkipReason, } from "./marks.js";
|
|
9
10
|
export { type MailTurn, mailTurn } from "./owed.js";
|
|
10
11
|
export { type Address, htmlToText, looksLikeHtml, MAX_ATTACHMENTS, MAX_BODY_CHARS, type ParsedMail, parseMail, } from "./parse.js";
|
|
11
12
|
export { PARTIAL, type PartialOutcome } from "./partial.js";
|
package/dist/mail/index.js
CHANGED
|
@@ -6,6 +6,7 @@ export { dropMissing, tooManyGone } from "./gone.js";
|
|
|
6
6
|
export { HEADER_FIELDS, headersOf, mailHeadersOf } from "./headers.js";
|
|
7
7
|
export { mailIds } from "./ids.js";
|
|
8
8
|
export { decodedLabelName, decodeMailboxName, labelName, systemLabel } from "./labels.js";
|
|
9
|
+
export { acknowledgeMark, cursorOf, HOLD_PARTIAL_MS, HOLDER, MARK_BATCH, markOf, marksOf, readPastMark, } from "./marks.js";
|
|
9
10
|
export { mailTurn } from "./owed.js";
|
|
10
11
|
export { htmlToText, looksLikeHtml, MAX_ATTACHMENTS, MAX_BODY_CHARS, parseMail, } from "./parse.js";
|
|
11
12
|
export { PARTIAL } from "./partial.js";
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { AccountKey, MessageStore, StoredHit } from "../store/store.js";
|
|
2
|
+
/** A holder's name: what `--holder` takes and the mark's key ends with. */
|
|
3
|
+
export declare const HOLDER: RegExp;
|
|
4
|
+
/** At most this many emails are read past the mark in one go; the rest come with the next read. */
|
|
5
|
+
export declare const MARK_BATCH = 200;
|
|
6
|
+
/** A partial email waits this long to be stored whole, then is skipped. */
|
|
7
|
+
export declare const HOLD_PARTIAL_MS: number;
|
|
8
|
+
export type SkipReason = "bulk" | "envelope" | "partial" | "draft";
|
|
9
|
+
/**
|
|
10
|
+
* How far one holder — an agent, a pipeline — has processed an account's mail, in the shared store (`sync_cursors`,
|
|
11
|
+
* key `consumer:<holder>`). It counts by the store's insert order (`emails.id`), so mail stored late, older than what
|
|
12
|
+
* was read, is still after the mark. Partial emails are held until they are whole.
|
|
13
|
+
*/
|
|
14
|
+
export interface MailMark {
|
|
15
|
+
holder: string;
|
|
16
|
+
throughId: number;
|
|
17
|
+
/** ms: when the email at `throughId` was received; for display. */
|
|
18
|
+
throughAt: number | null;
|
|
19
|
+
held: number[];
|
|
20
|
+
read: number;
|
|
21
|
+
skipped: Record<SkipReason, number>;
|
|
22
|
+
/** ms. */
|
|
23
|
+
updatedAt: number;
|
|
24
|
+
}
|
|
25
|
+
export declare const markOf: (store: MessageStore, mailbox: AccountKey, holder: string) => Promise<MailMark | undefined>;
|
|
26
|
+
/** Every holder's mark on the mailbox, by name. */
|
|
27
|
+
export declare const marksOf: (store: MessageStore, mailbox: AccountKey) => Promise<MailMark[]>;
|
|
28
|
+
/** The mark's state, as a cursor `acknowledge` takes back: the last id read, and when it was read. */
|
|
29
|
+
export declare const cursorOf: (throughId: number, readAt: number) => string;
|
|
30
|
+
export interface MarkRead {
|
|
31
|
+
/** The emails for the holder, oldest first; bulk, envelopes, drafts and partial mail left out. */
|
|
32
|
+
emails: StoredHit[];
|
|
33
|
+
/** What `acknowledge --cursor` takes once the holder is done with these. */
|
|
34
|
+
cursor: string;
|
|
35
|
+
/** More is stored after this batch: read again after acknowledging. */
|
|
36
|
+
more: boolean;
|
|
37
|
+
held: number;
|
|
38
|
+
skipped: Record<SkipReason, number>;
|
|
39
|
+
/** `undefined` before the holder's first acknowledge: it starts from the mail stored now. */
|
|
40
|
+
mark: MailMark | undefined;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* What the holder has not processed: the emails stored after its mark, and the held ones that are whole now. Reads
|
|
44
|
+
* only; the mark moves when the holder acknowledges. A holder with no mark starts from now: its first read is
|
|
45
|
+
* empty and gives the cursor of the newest stored email.
|
|
46
|
+
*/
|
|
47
|
+
export declare const readPastMark: (store: MessageStore, mailbox: AccountKey, holder: string, { now, limit }?: {
|
|
48
|
+
now?: number;
|
|
49
|
+
limit?: number;
|
|
50
|
+
}) => Promise<MarkRead>;
|
|
51
|
+
/**
|
|
52
|
+
* Moves the holder's mark to the cursor its read gave, never back. An email whole when it was read counts read; one
|
|
53
|
+
* still partial, or changed after the read (it may have become whole since), is held and comes with the next read.
|
|
54
|
+
* The same cursor twice changes nothing more.
|
|
55
|
+
*/
|
|
56
|
+
export declare const acknowledgeMark: (store: MessageStore, mailbox: AccountKey, holder: string, cursor: string, now?: number) => Promise<MailMark>;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { CliError } from "@wirecat/cli-base";
|
|
2
|
+
/** A holder's name: what `--holder` takes and the mark's key ends with. */
|
|
3
|
+
export const HOLDER = /^[a-z0-9][a-z0-9-]{0,39}$/;
|
|
4
|
+
/** At most this many emails are read past the mark in one go; the rest come with the next read. */
|
|
5
|
+
export const MARK_BATCH = 200;
|
|
6
|
+
/** A partial email waits this long to be stored whole, then is skipped. */
|
|
7
|
+
export const HOLD_PARTIAL_MS = 7 * 86_400_000;
|
|
8
|
+
/** Held emails kept on one mark at most; past it the oldest are skipped as partial. */
|
|
9
|
+
const MOST_HELD = 500;
|
|
10
|
+
const PREFIX = "consumer:";
|
|
11
|
+
const noneSkipped = () => ({ bulk: 0, envelope: 0, partial: 0, draft: 0 });
|
|
12
|
+
const keyOf = (holder) => {
|
|
13
|
+
if (!HOLDER.test(holder))
|
|
14
|
+
throw new CliError("validation_error", `a holder is lower-case letters, digits and dashes, not "${holder}"`);
|
|
15
|
+
return `${PREFIX}${holder}`;
|
|
16
|
+
};
|
|
17
|
+
const parsed = (holder, value, at) => {
|
|
18
|
+
try {
|
|
19
|
+
const saved = JSON.parse(value);
|
|
20
|
+
if (typeof saved.throughId !== "number")
|
|
21
|
+
return undefined;
|
|
22
|
+
return {
|
|
23
|
+
holder,
|
|
24
|
+
throughId: saved.throughId,
|
|
25
|
+
throughAt: saved.throughAt ?? null,
|
|
26
|
+
held: Array.isArray(saved.held) ? saved.held.filter((id) => typeof id === "number") : [],
|
|
27
|
+
read: saved.read ?? 0,
|
|
28
|
+
skipped: { ...noneSkipped(), ...saved.skipped },
|
|
29
|
+
updatedAt: at,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const accountOf = async (store, mailbox) => store.accounts.storedAccount(mailbox).catch((error) => {
|
|
37
|
+
if (error instanceof CliError && error.code === "not_found")
|
|
38
|
+
return undefined;
|
|
39
|
+
throw error;
|
|
40
|
+
});
|
|
41
|
+
export const markOf = async (store, mailbox, holder) => {
|
|
42
|
+
const saved = await store.sync.syncState(mailbox, keyOf(holder));
|
|
43
|
+
return saved ? parsed(holder, saved.value, Date.parse(saved.at)) : undefined;
|
|
44
|
+
};
|
|
45
|
+
/** Every holder's mark on the mailbox, by name. */
|
|
46
|
+
export const marksOf = async (store, mailbox) => {
|
|
47
|
+
const account = await accountOf(store, mailbox);
|
|
48
|
+
if (!account)
|
|
49
|
+
return [];
|
|
50
|
+
return (await store.mail.statesLike(account.id, PREFIX))
|
|
51
|
+
.flatMap(({ key, value, at }) => parsed(key.slice(PREFIX.length), value, at) ?? [])
|
|
52
|
+
.sort((a, b) => a.holder.localeCompare(b.holder));
|
|
53
|
+
};
|
|
54
|
+
/** Why an email is not for the holder, or `held` while it is partial and young, or `read`. */
|
|
55
|
+
const verdict = (email, now) => {
|
|
56
|
+
if (email.draft)
|
|
57
|
+
return "draft";
|
|
58
|
+
if (email.envelope)
|
|
59
|
+
return "envelope";
|
|
60
|
+
if (email.bulk)
|
|
61
|
+
return "bulk";
|
|
62
|
+
if (email.partial)
|
|
63
|
+
return now - email.storedAt < HOLD_PARTIAL_MS ? "held" : "partial";
|
|
64
|
+
return "read";
|
|
65
|
+
};
|
|
66
|
+
/** The mark's state, as a cursor `acknowledge` takes back: the last id read, and when it was read. */
|
|
67
|
+
export const cursorOf = (throughId, readAt) => `${throughId}.${readAt}`;
|
|
68
|
+
const parseCursor = (cursor) => {
|
|
69
|
+
const match = /^(\d+)\.(\d+)$/.exec(cursor.trim());
|
|
70
|
+
if (!match)
|
|
71
|
+
throw new CliError("validation_error", `--cursor takes the cursor review gave, such as 9123.1762090000000`);
|
|
72
|
+
return { throughId: Number(match[1]), readAt: Number(match[2]) };
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* What the holder has not processed: the emails stored after its mark, and the held ones that are whole now. Reads
|
|
76
|
+
* only; the mark moves when the holder acknowledges. A holder with no mark starts from now: its first read is
|
|
77
|
+
* empty and gives the cursor of the newest stored email.
|
|
78
|
+
*/
|
|
79
|
+
export const readPastMark = async (store, mailbox, holder, { now = Date.now(), limit = MARK_BATCH } = {}) => {
|
|
80
|
+
const mark = await markOf(store, mailbox, holder);
|
|
81
|
+
const account = await accountOf(store, mailbox);
|
|
82
|
+
const empty = { emails: [], more: false, held: 0, skipped: noneSkipped(), mark };
|
|
83
|
+
if (!account)
|
|
84
|
+
return { ...empty, cursor: cursorOf(0, now) };
|
|
85
|
+
if (!mark)
|
|
86
|
+
return { ...empty, cursor: cursorOf(await store.mail.newestEmailId(account.id), now) };
|
|
87
|
+
const found = await store.mail.emailsAfter(account.id, mark.throughId, limit + 1, mark.held);
|
|
88
|
+
// Mail a running sync stored is read once that sync ended: it files bulk mail only at its end.
|
|
89
|
+
const settled = await store.sync.syncState(mailbox, "mail_coverage");
|
|
90
|
+
const settledAt = settled ? Date.parse(settled.at) : Number.POSITIVE_INFINITY;
|
|
91
|
+
const after = found.filter((email) => email.id > mark.throughId);
|
|
92
|
+
const unsettled = after.findIndex((email) => email.storedAt > settledAt);
|
|
93
|
+
const fresh = unsettled === -1 ? after : after.slice(0, unsettled);
|
|
94
|
+
const batch = fresh.slice(0, limit);
|
|
95
|
+
const examined = [...found.filter((email) => email.id <= mark.throughId), ...batch];
|
|
96
|
+
const skipped = noneSkipped();
|
|
97
|
+
const wanted = [];
|
|
98
|
+
let held = 0;
|
|
99
|
+
for (const email of examined) {
|
|
100
|
+
const said = verdict(email, now);
|
|
101
|
+
if (said === "read")
|
|
102
|
+
wanted.push(email.id);
|
|
103
|
+
else if (said === "held")
|
|
104
|
+
held += 1;
|
|
105
|
+
else if (email.id > mark.throughId)
|
|
106
|
+
skipped[said] += 1;
|
|
107
|
+
}
|
|
108
|
+
const emails = await store.mail.emailHits(wanted.sort((a, b) => a - b));
|
|
109
|
+
return {
|
|
110
|
+
emails,
|
|
111
|
+
cursor: cursorOf(batch.at(-1)?.id ?? mark.throughId, now),
|
|
112
|
+
more: after.length > batch.length,
|
|
113
|
+
held,
|
|
114
|
+
skipped,
|
|
115
|
+
mark,
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* Moves the holder's mark to the cursor its read gave, never back. An email whole when it was read counts read; one
|
|
120
|
+
* still partial, or changed after the read (it may have become whole since), is held and comes with the next read.
|
|
121
|
+
* The same cursor twice changes nothing more.
|
|
122
|
+
*/
|
|
123
|
+
export const acknowledgeMark = async (store, mailbox, holder, cursor, now = Date.now()) => {
|
|
124
|
+
const key = keyOf(holder);
|
|
125
|
+
const { throughId, readAt } = parseCursor(cursor);
|
|
126
|
+
const account = await accountOf(store, mailbox);
|
|
127
|
+
if (!account)
|
|
128
|
+
throw new CliError("not_found", `no mail of ${mailbox.account} is stored`);
|
|
129
|
+
const mark = await markOf(store, mailbox, holder);
|
|
130
|
+
const from = mark?.throughId ?? throughId;
|
|
131
|
+
if (throughId < from)
|
|
132
|
+
throw new CliError("validation_error", `${holder} is already past ${throughId}: its mark is at ${from}`);
|
|
133
|
+
const examined = [];
|
|
134
|
+
let after = from;
|
|
135
|
+
const held = new Set(mark?.held ?? []);
|
|
136
|
+
const again = await store.mail.emailsAfter(account.id, Number.MAX_SAFE_INTEGER, 0, [...held]);
|
|
137
|
+
examined.push(...again);
|
|
138
|
+
while (after < throughId) {
|
|
139
|
+
const page = (await store.mail.emailsAfter(account.id, after, MARK_BATCH)).filter((one) => one.id <= throughId);
|
|
140
|
+
if (page.length === 0)
|
|
141
|
+
break;
|
|
142
|
+
examined.push(...page);
|
|
143
|
+
after = page.at(-1)?.id ?? throughId;
|
|
144
|
+
}
|
|
145
|
+
const skipped = { ...(mark?.skipped ?? noneSkipped()) };
|
|
146
|
+
let read = mark?.read ?? 0;
|
|
147
|
+
const keep = [];
|
|
148
|
+
const live = new Set(again.map((one) => one.id));
|
|
149
|
+
for (const id of held)
|
|
150
|
+
if (!live.has(id))
|
|
151
|
+
held.delete(id);
|
|
152
|
+
for (const email of examined) {
|
|
153
|
+
const said = verdict(email, readAt);
|
|
154
|
+
const wasHeld = held.has(email.id);
|
|
155
|
+
if (said === "held" || (said === "read" && email.changedAt > readAt) || (wasHeld && email.changedAt > readAt)) {
|
|
156
|
+
if (email.partial && now - email.storedAt >= HOLD_PARTIAL_MS)
|
|
157
|
+
skipped.partial += 1;
|
|
158
|
+
else
|
|
159
|
+
keep.push(email.id);
|
|
160
|
+
}
|
|
161
|
+
else if (said === "read")
|
|
162
|
+
read += wasHeld || email.id > from ? 1 : 0;
|
|
163
|
+
else if (!wasHeld || said === "partial")
|
|
164
|
+
skipped[said] += 1;
|
|
165
|
+
}
|
|
166
|
+
const kept = [...new Set(keep)].sort((a, b) => a - b);
|
|
167
|
+
skipped.partial += Math.max(0, kept.length - MOST_HELD);
|
|
168
|
+
const through = examined.filter((one) => one.id <= throughId).at(-1);
|
|
169
|
+
const next = {
|
|
170
|
+
throughId,
|
|
171
|
+
throughAt: through?.id === throughId ? through.at : (mark?.throughAt ?? null),
|
|
172
|
+
held: kept.slice(-MOST_HELD),
|
|
173
|
+
read,
|
|
174
|
+
skipped,
|
|
175
|
+
};
|
|
176
|
+
await store.sync.setSyncState(mailbox, key, JSON.stringify(next));
|
|
177
|
+
return { holder, ...next, updatedAt: now };
|
|
178
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { ChatKind, Id, Message } from "../domain/models.js";
|
|
2
|
+
import type { AccountKey, MessageStore, TaskAnalysisMark } from "../store/index.js";
|
|
3
|
+
/** How many messages one read gives an agent: a page it can judge in one go. */
|
|
4
|
+
export declare const ANALYSIS_PAGE = 100;
|
|
5
|
+
export declare const ANALYSIS_PAGE_MAX = 1000;
|
|
6
|
+
/** Where a chat never read for tasks starts, unless the caller names a time: older history is not new work. */
|
|
7
|
+
export declare const defaultStart: (now: Date) => string;
|
|
8
|
+
export interface ChatAnalysis {
|
|
9
|
+
chatId: Id;
|
|
10
|
+
title: string | null;
|
|
11
|
+
kind: ChatKind;
|
|
12
|
+
newestAt: string | null;
|
|
13
|
+
mark: TaskAnalysisMark | null;
|
|
14
|
+
/** Stored messages after the mark, or since the default start when the chat was never read for tasks. */
|
|
15
|
+
waiting: number;
|
|
16
|
+
/** Stored inside the read stretch after it was read: `--since <from>` reads the stretch again. */
|
|
17
|
+
late: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Every chat whose messages can hold the owner's tasks — not a channel or Saved Messages (owner, 2026-10-17) — with
|
|
21
|
+
* how far it was read for tasks and how much waits, newest chat first.
|
|
22
|
+
*/
|
|
23
|
+
export declare const taskAnalysisList: (store: MessageStore, account: AccountKey, now?: Date) => Promise<ChatAnalysis[]>;
|
|
24
|
+
export interface UnanalysedPage {
|
|
25
|
+
mark: TaskAnalysisMark | null;
|
|
26
|
+
/** Where this read started: the mark, `since`, or the default start. */
|
|
27
|
+
from: string;
|
|
28
|
+
messages: Message[];
|
|
29
|
+
more: boolean;
|
|
30
|
+
late: number;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The stored messages of a chat not yet read for tasks, oldest first, from its mark; `since` reads from that time
|
|
34
|
+
* instead, to read a stretch again. Reading moves nothing: `acknowledgeTaskAnalysis` does, once the reader is done.
|
|
35
|
+
*/
|
|
36
|
+
export declare const unanalysedMessages: (store: MessageStore, account: AccountKey, chatId: Id, { limit, since, now }?: {
|
|
37
|
+
limit?: number;
|
|
38
|
+
since?: string;
|
|
39
|
+
now?: Date;
|
|
40
|
+
}) => Promise<UnanalysedPage>;
|
|
41
|
+
/**
|
|
42
|
+
* Records that the chat was read for tasks through `messageId`, from `since` when that read started there. The mark
|
|
43
|
+
* never moves back. A read that covered the whole stretch again also clears what arrived late inside it.
|
|
44
|
+
*/
|
|
45
|
+
export declare const acknowledgeTaskAnalysis: (store: MessageStore, account: AccountKey, chatId: Id, messageId: Id, { since, by, now }: {
|
|
46
|
+
since?: string;
|
|
47
|
+
by: string;
|
|
48
|
+
now?: Date;
|
|
49
|
+
}) => Promise<{
|
|
50
|
+
mark: TaskAnalysisMark;
|
|
51
|
+
moved: boolean;
|
|
52
|
+
}>;
|
|
53
|
+
/**
|
|
54
|
+
* A read again of history the chat's task point already passed only updates and closes tasks (owner, 2026-11-03):
|
|
55
|
+
* a new task from such a message needs `--open-new`. A message that already has a task, or one after the point, is
|
|
56
|
+
* let through.
|
|
57
|
+
*/
|
|
58
|
+
export declare const refuseAlreadyRead: (store: MessageStore, account: AccountKey, source: string) => Promise<void>;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { CliError } from "@wirecat/cli-base";
|
|
2
|
+
import { isLocator, parseLocator } from "../domain/locator.js";
|
|
3
|
+
import { FIRST_PASS_DAYS, RULES_IN, taskAccount } from "./task-rules.js";
|
|
4
|
+
/** How many messages one read gives an agent: a page it can judge in one go. */
|
|
5
|
+
export const ANALYSIS_PAGE = 100;
|
|
6
|
+
export const ANALYSIS_PAGE_MAX = 1000;
|
|
7
|
+
/** Where a chat never read for tasks starts, unless the caller names a time: older history is not new work. */
|
|
8
|
+
export const defaultStart = (now) => new Date(now.getTime() - FIRST_PASS_DAYS * 86_400_000).toISOString();
|
|
9
|
+
const after = (iso) => new Date(Date.parse(iso) + 1).toISOString();
|
|
10
|
+
/** Messages saved or edited after the mark was last checked, sent inside the stretch it had read. */
|
|
11
|
+
const lateOf = async (store, account, mark) => {
|
|
12
|
+
const { messages } = await store.messages.changes(account, mark.chatId, mark.checkedAt);
|
|
13
|
+
return messages.filter((one) => one.timestamp >= mark.from && one.timestamp <= mark.checkedThrough);
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Every chat whose messages can hold the owner's tasks — not a channel or Saved Messages (owner, 2026-10-17) — with
|
|
17
|
+
* how far it was read for tasks and how much waits, newest chat first.
|
|
18
|
+
*/
|
|
19
|
+
export const taskAnalysisList = async (store, account, now = new Date()) => {
|
|
20
|
+
const marks = new Map((await store.sync.taskAnalysisMarks(account)).map((mark) => [mark.chatId, mark]));
|
|
21
|
+
const start = defaultStart(now);
|
|
22
|
+
const chats = [];
|
|
23
|
+
for (const chat of await store.chats.chatStats(account)) {
|
|
24
|
+
if (RULES_IN[chat.kind] === "none")
|
|
25
|
+
continue;
|
|
26
|
+
const mark = marks.get(chat.chatId) ?? null;
|
|
27
|
+
chats.push({
|
|
28
|
+
chatId: chat.chatId,
|
|
29
|
+
title: chat.title,
|
|
30
|
+
kind: chat.kind,
|
|
31
|
+
newestAt: chat.newestAt,
|
|
32
|
+
mark,
|
|
33
|
+
waiting: await store.messages.countMessages(account, chat.chatId, { since: mark ? after(mark.through) : start }),
|
|
34
|
+
late: mark ? (await lateOf(store, account, mark)).length : 0,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return chats.sort((a, b) => (b.newestAt ?? "").localeCompare(a.newestAt ?? ""));
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* The stored messages of a chat not yet read for tasks, oldest first, from its mark; `since` reads from that time
|
|
41
|
+
* instead, to read a stretch again. Reading moves nothing: `acknowledgeTaskAnalysis` does, once the reader is done.
|
|
42
|
+
*/
|
|
43
|
+
export const unanalysedMessages = async (store, account, chatId, { limit = ANALYSIS_PAGE, since, now = new Date() } = {}) => {
|
|
44
|
+
const mark = (await store.sync.taskAnalysisMark(account, chatId)) ?? null;
|
|
45
|
+
const from = since ?? mark?.through ?? defaultStart(now);
|
|
46
|
+
const byTime = { time: Date.parse(from) - (since === undefined && mark ? 0 : 1) };
|
|
47
|
+
const read = (start) => store.messages.messages(account, chatId, { limit, after: start });
|
|
48
|
+
let page;
|
|
49
|
+
if (since === undefined && mark) {
|
|
50
|
+
try {
|
|
51
|
+
page = await read({ id: mark.throughId });
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
if (!(error instanceof CliError && error.code === "not_found"))
|
|
55
|
+
throw error;
|
|
56
|
+
page = await read(byTime);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else
|
|
60
|
+
page = await read(byTime);
|
|
61
|
+
return {
|
|
62
|
+
mark,
|
|
63
|
+
from,
|
|
64
|
+
messages: page.items,
|
|
65
|
+
more: page.hasMore,
|
|
66
|
+
late: mark ? (await lateOf(store, account, mark)).length : 0,
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Records that the chat was read for tasks through `messageId`, from `since` when that read started there. The mark
|
|
71
|
+
* never moves back. A read that covered the whole stretch again also clears what arrived late inside it.
|
|
72
|
+
*/
|
|
73
|
+
export const acknowledgeTaskAnalysis = async (store, account, chatId, messageId, { since, by, now = new Date() }) => {
|
|
74
|
+
const message = await store.messages.message(account, messageId, { chatId });
|
|
75
|
+
if (!message)
|
|
76
|
+
throw new CliError("not_found", `message ${messageId} is not in the local copy of chat ${chatId}`);
|
|
77
|
+
const mark = await store.sync.taskAnalysisMark(account, chatId);
|
|
78
|
+
const start = since ?? mark?.from ?? defaultStart(now);
|
|
79
|
+
if (message.timestamp < start)
|
|
80
|
+
throw new CliError("validation_error", `message ${messageId} was sent before the read started (${start})`);
|
|
81
|
+
if (mark && message.timestamp < mark.from)
|
|
82
|
+
throw new CliError("validation_error", `message ${messageId} ends before the stretch already read (${mark.from}): read on to it, then acknowledge`);
|
|
83
|
+
const ahead = !mark || message.timestamp > mark.through || (since !== undefined && message.timestamp >= mark.through);
|
|
84
|
+
if (mark && !ahead && start >= mark.from)
|
|
85
|
+
return { mark, moved: false };
|
|
86
|
+
const reread = mark !== undefined && since !== undefined && since <= mark.from && ahead;
|
|
87
|
+
const checked = !mark || reread || (since === undefined && (await lateOf(store, account, mark)).length === 0);
|
|
88
|
+
const through = mark && !ahead ? mark : { through: message.timestamp, throughId: message.id };
|
|
89
|
+
const next = {
|
|
90
|
+
chatId,
|
|
91
|
+
from: mark && mark.from < start ? mark.from : start,
|
|
92
|
+
through: through.through,
|
|
93
|
+
throughId: through.throughId,
|
|
94
|
+
by,
|
|
95
|
+
checked,
|
|
96
|
+
checkedThrough: checked || !mark ? through.through : mark.checkedThrough,
|
|
97
|
+
};
|
|
98
|
+
await store.sync.setTaskAnalysisMark(account, next);
|
|
99
|
+
const saved = await store.sync.taskAnalysisMark(account, chatId);
|
|
100
|
+
if (!saved)
|
|
101
|
+
throw new Error(`the task analysis mark of chat ${chatId} was not saved`);
|
|
102
|
+
return { mark: saved, moved: true };
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* A read again of history the chat's task point already passed only updates and closes tasks (owner, 2026-11-03):
|
|
106
|
+
* a new task from such a message needs `--open-new`. A message that already has a task, or one after the point, is
|
|
107
|
+
* let through.
|
|
108
|
+
*/
|
|
109
|
+
export const refuseAlreadyRead = async (store, account, source) => {
|
|
110
|
+
if (!isLocator(source))
|
|
111
|
+
return;
|
|
112
|
+
const { provider, account: owner, chat, message } = parseLocator(source);
|
|
113
|
+
if (provider !== account.provider || owner !== account.account)
|
|
114
|
+
return;
|
|
115
|
+
const mark = await store.sync.taskAnalysisMark(account, chat);
|
|
116
|
+
if (!mark)
|
|
117
|
+
return;
|
|
118
|
+
const stored = await store.messages.message(account, message, { chatId: chat });
|
|
119
|
+
if (!stored || stored.timestamp > mark.through)
|
|
120
|
+
return;
|
|
121
|
+
if ((await store.tasks.findBySource(taskAccount(account), source)).length > 0)
|
|
122
|
+
return;
|
|
123
|
+
throw new CliError("validation_error", `${source} was already read for tasks (the point is at ${mark.through}): a read again only updates and closes ` +
|
|
124
|
+
"tasks; add --open-new to open a task from it anyway", { reason: "task.already-read" });
|
|
125
|
+
};
|
package/dist/store/exports.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ export { storeClients } from "./sqlite/clients.js";
|
|
|
23
23
|
export { CHAT_LIST_KEY, type ChatCompleteness, fetchedKey, historyStartKey } from "./sqlite/completeness.js";
|
|
24
24
|
export { CONVERSATION_COLUMNS } from "./sqlite/conversations.js";
|
|
25
25
|
export type { CounterTarget } from "./sqlite/counters.js";
|
|
26
|
-
export { type EmailMarks, MAIL_AGENDA_COLUMNS, type NewestEmail, type ThreadActivity, } from "./sqlite/email-activity.js";
|
|
26
|
+
export { type EmailMarks, MAIL_AGENDA_COLUMNS, type MarkedEmail, type NewestEmail, type ThreadActivity, } from "./sqlite/email-activity.js";
|
|
27
27
|
export { messageOfEmail, threadConversation } from "./sqlite/emails.js";
|
|
28
28
|
export { orphanPointers } from "./sqlite/entity-types.js";
|
|
29
29
|
export { involvementStoreOver } from "./sqlite/involvements.js";
|
|
@@ -61,5 +61,5 @@ export type { MatchPlace, ScoredHit } from "./sqlite/words.js";
|
|
|
61
61
|
export type { TaskDetails } from "./sqlite/work-tasks.js";
|
|
62
62
|
export type { SyncStore } from "./sync.js";
|
|
63
63
|
export type { TagsStore } from "./tags.js";
|
|
64
|
-
export type { AccountKey, AccountMessageFilter, AgentAnswer, BatchMessage, ChatActivity, ChatStats, ConversationBuild, ConversationHit, ConversationSummary, DeletionScope, Delta, IdentityRef, IngestedVia, LinkBatch, LinkedIdentity, LinkOptions, MessageFilter, PersonFacts, PersonRecord, PictureInput, Range, SenderChatStats, StoredAccount, StoredChatFilter, StoredHit, StoredLink, StoredReadiness, } from "./types.js";
|
|
64
|
+
export type { AccountKey, AccountMessageFilter, AgentAnswer, BatchMessage, ChatActivity, ChatStats, ConversationBuild, ConversationHit, ConversationSummary, DeletionScope, Delta, IdentityRef, IngestedVia, LinkBatch, LinkedIdentity, LinkOptions, MessageFilter, PersonFacts, PersonRecord, PictureInput, Range, SenderChatStats, StoredAccount, StoredChatFilter, StoredHit, StoredLink, StoredReadiness, TaskAnalysisMark, TaskAnalysisMarkWrite, } from "./types.js";
|
|
65
65
|
export type { AccountVectors, VectorsStore } from "./vectors.js";
|
package/dist/store/index.d.ts
CHANGED
|
@@ -71,4 +71,4 @@ export type { StoreTaskStore } from "./sqlite/tasks.js";
|
|
|
71
71
|
export type { TagPreflight, VocabularyAlias, VocabularyAliasReceipt, VocabularyAudit, VocabularyAuditOptions, VocabularyCatalog, VocabularyEntry, VocabularyKind, VocabularyOptions, VocabularyResolution, VocabularyStore, VocabularyTarget, } from "./sqlite/vocabulary.js";
|
|
72
72
|
export type { BoardCounts, BoardOptions, NewWorkTask, RevisionCheck, TaskAssignment, TaskDetails, TaskEvent, TaskFields, TaskMove, TaskMoved, TaskRole, TaskType, WorkTaskType, } from "./sqlite/work-tasks.js";
|
|
73
73
|
export { TASK_EVENT_KINDS, TASK_ROLES } from "./sqlite/work-tasks.js";
|
|
74
|
-
export { type AccountAccounts, type AccountAttachments, type AccountCounters, type AccountKey, type AccountMentions, type AccountMessageFilter, type AccountMessages, type AccountPeople, type AccountsStore, type AccountVectors, type AttachmentsStore, type AttachmentTextEntry, type AttachmentView, type ChatStats, type ChatsStore, type ConversationsStore, type CountersStore, type DeletionScope, type Delta, type FileAttachment, type IdentityRef, type IngestedVia, type LinkedIdentity, type LinkOptions, MENTION_QUERY, MENTION_STATUSES, type MentionsStore, type MessageFilter, type MessageStore, type MessagesStore, OTHER_SOURCES, openStore, type PeopleStore, type PersonFacts, type PersonRecord, type QueriesStore, type Range, type RankedEvidence, type RankedStoreFound, type RankedStoreRow, type RankingEvidenceItem, type RankingEvidenceRequest, type RankingRequest, type RosterStore, type SearchCommand, type SearchesStore, type SearchIndexStore, type SearchRecord, type StoredAccount, type StoredChatFilter, type StoredHit, type StoredSearch, type StoredTag, type StoreOptions, type SyncStore, type TagFilter, type TagsStore, type TagTarget, type TaskTagTarget, type TextOrigin, type VectorsStore, } from "./store.js";
|
|
74
|
+
export { type AccountAccounts, type AccountAttachments, type AccountCounters, type AccountKey, type AccountMentions, type AccountMessageFilter, type AccountMessages, type AccountPeople, type AccountsStore, type AccountVectors, type AttachmentsStore, type AttachmentTextEntry, type AttachmentView, type ChatStats, type ChatsStore, type ConversationsStore, type CountersStore, type DeletionScope, type Delta, type FileAttachment, type IdentityRef, type IngestedVia, type LinkedIdentity, type LinkOptions, MENTION_QUERY, MENTION_STATUSES, type MentionsStore, type MessageFilter, type MessageStore, type MessagesStore, OTHER_SOURCES, openStore, type PeopleStore, type PersonFacts, type PersonRecord, type QueriesStore, type Range, type RankedEvidence, type RankedStoreFound, type RankedStoreRow, type RankingEvidenceItem, type RankingEvidenceRequest, type RankingRequest, type RosterStore, type SearchCommand, type SearchesStore, type SearchIndexStore, type SearchRecord, type StoredAccount, type StoredChatFilter, type StoredHit, type StoredSearch, type StoredTag, type StoreOptions, type SyncStore, type TagFilter, type TagsStore, type TagTarget, type TaskAnalysisMark, type TaskAnalysisMarkWrite, type TaskTagTarget, type TextOrigin, type VectorsStore, } from "./store.js";
|