@wirecat/cli-core 0.548.0 → 0.550.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 +232 -0
- package/dist/calendar/cli/index.d.ts +10 -1
- package/dist/calendar/cli/index.js +9 -3
- package/dist/calendar/cli/search-all.d.ts +17 -0
- package/dist/calendar/cli/search-all.js +72 -0
- package/dist/cli/answer-entity.d.ts +4 -0
- package/dist/cli/answer-entity.js +5 -0
- package/dist/cli/bot/context.d.ts +2 -0
- package/dist/cli/compact.d.ts +41 -0
- package/dist/cli/compact.js +313 -0
- package/dist/cli/context.d.ts +11 -2
- package/dist/cli/context.js +15 -6
- package/dist/cli/envelope.d.ts +5 -1
- package/dist/cli/envelope.js +15 -4
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +2 -0
- package/dist/cli/messenger/search-command.d.ts +10 -0
- package/dist/cli/messenger/search-command.js +19 -13
- package/dist/cli/messenger/store-maintenance-command.d.ts +1 -1
- package/dist/cli/program.d.ts +3 -0
- package/dist/cli/program.js +7 -1
- package/dist/domain/providers.d.ts +3 -0
- package/dist/domain/providers.js +20 -4
- package/dist/domain/ref-arguments.js +3 -7
- package/dist/domain/ref-summaries.d.ts +56 -0
- package/dist/domain/ref-summaries.js +63 -0
- package/dist/entities/index.d.ts +11 -0
- package/dist/entities/relationship.d.ts +2 -0
- package/dist/entities/relationship.js +2 -0
- package/dist/entities/task-results.d.ts +10 -0
- package/dist/entities/task-results.js +1 -0
- package/dist/errors/reasons.js +2 -0
- package/dist/mail/partial.d.ts +9 -3
- package/dist/mail/partial.js +35 -10
- package/dist/mail/source.d.ts +7 -1
- package/dist/mail/sync.js +1 -1
- package/dist/output-detail.js +9 -5
- package/dist/output.d.ts +5 -1
- package/dist/output.js +29 -4
- package/dist/services/proposals/kinds/relationship-update.d.ts +4 -0
- package/dist/services/proposals/kinds/relationship-update.js +53 -0
- package/dist/services/proposals/registry.js +2 -0
- package/dist/store/decisions.d.ts +3 -1
- package/dist/store/decisions.js +7 -1
- package/dist/store/index.d.ts +2 -1
- package/dist/store/sqlite/email-locations.d.ts +22 -1
- package/dist/store/sqlite/email-locations.js +29 -2
- package/dist/store/sqlite/emails.d.ts +3 -7
- package/dist/store/sqlite/emails.js +4 -2
- package/dist/store/sqlite/evidence-sources.d.ts +2 -2
- package/dist/store/sqlite/evidence-sources.js +41 -77
- package/dist/store/sqlite/knowledge.d.ts +17 -23
- package/dist/store/sqlite/knowledge.js +13 -13
- package/dist/store/sqlite/manifest.js +2 -0
- package/dist/store/sqlite/migrations.generated.js +12 -0
- package/dist/store/sqlite/notes.d.ts +5 -0
- package/dist/store/sqlite/notes.js +6 -2
- package/dist/store/sqlite/open-store.js +2 -0
- package/dist/store/sqlite/proposed-actions.d.ts +1 -1
- package/dist/store/sqlite/proposed-actions.js +10 -2
- package/dist/store/sqlite/ref-summaries.d.ts +17 -0
- package/dist/store/sqlite/ref-summaries.js +378 -0
- package/dist/store/sqlite/relation-proposals.d.ts +40 -0
- package/dist/store/sqlite/relation-proposals.js +88 -1
- package/dist/store/sqlite/schema.d.ts +16 -0
- package/dist/store/sqlite/schema.js +1 -0
- package/dist/store/sqlite/suggested-links.d.ts +10 -0
- package/dist/store/sqlite/suggested-links.js +68 -0
- package/dist/store/store.d.ts +3 -0
- package/dist/store/store.js +5 -0
- package/package.json +3 -3
- package/parity.json +4 -0
package/dist/cli/program.js
CHANGED
|
@@ -2,6 +2,7 @@ import { join } from "node:path";
|
|
|
2
2
|
import { CliError, errorObjectOf, errorRecordOf, exitCodeFor, exitCodeOfError, processStreams, renderError, resolvePaths, } from "@wirecat/cli-base";
|
|
3
3
|
import { metaOf, refuseJoinedPath } from "@wirecat/cli-base/commands";
|
|
4
4
|
import { skillHint } from "@wirecat/cli-base/skill";
|
|
5
|
+
import { COMPACT_FLAG } from "@wirecat/cli-base/standards";
|
|
5
6
|
import { Command, Help } from "commander";
|
|
6
7
|
import { relayCommand } from "../background/relay.js";
|
|
7
8
|
import { failingNotice, scheduledRuns } from "../background/schedules.js";
|
|
@@ -17,6 +18,7 @@ import { liftOptionsAfterDashes } from "./after-dashes.js";
|
|
|
17
18
|
import { logParsedArgv, logVersion } from "./argv-log.js";
|
|
18
19
|
import { commandAvailability } from "./command-availability.js";
|
|
19
20
|
import { commandPathOf } from "./command-contract.js";
|
|
21
|
+
import { entityRows } from "./compact.js";
|
|
20
22
|
import { outputFor, provide } from "./context.js";
|
|
21
23
|
import { refuseByCommandPermissions, refuseWriteBeforeConnecting } from "./early-refusal.js";
|
|
22
24
|
import { changesAuthorityAt, changesAuthorityNow, isLogin, isStoreChange, judgedEffects, judgedTargets, storeChangeRefusal, } from "./effects.js";
|
|
@@ -73,6 +75,7 @@ export const createProgram = (definition, { out, err } = {}) => {
|
|
|
73
75
|
.option("--max-output-bytes <bytes>", "maximum machine output bytes (default: 4194304; 0 disables)")
|
|
74
76
|
.option("--brief", `${found} as reference, title, time, ${messenger ? "sender" : "author"} and excerpt only`)
|
|
75
77
|
.option("--full", `${found} with their whole text and every provider field`)
|
|
78
|
+
.option(COMPACT_FLAG, "one line per item for an agent: reference first, text last, paging and next steps as # lines")
|
|
76
79
|
.option("--dry-run", "preview parsed arguments and permissions before running the action")
|
|
77
80
|
.option("--yes", "go ahead without the question an ask level puts before a write")
|
|
78
81
|
.option("--record", "keep this run even where the config says record false; record_input false leaves the typed words out")
|
|
@@ -82,6 +85,8 @@ export const createProgram = (definition, { out, err } = {}) => {
|
|
|
82
85
|
program.addCommand(command);
|
|
83
86
|
configure?.(program);
|
|
84
87
|
if (!contractsChecked.has(definition)) {
|
|
88
|
+
if (definition.rows)
|
|
89
|
+
entityRows(definition.rows);
|
|
85
90
|
assertSharedContracts(program);
|
|
86
91
|
contractsChecked.add(definition);
|
|
87
92
|
}
|
|
@@ -136,7 +141,7 @@ export const run = async (argv, definition, options = {}) => {
|
|
|
136
141
|
useAsProcessRun(outer);
|
|
137
142
|
}
|
|
138
143
|
};
|
|
139
|
-
const isMachine = (words) => words.some((word) => word === "--json" || word.startsWith("--json=") || word === "--jsonl");
|
|
144
|
+
const isMachine = (words) => words.some((word) => word === "--json" || word.startsWith("--json=") || word === "--jsonl" || word === COMPACT_FLAG);
|
|
140
145
|
const runOnce = async (argv, definition, options) => {
|
|
141
146
|
const streams = options.streams ?? processStreams;
|
|
142
147
|
if (!options.streams)
|
|
@@ -218,6 +223,7 @@ const runOnce = async (argv, definition, options) => {
|
|
|
218
223
|
commandSignal: control.signal,
|
|
219
224
|
trackCloseable: control.trackCloseable,
|
|
220
225
|
app: definition.app,
|
|
226
|
+
...(definition.rows ? { rows: definition.rows } : {}),
|
|
221
227
|
platform: options.platform ?? process.platform,
|
|
222
228
|
exit,
|
|
223
229
|
runCommand: (words, given) => run(words, definition, given),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Provider } from "./models.js";
|
|
2
|
+
import { type RefKindName } from "./references.js";
|
|
2
3
|
/** What a search reads; the same names `--only`, `searched` and `skipped` use. */
|
|
3
4
|
export declare const KINDS: readonly ["messages", "mail", "notes", "documents", "meetings", "events"];
|
|
4
5
|
export type Kind = (typeof KINDS)[number];
|
|
@@ -8,6 +9,8 @@ export interface ProviderEntry {
|
|
|
8
9
|
aliases: readonly string[];
|
|
9
10
|
/** The CLI that wrote the entry (`zm`, `drive`). One CLI may write several providers, so it is not an alias. */
|
|
10
11
|
command?: string;
|
|
12
|
+
/** The read command, after `<command> <profile>`, that opens a ref of each kind: `{ chat: "messages list" }`. */
|
|
13
|
+
opens?: Partial<Record<RefKindName, string>>;
|
|
11
14
|
}
|
|
12
15
|
/** The seed: what cli-core knows before any CLI has written its entry into the store. */
|
|
13
16
|
export declare const PROVIDERS: readonly ProviderEntry[];
|
package/dist/domain/providers.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
|
+
import { REF_KINDS } from "./references.js";
|
|
2
3
|
/** What a search reads; the same names `--only`, `searched` and `skipped` use. */
|
|
3
4
|
export const KINDS = ["messages", "mail", "notes", "documents", "meetings", "events"];
|
|
5
|
+
const MESSENGER_OPENS = { chat: "messages list", message: "messages show", contact: "contacts show" };
|
|
4
6
|
/** The seed: what cli-core knows before any CLI has written its entry into the store. */
|
|
5
7
|
export const PROVIDERS = [
|
|
6
|
-
{ name: "telegram", kind: "messages", aliases: ["tg"], command: "tg" },
|
|
8
|
+
{ name: "telegram", kind: "messages", aliases: ["tg"], command: "tg", opens: MESSENGER_OPENS },
|
|
7
9
|
{ name: "telegram-bot", kind: "messages", aliases: ["tg-bot"] },
|
|
8
|
-
{ name: "max", kind: "messages", aliases: [], command: "max" },
|
|
10
|
+
{ name: "max", kind: "messages", aliases: [], command: "max", opens: MESSENGER_OPENS },
|
|
9
11
|
{ name: "max-bot", kind: "messages", aliases: [] },
|
|
10
|
-
{ name: "whatsapp", kind: "messages", aliases: ["wa"], command: "wa" },
|
|
12
|
+
{ name: "whatsapp", kind: "messages", aliases: ["wa"], command: "wa", opens: MESSENGER_OPENS },
|
|
11
13
|
{ name: "whatsapp-bot", kind: "messages", aliases: ["wa-bot"] },
|
|
12
14
|
{ name: "email", kind: "mail", aliases: [] },
|
|
13
15
|
{ name: "notes", kind: "notes", aliases: [] },
|
|
@@ -58,11 +60,23 @@ const invalid = (message) => {
|
|
|
58
60
|
const wordOf = (value, what) => typeof value === "string" && WORD.test(value)
|
|
59
61
|
? value
|
|
60
62
|
: invalid(`${what} is lowercase letters, digits, - and _ — not ${JSON.stringify(value)}`);
|
|
63
|
+
const OPEN_KINDS = new Set(REF_KINDS.map(({ type }) => type).filter((type) => type !== "entity"));
|
|
64
|
+
const COMMAND_WORDS = /^[a-z0-9][a-z0-9_-]*( [a-z0-9][a-z0-9_-]*)*$/;
|
|
65
|
+
/** A kind this build does not know is dropped, not refused: a newer CLI may open kinds added after this release. */
|
|
66
|
+
const opensOf = (value, named) => {
|
|
67
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
68
|
+
return invalid(`provider ${named}'s opens maps a kind of reference to a command`);
|
|
69
|
+
const known = Object.entries(value).filter(([kind]) => OPEN_KINDS.has(kind));
|
|
70
|
+
for (const [kind, command] of known)
|
|
71
|
+
if (typeof command !== "string" || !COMMAND_WORDS.test(command))
|
|
72
|
+
invalid(`provider ${named} opens a ${kind} with command words, not ${JSON.stringify(command)}`);
|
|
73
|
+
return Object.fromEntries(known);
|
|
74
|
+
};
|
|
61
75
|
/** An entry as a CLI or the store gives it, checked: anything else is a `validation_error`. */
|
|
62
76
|
export const providerEntryOf = (value) => {
|
|
63
77
|
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
64
78
|
return invalid("a provider entry is an object");
|
|
65
|
-
const { name, kind, aliases = [], command } = value;
|
|
79
|
+
const { name, kind, aliases = [], command, opens } = value;
|
|
66
80
|
const named = wordOf(name, "a provider's name");
|
|
67
81
|
if (typeof kind !== "string" || !KINDS.includes(kind))
|
|
68
82
|
invalid(`provider ${named}'s kind is one of ${KINDS.join(", ")} — not ${JSON.stringify(kind)}`);
|
|
@@ -73,6 +87,7 @@ export const providerEntryOf = (value) => {
|
|
|
73
87
|
kind: kind,
|
|
74
88
|
aliases: [...new Set(aliases.map((alias) => wordOf(alias, `an alias of ${named}`)))],
|
|
75
89
|
...(command === undefined ? {} : { command: wordOf(command, `the command of ${named}`) }),
|
|
90
|
+
...(opens === undefined ? {} : { opens: opensOf(opens, named) }),
|
|
76
91
|
};
|
|
77
92
|
};
|
|
78
93
|
const describe = ({ name, kind, command }) => `${name} (${kind}${command ? `, from ${command}` : ""})`;
|
|
@@ -87,6 +102,7 @@ const joined = (known, entry) => {
|
|
|
87
102
|
...same,
|
|
88
103
|
aliases: [...new Set([...same.aliases, ...entry.aliases])],
|
|
89
104
|
...(entry.command ? { command: entry.command } : {}),
|
|
105
|
+
...(entry.opens ? { opens: { ...same.opens, ...entry.opens } } : {}),
|
|
90
106
|
}
|
|
91
107
|
: entry;
|
|
92
108
|
const others = known.filter((one) => one !== same);
|
|
@@ -5,18 +5,14 @@ const TAKES = {
|
|
|
5
5
|
chat: "a chat: chat:<provider>/<account>/<chat>, its id or its title",
|
|
6
6
|
message: "a message: msg:<provider>/<account>/<chat>/<message>, or a chat and the message's id",
|
|
7
7
|
};
|
|
8
|
-
const OPENS = {
|
|
9
|
-
chat: "messages list",
|
|
10
|
-
message: "messages show",
|
|
11
|
-
contact: "contacts show",
|
|
12
|
-
};
|
|
13
8
|
const article = (word) => (/^[aeiou]/.test(word) ? "an" : "a");
|
|
14
9
|
const wrongKind = (text, kind, takes) => new CliError("validation_error", `${text} names ${article(kind)} ${kind}; this takes ${TAKES[takes]}`, {
|
|
15
10
|
reason: "ref.wrong-kind",
|
|
16
11
|
});
|
|
17
12
|
const wrongProvider = (text, provider, kind) => {
|
|
18
|
-
const
|
|
19
|
-
const
|
|
13
|
+
const entry = providerNamed(provider);
|
|
14
|
+
const command = entry?.command;
|
|
15
|
+
const opens = kind === "entity" ? undefined : entry?.opens?.[kind];
|
|
20
16
|
const how = command && opens ? `open it with ${command} <profile> ${opens} ${text}` : "open it with its own CLI";
|
|
21
17
|
const label = providerLabel(provider);
|
|
22
18
|
return new CliError("validation_error", `${text} is ${article(label.toLowerCase())} ${label} ${kind}; ${how}`, {
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { RefKindName } from "./references.js";
|
|
2
|
+
export type RefState = "available" | "deleted" | "unavailable";
|
|
3
|
+
/** A message or chat of the `email` provider is mail, so it reads as `email` and `email_thread`. */
|
|
4
|
+
export type RefSummaryKind = RefKindName | "email" | "email_thread" | "unknown";
|
|
5
|
+
/** What a reference names, read from the store in one batch so a page of refs costs one query per kind. */
|
|
6
|
+
export interface RefSummary {
|
|
7
|
+
/** Canonical spelling; the map key is the text the caller passed. */
|
|
8
|
+
ref: string;
|
|
9
|
+
kind: RefSummaryKind;
|
|
10
|
+
/** One line, at most 120 characters, never a message body. `null` when the store has no title. */
|
|
11
|
+
title: string | null;
|
|
12
|
+
/** What holds it, one line: a message's chat title, a task's project, a document's provider label. */
|
|
13
|
+
in: string | null;
|
|
14
|
+
/** ISO 8601 UTC. */
|
|
15
|
+
at: string | null;
|
|
16
|
+
provider: string | null;
|
|
17
|
+
/** The external id of the account that holds it, for `ProfilesFor`; `null` when it belongs to no account. */
|
|
18
|
+
account: string | null;
|
|
19
|
+
/** The CLI that opens it (`max`, `tg`, `drive`); `null` when no CLI has registered the provider. */
|
|
20
|
+
tool: string | null;
|
|
21
|
+
state: RefState;
|
|
22
|
+
/** Only with `{ excerpt: true }`: at most 280 characters of the body. */
|
|
23
|
+
excerpt?: string | null;
|
|
24
|
+
}
|
|
25
|
+
export interface RefsStore {
|
|
26
|
+
/** One query per kind present. Never throws for a bad ref: it comes back `unknown`, `unavailable`. */
|
|
27
|
+
summaries(refs: readonly string[], options?: {
|
|
28
|
+
excerpt?: boolean;
|
|
29
|
+
}): Promise<Map<string, RefSummary>>;
|
|
30
|
+
}
|
|
31
|
+
/** The caller's answer to "which profile opens this": one signed in, or the choices and why there is not one. */
|
|
32
|
+
export type ProfilesFor = (provider: string, account: string | null) => {
|
|
33
|
+
profile: string;
|
|
34
|
+
} | {
|
|
35
|
+
why: string;
|
|
36
|
+
profiles: string[];
|
|
37
|
+
};
|
|
38
|
+
export interface RefOpen {
|
|
39
|
+
/** Ready to run; contains `<profile>` when `profiles` is not exactly one. `null` when no CLI opens it. */
|
|
40
|
+
argv: string[] | null;
|
|
41
|
+
profiles: string[];
|
|
42
|
+
why: string | null;
|
|
43
|
+
}
|
|
44
|
+
export declare const PROFILE_PLACEHOLDER = "<profile>";
|
|
45
|
+
/** A title from someone else's text, as one line: a newline in it would break every row it is printed in. */
|
|
46
|
+
export declare const oneLine: (text: string) => string;
|
|
47
|
+
/**
|
|
48
|
+
* The command that opens a ref. A registered provider opens its own kinds; anything else opens with the caller's
|
|
49
|
+
* `local` words (memo's `notes show`). The profile is never picked: with none or several, `argv` keeps `<profile>`.
|
|
50
|
+
*/
|
|
51
|
+
export declare const openOf: (summary: RefSummary, ports: {
|
|
52
|
+
profilesFor: ProfilesFor;
|
|
53
|
+
local?: Partial<Record<RefKindName, string[]>>;
|
|
54
|
+
}) => RefOpen;
|
|
55
|
+
/** `Chat «Сайт + автоматизация» (MAX)`: the one label every review row, status line and `show` prints. */
|
|
56
|
+
export declare const refLabel: (summary: RefSummary) => string;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { singleLine } from "@wirecat/cli-base";
|
|
2
|
+
import { providerNamed } from "./providers.js";
|
|
3
|
+
export const PROFILE_PLACEHOLDER = "<profile>";
|
|
4
|
+
/** A title from someone else's text, as one line: a newline in it would break every row it is printed in. */
|
|
5
|
+
export const oneLine = (text) => singleLine(text.replace(/\s+/g, " ").trim());
|
|
6
|
+
/** Mail is a message and a chat to the provider registry: `opens` is keyed by the reference kind. */
|
|
7
|
+
const openKind = (kind) => kind === "email" ? "message" : kind === "email_thread" ? "chat" : kind === "unknown" ? undefined : kind;
|
|
8
|
+
/**
|
|
9
|
+
* The command that opens a ref. A registered provider opens its own kinds; anything else opens with the caller's
|
|
10
|
+
* `local` words (memo's `notes show`). The profile is never picked: with none or several, `argv` keeps `<profile>`.
|
|
11
|
+
*/
|
|
12
|
+
export const openOf = (summary, ports) => {
|
|
13
|
+
const kind = openKind(summary.kind);
|
|
14
|
+
if (kind === undefined)
|
|
15
|
+
return { argv: null, profiles: [], why: `${summary.ref} is not a reference` };
|
|
16
|
+
const entry = summary.provider === null ? undefined : providerNamed(summary.provider);
|
|
17
|
+
const words = entry?.command === undefined ? undefined : entry.opens?.[kind];
|
|
18
|
+
if (entry?.command !== undefined && words !== undefined && summary.provider !== null) {
|
|
19
|
+
const command = [...words.split(" "), summary.ref];
|
|
20
|
+
const asked = ports.profilesFor(summary.provider, summary.account);
|
|
21
|
+
return "profile" in asked
|
|
22
|
+
? { argv: [entry.command, asked.profile, ...command], profiles: [asked.profile], why: null }
|
|
23
|
+
: { argv: [entry.command, PROFILE_PLACEHOLDER, ...command], profiles: asked.profiles, why: asked.why };
|
|
24
|
+
}
|
|
25
|
+
const local = ports.local?.[kind];
|
|
26
|
+
if (local !== undefined)
|
|
27
|
+
return { argv: [...local, summary.ref], profiles: [], why: null };
|
|
28
|
+
return {
|
|
29
|
+
argv: null,
|
|
30
|
+
profiles: [],
|
|
31
|
+
why: summary.provider === null
|
|
32
|
+
? `nothing here opens a ${kind}`
|
|
33
|
+
: `no CLI has registered ${summary.provider} in this store yet; run its CLI once`,
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
const WORDS = {
|
|
37
|
+
message: "Message",
|
|
38
|
+
chat: "Chat",
|
|
39
|
+
contact: "Contact",
|
|
40
|
+
folder: "Folder",
|
|
41
|
+
meeting: "Meeting",
|
|
42
|
+
event: "Event",
|
|
43
|
+
note: "Note",
|
|
44
|
+
document: "Document",
|
|
45
|
+
person: "Person",
|
|
46
|
+
organization: "Organization",
|
|
47
|
+
project: "Project",
|
|
48
|
+
task: "Task",
|
|
49
|
+
memory: "Memory",
|
|
50
|
+
fact: "Fact",
|
|
51
|
+
bot: "Bot",
|
|
52
|
+
email: "Email",
|
|
53
|
+
email_thread: "Email thread",
|
|
54
|
+
unknown: "Unknown",
|
|
55
|
+
};
|
|
56
|
+
/** `Chat «Сайт + автоматизация» (MAX)`: the one label every review row, status line and `show` prints. */
|
|
57
|
+
export const refLabel = (summary) => {
|
|
58
|
+
const named = summary.title === null ? summary.ref : `«${oneLine(summary.title)}»`;
|
|
59
|
+
const notes = [summary.in === null ? null : oneLine(summary.in), summary.state === "available" ? null : summary.state]
|
|
60
|
+
.filter((one) => one !== null)
|
|
61
|
+
.join(", ");
|
|
62
|
+
return `${WORDS[summary.kind]} ${named}${notes === "" ? "" : ` (${notes})`}`;
|
|
63
|
+
};
|
package/dist/entities/index.d.ts
CHANGED
|
@@ -4911,6 +4911,7 @@ export declare const ENTITIES: {
|
|
|
4911
4911
|
readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
|
|
4912
4912
|
readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
4913
4913
|
readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
4914
|
+
readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
4914
4915
|
readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
4915
4916
|
readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
4916
4917
|
readonly confirmed: v.BooleanSchema<undefined>;
|
|
@@ -4925,6 +4926,7 @@ export declare const ENTITIES: {
|
|
|
4925
4926
|
readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
|
|
4926
4927
|
readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
4927
4928
|
readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
4929
|
+
readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
4928
4930
|
readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
4929
4931
|
readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
4930
4932
|
readonly confirmed: v.BooleanSchema<undefined>;
|
|
@@ -5023,6 +5025,7 @@ export declare const ENTITIES: {
|
|
|
5023
5025
|
readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
|
|
5024
5026
|
readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5025
5027
|
readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5028
|
+
readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5026
5029
|
readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5027
5030
|
readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5028
5031
|
readonly confirmed: v.BooleanSchema<undefined>;
|
|
@@ -5037,6 +5040,7 @@ export declare const ENTITIES: {
|
|
|
5037
5040
|
readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
|
|
5038
5041
|
readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5039
5042
|
readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5043
|
+
readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5040
5044
|
readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5041
5045
|
readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5042
5046
|
readonly confirmed: v.BooleanSchema<undefined>;
|
|
@@ -5136,6 +5140,7 @@ export declare const ENTITIES: {
|
|
|
5136
5140
|
readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
|
|
5137
5141
|
readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5138
5142
|
readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5143
|
+
readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5139
5144
|
readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5140
5145
|
readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5141
5146
|
readonly confirmed: v.BooleanSchema<undefined>;
|
|
@@ -5150,6 +5155,7 @@ export declare const ENTITIES: {
|
|
|
5150
5155
|
readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
|
|
5151
5156
|
readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5152
5157
|
readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5158
|
+
readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5153
5159
|
readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5154
5160
|
readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5155
5161
|
readonly confirmed: v.BooleanSchema<undefined>;
|
|
@@ -5277,6 +5283,7 @@ export declare const ENTITIES: {
|
|
|
5277
5283
|
readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
|
|
5278
5284
|
readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5279
5285
|
readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5286
|
+
readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5280
5287
|
readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5281
5288
|
readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5282
5289
|
readonly confirmed: v.BooleanSchema<undefined>;
|
|
@@ -5291,6 +5298,7 @@ export declare const ENTITIES: {
|
|
|
5291
5298
|
readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
|
|
5292
5299
|
readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5293
5300
|
readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5301
|
+
readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5294
5302
|
readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5295
5303
|
readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5296
5304
|
readonly confirmed: v.BooleanSchema<undefined>;
|
|
@@ -5383,6 +5391,7 @@ export declare const ENTITIES: {
|
|
|
5383
5391
|
readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
|
|
5384
5392
|
readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5385
5393
|
readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5394
|
+
readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5386
5395
|
readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5387
5396
|
readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5388
5397
|
readonly confirmed: v.BooleanSchema<undefined>;
|
|
@@ -5397,6 +5406,7 @@ export declare const ENTITIES: {
|
|
|
5397
5406
|
readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
|
|
5398
5407
|
readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5399
5408
|
readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5409
|
+
readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5400
5410
|
readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5401
5411
|
readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
5402
5412
|
readonly confirmed: v.BooleanSchema<undefined>;
|
|
@@ -6600,6 +6610,7 @@ export declare const ENTITIES: {
|
|
|
6600
6610
|
readonly to: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
|
|
6601
6611
|
readonly kind: v.PicklistSchema<readonly ["member-of", "related-to"], undefined>;
|
|
6602
6612
|
readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
6613
|
+
readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
6603
6614
|
readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
6604
6615
|
readonly confirmed: v.BooleanSchema<undefined>;
|
|
6605
6616
|
readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
@@ -6,6 +6,8 @@ export declare const relationship: v.StrictObjectSchema<{
|
|
|
6
6
|
readonly to: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
|
|
7
7
|
readonly kind: v.PicklistSchema<readonly ["member-of", "related-to"], undefined>;
|
|
8
8
|
readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
9
|
+
/** A purpose from memo's config; `null` means `work`. */
|
|
10
|
+
readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
9
11
|
readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
10
12
|
/** An agent's suggestion stays unconfirmed, and `weak`, until the owner confirms it. */
|
|
11
13
|
readonly confirmed: v.BooleanSchema<undefined>;
|
|
@@ -8,6 +8,8 @@ export const relationship = v.strictObject({
|
|
|
8
8
|
to: reference,
|
|
9
9
|
kind: v.picklist(RELATION_KINDS),
|
|
10
10
|
role: v.nullable(text),
|
|
11
|
+
/** A purpose from memo's config; `null` means `work`. */
|
|
12
|
+
purpose: v.nullable(text),
|
|
11
13
|
evidence: v.nullable(text),
|
|
12
14
|
/** An agent's suggestion stays unconfirmed, and `weak`, until the owner confirms it. */
|
|
13
15
|
confirmed: v.boolean(),
|
|
@@ -53,6 +53,7 @@ export declare const taskCard: v.StrictObjectSchema<{
|
|
|
53
53
|
readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
|
|
54
54
|
readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
55
55
|
readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
56
|
+
readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
56
57
|
readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
57
58
|
readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
58
59
|
readonly confirmed: v.BooleanSchema<undefined>;
|
|
@@ -67,6 +68,7 @@ export declare const taskCard: v.StrictObjectSchema<{
|
|
|
67
68
|
readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
|
|
68
69
|
readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
69
70
|
readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
71
|
+
readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
70
72
|
readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
71
73
|
readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
72
74
|
readonly confirmed: v.BooleanSchema<undefined>;
|
|
@@ -165,6 +167,7 @@ export declare const taskCreated: v.StrictObjectSchema<{
|
|
|
165
167
|
readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
|
|
166
168
|
readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
167
169
|
readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
170
|
+
readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
168
171
|
readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
169
172
|
readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
170
173
|
readonly confirmed: v.BooleanSchema<undefined>;
|
|
@@ -179,6 +182,7 @@ export declare const taskCreated: v.StrictObjectSchema<{
|
|
|
179
182
|
readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
|
|
180
183
|
readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
181
184
|
readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
185
|
+
readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
182
186
|
readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
183
187
|
readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
184
188
|
readonly confirmed: v.BooleanSchema<undefined>;
|
|
@@ -278,6 +282,7 @@ export declare const taskLinks: v.UnionSchema<[v.StrictObjectSchema<{
|
|
|
278
282
|
readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
|
|
279
283
|
readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
280
284
|
readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
285
|
+
readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
281
286
|
readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
282
287
|
readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
283
288
|
readonly confirmed: v.BooleanSchema<undefined>;
|
|
@@ -292,6 +297,7 @@ export declare const taskLinks: v.UnionSchema<[v.StrictObjectSchema<{
|
|
|
292
297
|
readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
|
|
293
298
|
readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
294
299
|
readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
300
|
+
readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
295
301
|
readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
296
302
|
readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
297
303
|
readonly confirmed: v.BooleanSchema<undefined>;
|
|
@@ -419,6 +425,7 @@ export declare const taskMerge: v.StrictObjectSchema<{
|
|
|
419
425
|
readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
|
|
420
426
|
readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
421
427
|
readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
428
|
+
readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
422
429
|
readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
423
430
|
readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
424
431
|
readonly confirmed: v.BooleanSchema<undefined>;
|
|
@@ -433,6 +440,7 @@ export declare const taskMerge: v.StrictObjectSchema<{
|
|
|
433
440
|
readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
|
|
434
441
|
readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
435
442
|
readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
443
|
+
readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
436
444
|
readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
437
445
|
readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
438
446
|
readonly confirmed: v.BooleanSchema<undefined>;
|
|
@@ -525,6 +533,7 @@ export declare const taskUnmerge: v.StrictObjectSchema<{
|
|
|
525
533
|
readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
|
|
526
534
|
readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
527
535
|
readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
536
|
+
readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
528
537
|
readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
529
538
|
readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
530
539
|
readonly confirmed: v.BooleanSchema<undefined>;
|
|
@@ -539,6 +548,7 @@ export declare const taskUnmerge: v.StrictObjectSchema<{
|
|
|
539
548
|
readonly origin: v.PicklistSchema<readonly ["file", "owner", "suggested", "message", "calendar"], undefined>;
|
|
540
549
|
readonly target_text: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
541
550
|
readonly role: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
551
|
+
readonly purpose: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
542
552
|
readonly evidence: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
543
553
|
readonly provenance: v.NullableSchema<v.StringSchema<undefined>, undefined>;
|
|
544
554
|
readonly confirmed: v.BooleanSchema<undefined>;
|
package/dist/errors/reasons.js
CHANGED
|
@@ -50,6 +50,7 @@ const OTHERS = [
|
|
|
50
50
|
validation("extraction.text-too-long", "the text is too long for memory extraction"),
|
|
51
51
|
validation("file.locked", "another process holds the file's lock"),
|
|
52
52
|
["mail.source-failed", "provider_error", "the mail server refused or did not complete a request"],
|
|
53
|
+
["mail.search-refused", "provider_error", "the mail server refused to search a folder"],
|
|
53
54
|
["mirror.partial", "provider_error", "some mirrored files were not synced"],
|
|
54
55
|
["profile.wrong-account", "authentication_error", "the signed-in source account does not match this profile"],
|
|
55
56
|
validation("push.too-many-files", "the folder holds more files than one push takes"),
|
|
@@ -144,6 +145,7 @@ const OTHERS = [
|
|
|
144
145
|
],
|
|
145
146
|
validation("name.invalid", "the name is not valid"),
|
|
146
147
|
validation("output.choose-fields", "at a terminal, --json names the fields it prints"),
|
|
148
|
+
validation("output.conflict", "two output switches ask for different output"),
|
|
147
149
|
validation("output.one-object", "a report is one JSON object; --jsonl streams lists only"),
|
|
148
150
|
validation("output.unknown-field", "the answer has no field by that name"),
|
|
149
151
|
validation("name.taken", "the name is already used"),
|
package/dist/mail/partial.d.ts
CHANGED
|
@@ -6,16 +6,22 @@ import type { MailSource } from "./source.js";
|
|
|
6
6
|
export declare const PARTIAL = "partial";
|
|
7
7
|
/** How many partial emails one sync reads whole at most; the rest wait for the next run. */
|
|
8
8
|
export declare const PARTIAL_PER_RUN = 100;
|
|
9
|
+
/** Failed reads after which a partial email is given up on (`partial.stuck`) and stays as it is. */
|
|
10
|
+
export declare const PARTIAL_TRIES = 3;
|
|
9
11
|
export interface PartialOutcome {
|
|
10
12
|
/** Read whole this run, the mark cleared. */
|
|
11
13
|
completed: number;
|
|
12
|
-
/** Still partial after this run. */
|
|
14
|
+
/** Still partial after this run, tried again by the next. */
|
|
13
15
|
left: number;
|
|
16
|
+
/** Given up on: the server no longer gives this email at its stored place. They stay readable as they are. */
|
|
17
|
+
stuck: number;
|
|
14
18
|
}
|
|
15
19
|
/**
|
|
16
20
|
* Reads whole the emails a server search stored from their first bytes, so the archive holds their attachments too.
|
|
17
|
-
* Sync never reads stored mail again, so without this step a partial email would stay partial.
|
|
18
|
-
*
|
|
21
|
+
* Sync never reads stored mail again, so without this step a partial email would stay partial. A source with `find`
|
|
22
|
+
* finds them by id; elsewhere they are read at their newest stored location under its UIDVALIDITY, trusted only when
|
|
23
|
+
* `identify` gives the stored id for the bytes read there. Each failed read is counted, and after `PARTIAL_TRIES`, or
|
|
24
|
+
* at once when the place holds another message, the email is left partial for good.
|
|
19
25
|
*/
|
|
20
26
|
export declare const completePartial: ({ store, source, accountId, address, folders, maxBodyChars, maxAttachmentChars, loadEngine, now, take, signal, }: {
|
|
21
27
|
store: MessageStore;
|
package/dist/mail/partial.js
CHANGED
|
@@ -6,15 +6,29 @@ import { parseMail } from "./parse.js";
|
|
|
6
6
|
export const PARTIAL = "partial";
|
|
7
7
|
/** How many partial emails one sync reads whole at most; the rest wait for the next run. */
|
|
8
8
|
export const PARTIAL_PER_RUN = 100;
|
|
9
|
+
/** Failed reads after which a partial email is given up on (`partial.stuck`) and stays as it is. */
|
|
10
|
+
export const PARTIAL_TRIES = 3;
|
|
9
11
|
/**
|
|
10
12
|
* Reads whole the emails a server search stored from their first bytes, so the archive holds their attachments too.
|
|
11
|
-
* Sync never reads stored mail again, so without this step a partial email would stay partial.
|
|
12
|
-
*
|
|
13
|
+
* Sync never reads stored mail again, so without this step a partial email would stay partial. A source with `find`
|
|
14
|
+
* finds them by id; elsewhere they are read at their newest stored location under its UIDVALIDITY, trusted only when
|
|
15
|
+
* `identify` gives the stored id for the bytes read there. Each failed read is counted, and after `PARTIAL_TRIES`, or
|
|
16
|
+
* at once when the place holds another message, the email is left partial for good.
|
|
13
17
|
*/
|
|
14
18
|
export const completePartial = async ({ store, source, accountId, address, folders, maxBodyChars, maxAttachmentChars, loadEngine, now, take, signal, }) => {
|
|
15
|
-
const { emails, total } = await store.mail.partialEmails(accountId, PARTIAL_PER_RUN);
|
|
19
|
+
const { emails, total, stuck } = await store.mail.partialEmails(accountId, PARTIAL_PER_RUN);
|
|
16
20
|
if (emails.length === 0)
|
|
17
|
-
return { completed: 0, left: 0 };
|
|
21
|
+
return { completed: 0, left: 0, stuck };
|
|
22
|
+
let given = 0;
|
|
23
|
+
// A failed read is counted, so an email the server no longer gives is not read whole on every sync forever.
|
|
24
|
+
const failed = async (email, forGood = false) => {
|
|
25
|
+
const mark = (email.metadata?.[PARTIAL] ?? {});
|
|
26
|
+
const tries = Number(mark.tries ?? 0) + 1;
|
|
27
|
+
const gaveUp = forGood || tries >= PARTIAL_TRIES;
|
|
28
|
+
await store.mail.markPartial(accountId, email.externalId, { ...mark, tries, ...(gaveUp ? { stuck: true } : {}) });
|
|
29
|
+
if (gaveUp)
|
|
30
|
+
given++;
|
|
31
|
+
};
|
|
18
32
|
const byId = new Map(emails.map((email) => [email.externalId, email]));
|
|
19
33
|
const items = new Map();
|
|
20
34
|
if (source.find) {
|
|
@@ -23,21 +37,27 @@ export const completePartial = async ({ store, source, accountId, address, folde
|
|
|
23
37
|
if (left.length === 0)
|
|
24
38
|
break;
|
|
25
39
|
if (take?.())
|
|
26
|
-
return { completed: 0, left: total };
|
|
40
|
+
return { completed: 0, left: total, stuck };
|
|
27
41
|
for (const found of await source.find(left, folder)) {
|
|
28
42
|
const email = byId.get(found.messageId);
|
|
29
43
|
if (email && !items.has(email))
|
|
30
44
|
items.set(email, found);
|
|
31
45
|
}
|
|
32
46
|
}
|
|
47
|
+
for (const email of emails)
|
|
48
|
+
if (!items.has(email))
|
|
49
|
+
await failed(email);
|
|
33
50
|
}
|
|
34
51
|
else if (source.identify)
|
|
35
52
|
for (const email of emails) {
|
|
36
53
|
const place = email.locations[0];
|
|
37
|
-
if (place)
|
|
54
|
+
if (!place)
|
|
55
|
+
await failed(email, true);
|
|
56
|
+
else
|
|
38
57
|
items.set(email, {
|
|
39
58
|
uid: String(place.uid),
|
|
40
59
|
folder: place.folder,
|
|
60
|
+
validity: place.uidvalidity,
|
|
41
61
|
receivedAt: new Date(email.receivedAt ?? 0).toISOString(),
|
|
42
62
|
});
|
|
43
63
|
}
|
|
@@ -58,11 +78,15 @@ export const completePartial = async ({ store, source, accountId, address, folde
|
|
|
58
78
|
const attachments = [];
|
|
59
79
|
for (const [index, [email, item]] of chunk.entries()) {
|
|
60
80
|
const raw = raws[index];
|
|
61
|
-
if (!(raw instanceof Uint8Array))
|
|
81
|
+
if (!(raw instanceof Uint8Array)) {
|
|
82
|
+
await failed(email);
|
|
62
83
|
continue;
|
|
63
|
-
|
|
64
|
-
|
|
84
|
+
}
|
|
85
|
+
// Only the stored id proves the bytes at a stored UID are this email; another one there will not change back.
|
|
86
|
+
if (!source.find && source.identify?.(raw, item).messageId !== email.externalId) {
|
|
87
|
+
await failed(email, true);
|
|
65
88
|
continue;
|
|
89
|
+
}
|
|
66
90
|
try {
|
|
67
91
|
const mail = await parseMail(raw, maxBodyChars);
|
|
68
92
|
const files = await readAttachments(mail, email.externalId, { loadEngine, maxAttachmentChars, at: now, ...(signal ? { signal } : {}) }, attachments);
|
|
@@ -87,8 +111,9 @@ export const completePartial = async ({ store, source, accountId, address, folde
|
|
|
87
111
|
catch {
|
|
88
112
|
if (signal?.aborted)
|
|
89
113
|
break;
|
|
114
|
+
await failed(email);
|
|
90
115
|
}
|
|
91
116
|
}
|
|
92
117
|
}
|
|
93
|
-
return { completed, left: total - completed };
|
|
118
|
+
return { completed, left: total - completed - given, stuck: stuck + given };
|
|
94
119
|
};
|
package/dist/mail/source.d.ts
CHANGED
|
@@ -24,7 +24,13 @@ export interface Listed {
|
|
|
24
24
|
headers?: MailHeaders;
|
|
25
25
|
}
|
|
26
26
|
/** A message as `quick` lists it: everything but the ids, which a source without a cheap one reads from the raw bytes. */
|
|
27
|
-
export type Quick = Omit<Listed, "messageId" | "threadId"
|
|
27
|
+
export type Quick = Omit<Listed, "messageId" | "threadId"> & {
|
|
28
|
+
/**
|
|
29
|
+
* The folder's UIDVALIDITY the UID was stored under. `fetchRaw` reads nothing for it when the folder's differs, and
|
|
30
|
+
* `identify` works the ids out under it.
|
|
31
|
+
*/
|
|
32
|
+
validity?: string;
|
|
33
|
+
};
|
|
28
34
|
export interface Searched {
|
|
29
35
|
/** Absent when the server does not say; then no UID is trusted across runs. */
|
|
30
36
|
validity?: string;
|
package/dist/mail/sync.js
CHANGED
|
@@ -54,7 +54,7 @@ export const syncMail = async (source, store, options) => {
|
|
|
54
54
|
...(options.budget ? { take: () => options.budget?.take() ?? null } : {}),
|
|
55
55
|
...(options.signal ? { signal: options.signal } : {}),
|
|
56
56
|
});
|
|
57
|
-
return partial.completed || partial.left ? { ...result, partial } : result;
|
|
57
|
+
return partial.completed || partial.left || partial.stuck ? { ...result, partial } : result;
|
|
58
58
|
};
|
|
59
59
|
const syncRounds = async (source, store, options) => {
|
|
60
60
|
const { budget, index } = options;
|