@wirecat/cli-core 0.562.0 → 0.563.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 +62 -0
- package/dist/cli/config-command.js +18 -1
- package/dist/cli/envelope.js +13 -1
- package/dist/cli/execution.js +1 -0
- package/dist/cli/messenger/reply-stats-command.d.ts +4 -0
- package/dist/cli/messenger/reply-stats-command.js +57 -0
- package/dist/cli/messenger/search-command.js +8 -0
- package/dist/cli/messenger/stats-answers.d.ts +3 -0
- package/dist/cli/messenger/stats-answers.js +18 -0
- package/dist/cli/messenger/stats-command.js +5 -0
- package/dist/cli/output-budget.d.ts +26 -0
- package/dist/cli/output-budget.js +73 -0
- package/dist/cli/program.js +14 -1
- package/dist/cli/settings.d.ts +5 -0
- package/dist/cli/settings.js +88 -5
- package/dist/cli/shared-command-exports.d.ts +8 -0
- package/dist/domain/admin-statistics.d.ts +5 -0
- package/dist/domain/admin-statistics.js +4 -3
- package/dist/entities/index.d.ts +31 -1
- package/dist/entities/index.js +4 -1
- package/dist/entities/reply-stats.d.ts +33 -0
- package/dist/entities/reply-stats.js +19 -0
- package/dist/errors/reasons.d.ts +1 -1
- package/dist/errors/reasons.js +2 -3
- package/dist/mail/attachments.d.ts +15 -2
- package/dist/mail/attachments.js +19 -3
- package/dist/mail/cli/index.d.ts +17 -1
- package/dist/mail/cli/index.js +1 -1
- package/dist/mail/cli/review.d.ts +5 -2
- package/dist/mail/cli/review.js +47 -39
- package/dist/mail/index.d.ts +2 -0
- package/dist/mail/index.js +2 -0
- package/dist/mail/partial.d.ts +3 -1
- package/dist/mail/partial.js +2 -2
- package/dist/mail/retry.d.ts +3 -1
- package/dist/mail/retry.js +3 -3
- package/dist/mail/sync-types.d.ts +3 -0
- package/dist/mail/sync.js +5 -3
- package/dist/mail/whole.d.ts +28 -0
- package/dist/mail/whole.js +69 -0
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.js +2 -0
- package/dist/services/proposal-tasks.js +3 -12
- package/dist/services/proposals/kinds/action.js +3 -2
- package/dist/services/proposals/registry.js +8 -4
- package/dist/services/proposals/types.d.ts +2 -0
- package/dist/services/reply-stats.d.ts +48 -0
- package/dist/services/reply-stats.js +137 -0
- package/dist/store/decisions.d.ts +14 -11
- package/dist/store/decisions.js +9 -2
- package/dist/store/index.d.ts +3 -3
- package/dist/store/index.js +2 -2
- package/dist/store/queries.d.ts +4 -0
- package/dist/store/queries.js +3 -0
- package/dist/store/sqlite/email-locations.d.ts +4 -0
- package/dist/store/sqlite/email-locations.js +26 -11
- package/dist/store/sqlite/lucene-fields.d.ts +2 -0
- package/dist/store/sqlite/lucene-fields.js +3 -1
- package/dist/store/sqlite/mail-findings.js +1 -1
- package/dist/store/sqlite/meeting-findings.js +1 -1
- package/dist/store/sqlite/proposed-actions.d.ts +2 -0
- package/dist/store/sqlite/proposed-actions.js +24 -17
- package/dist/store/sqlite/reply-times.d.ts +57 -0
- package/dist/store/sqlite/reply-times.js +113 -0
- package/dist/store/sqlite/work-tasks.d.ts +12 -2
- package/dist/store/sqlite/work-tasks.js +35 -26
- package/dist/store/store.d.ts +1 -0
- package/package.json +1 -1
- package/parity.json +24 -0
package/dist/mail/retry.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { LoadEngine } from "../readers/types.js";
|
|
2
2
|
import type { MessageStore } from "../store/index.js";
|
|
3
|
+
import { type MailAttachmentPolicy } from "./attachments.js";
|
|
3
4
|
import type { MailSource } from "./source.js";
|
|
4
5
|
import type { MailAccount, MailSyncOptions, MailSyncResult } from "./sync.js";
|
|
5
6
|
/** Failures a newer install mends: the reader was missing, not the file at fault, so reading again can succeed. */
|
|
@@ -9,7 +10,7 @@ export declare const RETRYABLE_ATTACHMENTS: string[];
|
|
|
9
10
|
* attachment file, so the email comes from the source again. Nothing is listed, so no deletion is proved and no
|
|
10
11
|
* resume point moves; the email keeps its stored mailboxes, flags and rule.
|
|
11
12
|
*/
|
|
12
|
-
export declare const retryUnread: ({ store, source, account, since, max, maxBodyChars, maxAttachmentChars, now, loadEngine, index, }: {
|
|
13
|
+
export declare const retryUnread: ({ store, source, account, since, max, maxBodyChars, maxAttachmentChars, policy, now, loadEngine, index, }: {
|
|
13
14
|
store: MessageStore;
|
|
14
15
|
source: MailSource;
|
|
15
16
|
account: MailAccount;
|
|
@@ -17,6 +18,7 @@ export declare const retryUnread: ({ store, source, account, since, max, maxBody
|
|
|
17
18
|
max: number;
|
|
18
19
|
maxBodyChars: number;
|
|
19
20
|
maxAttachmentChars: number;
|
|
21
|
+
policy?: MailAttachmentPolicy;
|
|
20
22
|
now: () => number;
|
|
21
23
|
loadEngine: LoadEngine;
|
|
22
24
|
index: MailSyncOptions["index"];
|
package/dist/mail/retry.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
2
|
import { mailAccountKey } from "./account-key.js";
|
|
3
|
-
import { addressOf, FETCH_CHUNK, readAttachments } from "./attachments.js";
|
|
3
|
+
import { addressOf, FETCH_CHUNK, readAttachments, } from "./attachments.js";
|
|
4
4
|
import { sentAtOf } from "./email-input.js";
|
|
5
5
|
import { mailFolders } from "./folders.js";
|
|
6
6
|
import { mailHeadersOf } from "./headers.js";
|
|
@@ -12,7 +12,7 @@ export const RETRYABLE_ATTACHMENTS = ["engine-missing"];
|
|
|
12
12
|
* attachment file, so the email comes from the source again. Nothing is listed, so no deletion is proved and no
|
|
13
13
|
* resume point moves; the email keeps its stored mailboxes, flags and rule.
|
|
14
14
|
*/
|
|
15
|
-
export const retryUnread = async ({ store, source, account, since, max, maxBodyChars, maxAttachmentChars, now, loadEngine, index, }) => {
|
|
15
|
+
export const retryUnread = async ({ store, source, account, since, max, maxBodyChars, maxAttachmentChars, policy, now, loadEngine, index, }) => {
|
|
16
16
|
const startedAt = now();
|
|
17
17
|
const key = mailAccountKey(account.address);
|
|
18
18
|
const find = source.find;
|
|
@@ -55,7 +55,7 @@ export const retryUnread = async ({ store, source, account, since, max, maxBodyC
|
|
|
55
55
|
continue;
|
|
56
56
|
}
|
|
57
57
|
const before = new Map(stored.attachments.map(({ position, extractionError }) => [position, extractionError]));
|
|
58
|
-
const files = await readAttachments(mail, item.messageId, { loadEngine, maxAttachmentChars, at: startedAt }, attachments);
|
|
58
|
+
const files = await readAttachments(mail, item.messageId, { loadEngine, maxAttachmentChars, at: startedAt, ...(policy ? { policy } : {}) }, attachments);
|
|
59
59
|
for (const file of files)
|
|
60
60
|
if (RETRYABLE_ATTACHMENTS.includes(before.get(file.position) ?? ""))
|
|
61
61
|
if (file.extraction === "text")
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { BudgetStop, RunBudget } from "../background/budget.js";
|
|
2
2
|
import type { LoadEngine } from "../readers/types.js";
|
|
3
3
|
import type { AccountKey, MessageStore } from "../store/index.js";
|
|
4
|
+
import type { MailAttachmentPolicy } from "./attachments.js";
|
|
4
5
|
import type { PartialOutcome } from "./partial.js";
|
|
5
6
|
import type { TagOutcome } from "./rule-tags.js";
|
|
6
7
|
import type { MailRule } from "./rules.js";
|
|
@@ -84,6 +85,8 @@ export interface MailSyncOptions {
|
|
|
84
85
|
max?: number;
|
|
85
86
|
maxBodyChars?: number;
|
|
86
87
|
maxAttachmentChars?: number;
|
|
88
|
+
/** Whether attachments are read into the index, and up to what size; `DEFAULT_ATTACHMENT_POLICY` when left out. */
|
|
89
|
+
attachments?: MailAttachmentPolicy;
|
|
87
90
|
now?: () => number;
|
|
88
91
|
/** Domain registration dates already known, for the sender score a rule asks for. */
|
|
89
92
|
ages?: ReadonlyMap<string, DomainAge>;
|
package/dist/mail/sync.js
CHANGED
|
@@ -3,7 +3,7 @@ import { CliError } from "@wirecat/cli-base";
|
|
|
3
3
|
import { importEngine } from "../attachments/extract.js";
|
|
4
4
|
import { isBusy } from "../store/sqlite/transaction.js";
|
|
5
5
|
import { mailAccountKey } from "./account-key.js";
|
|
6
|
-
import { FETCH_CHUNK, MAX_ATTACHMENT_CHARS, readAttachments } from "./attachments.js";
|
|
6
|
+
import { DEFAULT_ATTACHMENT_POLICY, FETCH_CHUNK, MAX_ATTACHMENT_CHARS, readAttachments } from "./attachments.js";
|
|
7
7
|
import { BULK, bulkBox } from "./bulk.js";
|
|
8
8
|
import { parsedEmail } from "./email-input.js";
|
|
9
9
|
import { factsOf } from "./facts.js";
|
|
@@ -51,6 +51,7 @@ export const syncMail = async (source, store, options) => {
|
|
|
51
51
|
folders: () => mailFolders(options.account, source),
|
|
52
52
|
maxBodyChars: options.maxBodyChars ?? MAX_BODY_CHARS,
|
|
53
53
|
maxAttachmentChars: options.maxAttachmentChars ?? MAX_ATTACHMENT_CHARS,
|
|
54
|
+
policy: options.attachments ?? DEFAULT_ATTACHMENT_POLICY,
|
|
54
55
|
loadEngine: options.loadEngine ?? importEngine,
|
|
55
56
|
now: (options.now ?? Date.now)(),
|
|
56
57
|
...(options.budget ? { take: () => options.budget?.take() ?? null } : {}),
|
|
@@ -169,7 +170,7 @@ const syncRounds = async (source, store, options) => {
|
|
|
169
170
|
: {}),
|
|
170
171
|
};
|
|
171
172
|
};
|
|
172
|
-
const syncRound = async (source, store, { account, since, max = 200, maxBodyChars = MAX_BODY_CHARS, maxAttachmentChars = MAX_ATTACHMENT_CHARS, now = Date.now, ages, loadEngine = importEngine, retryAttachments = false, rescan = false, saveCoverage = true, resumeFrom = RESUME_FROM, index, signal, }, plan) => {
|
|
173
|
+
const syncRound = async (source, store, { account, since, max = 200, maxBodyChars = MAX_BODY_CHARS, maxAttachmentChars = MAX_ATTACHMENT_CHARS, attachments: policy = DEFAULT_ATTACHMENT_POLICY, now = Date.now, ages, loadEngine = importEngine, retryAttachments = false, rescan = false, saveCoverage = true, resumeFrom = RESUME_FROM, index, signal, }, plan) => {
|
|
173
174
|
if (retryAttachments)
|
|
174
175
|
return retryUnread({
|
|
175
176
|
store,
|
|
@@ -179,6 +180,7 @@ const syncRound = async (source, store, { account, since, max = 200, maxBodyChar
|
|
|
179
180
|
max,
|
|
180
181
|
maxBodyChars,
|
|
181
182
|
maxAttachmentChars,
|
|
183
|
+
policy,
|
|
182
184
|
now,
|
|
183
185
|
loadEngine,
|
|
184
186
|
index,
|
|
@@ -759,7 +761,7 @@ const syncRound = async (source, store, { account, since, max = 200, maxBodyChar
|
|
|
759
761
|
plan?.unreadable.delete(item.messageId);
|
|
760
762
|
const header = mailHeadersOf(raws[index]);
|
|
761
763
|
// A stop between attachments ends the chunk; what the earlier slices saved stays.
|
|
762
|
-
const files = await readAttachments(mail, item.messageId, { loadEngine, maxAttachmentChars, at: startedAt, ...(signal ? { signal } : {}) }, attachments).catch(async (error) => {
|
|
764
|
+
const files = await readAttachments(mail, item.messageId, { loadEngine, maxAttachmentChars, at: startedAt, policy, ...(signal ? { signal } : {}) }, attachments).catch(async (error) => {
|
|
763
765
|
await stopIfAborted();
|
|
764
766
|
throw error;
|
|
765
767
|
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { LoadEngine } from "../readers/types.js";
|
|
2
|
+
import type { MessageStore } from "../store/index.js";
|
|
3
|
+
import type { MailSource } from "./source.js";
|
|
4
|
+
export interface WholeAttachment {
|
|
5
|
+
position: number;
|
|
6
|
+
name: string;
|
|
7
|
+
mime: string | null;
|
|
8
|
+
/** `null` when the email held no content for it. */
|
|
9
|
+
bytes: Uint8Array | null;
|
|
10
|
+
status: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Reads one stored email from its source again and every attachment of it, whatever its size: what a person asks for
|
|
14
|
+
* by name is read even when a sync's policy left it. The new text is saved as a sync saves it, so the search index
|
|
15
|
+
* takes it; the files are only answered, never written. Gmail's email is found by its id in `folders`; elsewhere it is
|
|
16
|
+
* read at its newest stored place, trusted only when `identify` gives the stored id for the bytes read there.
|
|
17
|
+
*/
|
|
18
|
+
export declare const readEmailWhole: ({ store, source, address, externalId, folders, maxBodyChars, maxAttachmentChars, loadEngine, now, }: {
|
|
19
|
+
store: MessageStore;
|
|
20
|
+
source: MailSource;
|
|
21
|
+
address: string;
|
|
22
|
+
externalId: string;
|
|
23
|
+
folders: () => Promise<readonly string[]>;
|
|
24
|
+
maxBodyChars: number;
|
|
25
|
+
maxAttachmentChars: number;
|
|
26
|
+
loadEngine: LoadEngine;
|
|
27
|
+
now: number;
|
|
28
|
+
}) => Promise<WholeAttachment[]>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { CliError } from "@wirecat/cli-base";
|
|
2
|
+
import { mailAccountKey } from "./account-key.js";
|
|
3
|
+
import { readAttachments } from "./attachments.js";
|
|
4
|
+
import { parsedEmail } from "./email-input.js";
|
|
5
|
+
import { mailHeadersOf } from "./headers.js";
|
|
6
|
+
import { parseMail } from "./parse.js";
|
|
7
|
+
import { PARTIAL } from "./partial.js";
|
|
8
|
+
/**
|
|
9
|
+
* Reads one stored email from its source again and every attachment of it, whatever its size: what a person asks for
|
|
10
|
+
* by name is read even when a sync's policy left it. The new text is saved as a sync saves it, so the search index
|
|
11
|
+
* takes it; the files are only answered, never written. Gmail's email is found by its id in `folders`; elsewhere it is
|
|
12
|
+
* read at its newest stored place, trusted only when `identify` gives the stored id for the bytes read there.
|
|
13
|
+
*/
|
|
14
|
+
export const readEmailWhole = async ({ store, source, address, externalId, folders, maxBodyChars, maxAttachmentChars, loadEngine, now, }) => {
|
|
15
|
+
const { id: accountId } = await store.accounts.storedAccount(mailAccountKey(address));
|
|
16
|
+
const stored = await store.mail.emailPlace(accountId, externalId);
|
|
17
|
+
if (stored === null)
|
|
18
|
+
throw new CliError("not_found", `${externalId} is not stored for ${address}`);
|
|
19
|
+
let item;
|
|
20
|
+
if (source.find)
|
|
21
|
+
for (const folder of await folders()) {
|
|
22
|
+
item = (await source.find([externalId], folder)).find(({ messageId }) => messageId === externalId);
|
|
23
|
+
if (item)
|
|
24
|
+
break;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
const place = stored.locations[0];
|
|
28
|
+
if (place)
|
|
29
|
+
item = {
|
|
30
|
+
uid: String(place.uid),
|
|
31
|
+
folder: place.folder,
|
|
32
|
+
validity: place.uidvalidity,
|
|
33
|
+
receivedAt: new Date(stored.receivedAt ?? 0).toISOString(),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
if (item === undefined)
|
|
37
|
+
throw new CliError("not_found", `the mail server no longer gives ${externalId}`);
|
|
38
|
+
const [raw] = await source.fetchRaw([item]);
|
|
39
|
+
if (!(raw instanceof Uint8Array))
|
|
40
|
+
throw new CliError("provider_unavailable", `${externalId} could not be read from the mail server`);
|
|
41
|
+
if (!source.find && source.identify?.(raw, item).messageId !== externalId)
|
|
42
|
+
throw new CliError("not_found", `the mail server holds another email where ${externalId} was`);
|
|
43
|
+
const mail = await parseMail(raw, maxBodyChars);
|
|
44
|
+
const log = [];
|
|
45
|
+
const files = await readAttachments(mail, externalId, { loadEngine, maxAttachmentChars, at: now }, log);
|
|
46
|
+
const { [PARTIAL]: _partial, ...metadata } = stored.metadata ?? {};
|
|
47
|
+
const whole = { ...item, messageId: externalId, threadId: stored.threadExternalId };
|
|
48
|
+
await store.mail.saveThread({
|
|
49
|
+
accountId,
|
|
50
|
+
externalId: stored.threadExternalId,
|
|
51
|
+
now,
|
|
52
|
+
emails: [
|
|
53
|
+
{
|
|
54
|
+
...parsedEmail(mail, whole, mailHeadersOf(raw), address),
|
|
55
|
+
receivedAt: stored.receivedAt,
|
|
56
|
+
size: raw.byteLength,
|
|
57
|
+
metadata,
|
|
58
|
+
attachments: files,
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
});
|
|
62
|
+
return mail.attachments.map((part, position) => ({
|
|
63
|
+
position,
|
|
64
|
+
name: part.name,
|
|
65
|
+
mime: part.mime,
|
|
66
|
+
bytes: part.bytes,
|
|
67
|
+
status: log[position]?.status ?? "unavailable",
|
|
68
|
+
}));
|
|
69
|
+
};
|
package/dist/services/index.d.ts
CHANGED
|
@@ -49,6 +49,7 @@ import { type PeopleService } from "./people.js";
|
|
|
49
49
|
import { type PicturesService } from "./pictures.js";
|
|
50
50
|
import { privatePeopleService } from "./private-people.js";
|
|
51
51
|
import { type RankingsService } from "./rankings.js";
|
|
52
|
+
import { type ReplyStatsService } from "./reply-stats.js";
|
|
52
53
|
import { type RetentionService } from "./retention.js";
|
|
53
54
|
import { type SearchesService } from "./searches.js";
|
|
54
55
|
import { type TagsService } from "./tags.js";
|
|
@@ -119,6 +120,7 @@ export interface Services {
|
|
|
119
120
|
counters: CountersService;
|
|
120
121
|
retention: RetentionService;
|
|
121
122
|
rankings: RankingsService;
|
|
123
|
+
replyStats: ReplyStatsService;
|
|
122
124
|
topics: TopicsService;
|
|
123
125
|
messages: MessagesService;
|
|
124
126
|
chats: ChatsService;
|
package/dist/services/index.js
CHANGED
|
@@ -40,6 +40,7 @@ import { peopleService } from "./people.js";
|
|
|
40
40
|
import { picturesService, withPictures } from "./pictures.js";
|
|
41
41
|
import { privatePeopleService } from "./private-people.js";
|
|
42
42
|
import { rankingsService } from "./rankings.js";
|
|
43
|
+
import { replyStatsService } from "./reply-stats.js";
|
|
43
44
|
import { retentionService } from "./retention.js";
|
|
44
45
|
import { searchesService } from "./searches.js";
|
|
45
46
|
import { tagsService } from "./tags.js";
|
|
@@ -89,6 +90,7 @@ export const servicesFor = (deps) => {
|
|
|
89
90
|
counters: countersService(deps),
|
|
90
91
|
retention: retentionService(deps),
|
|
91
92
|
rankings: rankingsService(deps),
|
|
93
|
+
replyStats: replyStatsService(deps),
|
|
92
94
|
topics: topicsService(deps),
|
|
93
95
|
messages: withPictures(messagesService(deps), pictures),
|
|
94
96
|
chats: chatsService(deps),
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
|
-
import { MEETING_FINDINGS_KIND } from "../meetings/index.js";
|
|
3
2
|
import { assertMayDecide } from "../store/decisions.js";
|
|
4
|
-
import {
|
|
5
|
-
import { proposerOfAction } from "../store/index.js";
|
|
3
|
+
import { assertGenericAction, proposerOfAction } from "../store/index.js";
|
|
6
4
|
import { createTaskService } from "../tasks/index.js";
|
|
7
5
|
import { taskAccount } from "./task-rules.js";
|
|
8
6
|
/** The source a proposal's task keeps: found again on a retry, so one proposal never makes two tasks. */
|
|
@@ -13,19 +11,12 @@ export const proposalSource = (id) => `proposed-action:${id}`;
|
|
|
13
11
|
*/
|
|
14
12
|
export const approveAsTask = async (store, id, by) => {
|
|
15
13
|
const proposal = await store.proposedActions.get(id);
|
|
16
|
-
|
|
17
|
-
throw new CliError("validation_error", "review meeting findings through the dedicated findings command", {
|
|
18
|
-
reason: "meeting-findings.dedicated-review",
|
|
19
|
-
});
|
|
20
|
-
if (proposal.kind === EVIDENCE_FINDINGS_KIND)
|
|
21
|
-
throw new CliError("validation_error", "review evidence findings through the dedicated findings command", {
|
|
22
|
-
reason: "evidence-findings.dedicated-review",
|
|
23
|
-
});
|
|
14
|
+
assertGenericAction(proposal.kind);
|
|
24
15
|
if (proposal.status !== "proposed")
|
|
25
16
|
throw new CliError("validation_error", `a ${proposal.status} action cannot be approved`, {
|
|
26
17
|
reason: "proposal.wrong-state",
|
|
27
18
|
});
|
|
28
|
-
assertMayDecide({ id: proposal.id, proposedBy: proposerOfAction(proposal) }, by, store.decisionPolicy);
|
|
19
|
+
assertMayDecide({ id: proposal.id, kind: proposal.kind, proposedBy: proposerOfAction(proposal) }, by, store.decisionPolicy);
|
|
29
20
|
if (proposal.account === null)
|
|
30
21
|
throw new CliError("validation_error", "a proposal names the account that would act; this one names none");
|
|
31
22
|
const { task } = await createTaskService({ store: store.tasks }).add({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { MEETING_FINDINGS_KIND } from "../../../meetings/index.js";
|
|
2
|
-
import { proposerOfAction } from "../../../store/index.js";
|
|
2
|
+
import { EVIDENCE_FINDINGS_KIND, isOwnerOnlyKind, MAIL_FINDINGS_KIND, proposerOfAction, } from "../../../store/index.js";
|
|
3
3
|
import { approveAsTask } from "../../proposal-tasks.js";
|
|
4
4
|
import { actionStatus, kept, projectOf, pushed, storedStatus } from "./shared.js";
|
|
5
5
|
const rowOf = (action) => ({
|
|
@@ -17,6 +17,7 @@ const rowOf = (action) => ({
|
|
|
17
17
|
signals: null,
|
|
18
18
|
parts: null,
|
|
19
19
|
createdAt: action.createdAt,
|
|
20
|
+
...(isOwnerOnlyKind(action.kind) ? { ownerOnly: true } : {}),
|
|
20
21
|
});
|
|
21
22
|
/**
|
|
22
23
|
* Every stored proposal no other kind claims: a reply, a ban, a task to create. Confirming makes it the owner's task
|
|
@@ -27,7 +28,7 @@ export const actionKind = (store, claimed) => ({
|
|
|
27
28
|
list: async (filter) => {
|
|
28
29
|
const status = storedStatus(filter);
|
|
29
30
|
const actions = await store.proposedActions.list({
|
|
30
|
-
except: [...claimed(), MEETING_FINDINGS_KIND],
|
|
31
|
+
except: [...claimed(), MEETING_FINDINGS_KIND, EVIDENCE_FINDINGS_KIND, MAIL_FINDINGS_KIND],
|
|
31
32
|
...(status === undefined ? {} : { status }),
|
|
32
33
|
...pushed(filter),
|
|
33
34
|
});
|
|
@@ -10,6 +10,7 @@ import { memoryKind } from "./kinds/memory.js";
|
|
|
10
10
|
import { relationshipKind } from "./kinds/relationship.js";
|
|
11
11
|
import { relationshipUpdateKind } from "./kinds/relationship-update.js";
|
|
12
12
|
const PREFIX = /^([a-z][a-z_]*):/;
|
|
13
|
+
const marked = (kind, row) => kind.ownerOnly ? { ...row, ownerOnly: true } : row;
|
|
13
14
|
const refusalOf = (id, error) => {
|
|
14
15
|
if (!(error instanceof CliError))
|
|
15
16
|
throw error;
|
|
@@ -51,12 +52,15 @@ export const emptyProposalRegistry = (store) => {
|
|
|
51
52
|
throw new CliError("validation_error", "a proposal list takes a nonnegative offset");
|
|
52
53
|
const asked = filter.kinds === undefined ? kinds : kinds.filter((kind) => filter.kinds?.includes(kind.kind));
|
|
53
54
|
const wide = { ...filter, offset: 0, limit: filter.offset + filter.limit + 1 };
|
|
54
|
-
const rows = (await Promise.all(asked.map((kind) => kind.list(wide)))).flat();
|
|
55
|
+
const rows = (await Promise.all(asked.map(async (kind) => (await kind.list(wide)).map((row) => marked(kind, row))))).flat();
|
|
55
56
|
rows.sort((a, b) => b.createdAt.localeCompare(a.createdAt) || b.id.localeCompare(a.id));
|
|
56
57
|
const page = rows.slice(filter.offset, filter.offset + filter.limit + 1);
|
|
57
58
|
return { items: page.slice(0, filter.limit), hasMore: page.length > filter.limit };
|
|
58
59
|
},
|
|
59
|
-
get: async (id) =>
|
|
60
|
+
get: async (id) => {
|
|
61
|
+
const kind = await kindOf(id);
|
|
62
|
+
return marked(kind, await kind.get(id));
|
|
63
|
+
},
|
|
60
64
|
decide: async (ids, decision) => {
|
|
61
65
|
const parts = new Map();
|
|
62
66
|
const whole = new Set();
|
|
@@ -89,7 +93,7 @@ export const emptyProposalRegistry = (store) => {
|
|
|
89
93
|
refuseIds(id, `a ${kind.kind} proposal has no parts; name ${id} alone`);
|
|
90
94
|
continue;
|
|
91
95
|
}
|
|
92
|
-
const row = await kind.get(id);
|
|
96
|
+
const row = marked(kind, await kind.get(id));
|
|
93
97
|
if (row.status !== "proposed") {
|
|
94
98
|
refused.push({
|
|
95
99
|
id,
|
|
@@ -99,7 +103,7 @@ export const emptyProposalRegistry = (store) => {
|
|
|
99
103
|
});
|
|
100
104
|
continue;
|
|
101
105
|
}
|
|
102
|
-
const refusal = mayDecide(
|
|
106
|
+
const refusal = mayDecide(row, decision.by, policy);
|
|
103
107
|
if (refusal) {
|
|
104
108
|
refused.push(refusal);
|
|
105
109
|
continue;
|
|
@@ -28,6 +28,8 @@ export interface ProposalRow {
|
|
|
28
28
|
outcome: "accepted" | "rejected" | null;
|
|
29
29
|
}[] | null;
|
|
30
30
|
createdAt: string;
|
|
31
|
+
/** Only the owner decides it: its kind says so, whichever path decides it. */
|
|
32
|
+
ownerOnly?: boolean;
|
|
31
33
|
}
|
|
32
34
|
export interface ProposalFilter {
|
|
33
35
|
project?: string;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { NewestMessage } from "../store/store.js";
|
|
2
|
+
import type { ServiceDeps } from "./deps.js";
|
|
3
|
+
export type ReplyWho = "me" | "others";
|
|
4
|
+
export interface ReplyFigures {
|
|
5
|
+
replies: number;
|
|
6
|
+
medianMs: number | null;
|
|
7
|
+
p90Ms: number | null;
|
|
8
|
+
meanMs: number | null;
|
|
9
|
+
}
|
|
10
|
+
export interface ReplyTimes extends ReplyFigures {
|
|
11
|
+
who: ReplyWho;
|
|
12
|
+
by: "day" | "total";
|
|
13
|
+
buckets: {
|
|
14
|
+
under5m: number;
|
|
15
|
+
under1h: number;
|
|
16
|
+
under1d: number;
|
|
17
|
+
over1d: number;
|
|
18
|
+
};
|
|
19
|
+
rows: (ReplyFigures & {
|
|
20
|
+
key: string;
|
|
21
|
+
})[];
|
|
22
|
+
}
|
|
23
|
+
export interface Waiting {
|
|
24
|
+
chats: number;
|
|
25
|
+
messages: number;
|
|
26
|
+
oldestAt: string | null;
|
|
27
|
+
byKind: {
|
|
28
|
+
private: number;
|
|
29
|
+
group: number;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export interface ReplyQuery {
|
|
33
|
+
text?: string;
|
|
34
|
+
source?: string;
|
|
35
|
+
timezone?: string;
|
|
36
|
+
}
|
|
37
|
+
export interface ReplyStatsService {
|
|
38
|
+
responses(query: ReplyQuery & {
|
|
39
|
+
who: ReplyWho;
|
|
40
|
+
by: "day" | "total";
|
|
41
|
+
}): Promise<ReplyTimes>;
|
|
42
|
+
waiting(query: ReplyQuery): Promise<Waiting>;
|
|
43
|
+
}
|
|
44
|
+
/** Per waiting chat, how far back its unanswered messages are counted. */
|
|
45
|
+
export declare const WAITING_TAIL = 100;
|
|
46
|
+
/** Asks the owner something: a question in a one-to-one chat; in a group, a mention by id or `@handle`, or a reply. */
|
|
47
|
+
export declare const waitsOnOwner: (newest: NewestMessage) => boolean;
|
|
48
|
+
export declare const replyStatsService: (deps: ServiceDeps) => ReplyStatsService;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { CliError } from "@wirecat/cli-base";
|
|
2
|
+
import { parseLucene, timezoneOf } from "@wirecat/cli-base/query";
|
|
3
|
+
import { delayStats } from "../domain/admin-statistics.js";
|
|
4
|
+
import { calendarKey } from "../domain/calendar.js";
|
|
5
|
+
import { rankingQuestion } from "../domain/rankings-options.js";
|
|
6
|
+
import { MESSAGE_PARSING } from "../search/lucene/types.js";
|
|
7
|
+
import { prepareLucene } from "./messages-search.js";
|
|
8
|
+
const FIELDS = ["date", "chat", "kind"];
|
|
9
|
+
/** Per waiting chat, how far back its unanswered messages are counted. */
|
|
10
|
+
export const WAITING_TAIL = 100;
|
|
11
|
+
/** Only AND-ed `date:`, `chat:` and `kind:` terms: each becomes an index bound, so no query reads every message. */
|
|
12
|
+
const plainOnly = (text, command) => {
|
|
13
|
+
if (text === undefined)
|
|
14
|
+
return;
|
|
15
|
+
const refuse = () => {
|
|
16
|
+
throw new CliError("validation_error", `${command} reads date:, chat: and kind: terms, AND-ed — not words, other fields, OR or NOT`, { reason: "query.unsupported-field" });
|
|
17
|
+
};
|
|
18
|
+
const visit = (node) => {
|
|
19
|
+
if (node.kind === "boolean")
|
|
20
|
+
for (const clause of node.clauses) {
|
|
21
|
+
if (clause.occur !== "must")
|
|
22
|
+
refuse();
|
|
23
|
+
visit(clause.node);
|
|
24
|
+
}
|
|
25
|
+
else if (!FIELDS.includes(node.field) || !["term", "phrase", "range"].includes(node.operator))
|
|
26
|
+
refuse();
|
|
27
|
+
};
|
|
28
|
+
visit(parseLucene(text, { ...MESSAGE_PARSING, defaultField: "text" }).root);
|
|
29
|
+
};
|
|
30
|
+
const scopeOf = (root, accounts) => {
|
|
31
|
+
const scope = { accounts, chats: [], kinds: [] };
|
|
32
|
+
const visit = (node) => {
|
|
33
|
+
if (node.kind === "boolean") {
|
|
34
|
+
for (const clause of node.clauses)
|
|
35
|
+
visit(clause.node);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const { chat, date } = node.resolution ?? {};
|
|
39
|
+
if (chat)
|
|
40
|
+
scope.chats.push(chat);
|
|
41
|
+
else if (node.field === "kind")
|
|
42
|
+
scope.kinds.push(node.value.toLowerCase());
|
|
43
|
+
else if (date) {
|
|
44
|
+
if (date.lower !== undefined) {
|
|
45
|
+
const lower = date.lower + (date.lower_inclusive ? 0 : 1);
|
|
46
|
+
scope.since = Math.max(scope.since ?? lower, lower);
|
|
47
|
+
}
|
|
48
|
+
if (date.upper !== undefined) {
|
|
49
|
+
const upper = date.upper - (date.upper_inclusive ? 0 : 1);
|
|
50
|
+
scope.until = Math.min(scope.until ?? upper, upper);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
visit(root);
|
|
55
|
+
return scope;
|
|
56
|
+
};
|
|
57
|
+
const figuresOf = (delays) => {
|
|
58
|
+
const { medianMilliseconds, p90Milliseconds } = delayStats(delays);
|
|
59
|
+
return {
|
|
60
|
+
replies: delays.length,
|
|
61
|
+
medianMs: medianMilliseconds,
|
|
62
|
+
p90Ms: p90Milliseconds,
|
|
63
|
+
meanMs: delays.length ? Math.round(delays.reduce((sum, one) => sum + one, 0) / delays.length) : null,
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
const MINUTE = 60_000;
|
|
67
|
+
/** Asks the owner something: a question in a one-to-one chat; in a group, a mention by id or `@handle`, or a reply. */
|
|
68
|
+
export const waitsOnOwner = (newest) => {
|
|
69
|
+
if (newest.mine)
|
|
70
|
+
return false;
|
|
71
|
+
if (newest.kind === "private")
|
|
72
|
+
return !newest.quiet && rankingQuestion(newest.text);
|
|
73
|
+
return (newest.addressed ||
|
|
74
|
+
(newest.ownerUsername !== null &&
|
|
75
|
+
new RegExp(`(^|[^\\w@])@${newest.ownerUsername.replace(/[^\w]/g, "\\$&")}(?!\\w)`, "i").test(newest.text)));
|
|
76
|
+
};
|
|
77
|
+
export const replyStatsService = (deps) => {
|
|
78
|
+
const prepared = async (query, command) => {
|
|
79
|
+
plainOnly(query.text, command);
|
|
80
|
+
const store = await deps.store();
|
|
81
|
+
const found = await prepareLucene(store, await deps.account(), {
|
|
82
|
+
...(query.text === undefined ? {} : { text: query.text }),
|
|
83
|
+
...(query.source === undefined ? {} : { source: query.source }),
|
|
84
|
+
timezone: timezoneOf(query.timezone),
|
|
85
|
+
limit: 1,
|
|
86
|
+
}, deps.messenger);
|
|
87
|
+
return { store, timezone: found.timezone, scope: scopeOf(found.execution.root, found.scopeAccounts) };
|
|
88
|
+
};
|
|
89
|
+
return {
|
|
90
|
+
responses: async ({ who, by, ...query }) => {
|
|
91
|
+
const { store, timezone, scope } = await prepared(query, "stats messages responses");
|
|
92
|
+
const delays = scope.accounts.length ? await store.queries.replyDelays(scope, who) : [];
|
|
93
|
+
const buckets = { under5m: 0, under1h: 0, under1d: 0, over1d: 0 };
|
|
94
|
+
for (const { delayMs } of delays) {
|
|
95
|
+
if (delayMs < 5 * MINUTE)
|
|
96
|
+
buckets.under5m++;
|
|
97
|
+
else if (delayMs < 60 * MINUTE)
|
|
98
|
+
buckets.under1h++;
|
|
99
|
+
else if (delayMs < 24 * 60 * MINUTE)
|
|
100
|
+
buckets.under1d++;
|
|
101
|
+
else
|
|
102
|
+
buckets.over1d++;
|
|
103
|
+
}
|
|
104
|
+
const days = new Map();
|
|
105
|
+
if (by === "day") {
|
|
106
|
+
const keyOf = calendarKey(timezone, "day");
|
|
107
|
+
for (const { repliedAt, delayMs } of delays) {
|
|
108
|
+
const key = keyOf(repliedAt);
|
|
109
|
+
const list = days.get(key) ?? [];
|
|
110
|
+
list.push(delayMs);
|
|
111
|
+
days.set(key, list);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
who,
|
|
116
|
+
by,
|
|
117
|
+
...figuresOf(delays.map(({ delayMs }) => delayMs)),
|
|
118
|
+
buckets,
|
|
119
|
+
rows: [...days].sort(([a], [b]) => a.localeCompare(b)).map(([key, list]) => ({ key, ...figuresOf(list) })),
|
|
120
|
+
};
|
|
121
|
+
},
|
|
122
|
+
waiting: async (query) => {
|
|
123
|
+
const { store, scope } = await prepared(query, "stats messages waiting");
|
|
124
|
+
const chats = scope.accounts.length ? await store.queries.waitingChats(scope, waitsOnOwner, WAITING_TAIL) : [];
|
|
125
|
+
const firsts = chats.flatMap(({ unanswered }) => unanswered.at(-1) ?? []);
|
|
126
|
+
return {
|
|
127
|
+
chats: chats.length,
|
|
128
|
+
messages: chats.reduce((sum, { unanswered }) => sum + unanswered.length, 0),
|
|
129
|
+
oldestAt: firsts.length ? new Date(firsts.reduce((low, one) => Math.min(low, one))).toISOString() : null,
|
|
130
|
+
byKind: {
|
|
131
|
+
private: chats.filter(({ kind }) => kind === "private").length,
|
|
132
|
+
group: chats.filter(({ kind }) => kind === "group").length,
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
};
|
|
@@ -21,6 +21,12 @@ export type Decider = "owner" | {
|
|
|
21
21
|
model?: string;
|
|
22
22
|
reason: string;
|
|
23
23
|
};
|
|
24
|
+
export interface DecidedRow {
|
|
25
|
+
id: string;
|
|
26
|
+
proposedBy: Proposer;
|
|
27
|
+
ownerOnly?: boolean;
|
|
28
|
+
kind?: string;
|
|
29
|
+
}
|
|
24
30
|
export interface DecisionPolicy {
|
|
25
31
|
/** The host's `agent_decisions`: off unless the owner sets it to `true`; off keeps every decision with the owner. */
|
|
26
32
|
agentDecisions: boolean;
|
|
@@ -34,17 +40,18 @@ export interface Refusal {
|
|
|
34
40
|
reason: string;
|
|
35
41
|
message: string;
|
|
36
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Stored proposal kinds only the owner decides, whatever `agent_decisions` says. Read from the row's kind, so a path
|
|
45
|
+
* that sees the row as something else — a generic action, a host's own kind — still refuses an agent.
|
|
46
|
+
*/
|
|
47
|
+
export declare const isOwnerOnlyKind: (kind: string | undefined) => boolean;
|
|
37
48
|
/**
|
|
38
49
|
* The one check every decide path runs before it confirms or rejects a proposal. `null` means allowed. Every Claude
|
|
39
50
|
* Code session is the bot `claude-code`, so comparing bot names keeps one session from deciding another's proposal.
|
|
40
51
|
*/
|
|
41
52
|
export declare const mayDecide: (
|
|
42
|
-
/** `ownerOnly`: what only the owner may say, such as that a payment was made. */
|
|
43
|
-
row:
|
|
44
|
-
id: string;
|
|
45
|
-
proposedBy: Proposer;
|
|
46
|
-
ownerOnly?: boolean;
|
|
47
|
-
}, by: Decider, policy?: DecisionPolicy) => Refusal | null;
|
|
53
|
+
/** `ownerOnly`: what only the owner may say, such as that a payment was made; `kind`: the stored kind. */
|
|
54
|
+
row: DecidedRow, by: Decider, policy?: DecisionPolicy) => Refusal | null;
|
|
48
55
|
/** One bot whatever its letter case or spacing: `Claude-Code` is `claude-code`. */
|
|
49
56
|
export declare const botKey: (name: string) => string;
|
|
50
57
|
/**
|
|
@@ -59,11 +66,7 @@ export declare const assertMayWithdraw: (row: {
|
|
|
59
66
|
bot: string;
|
|
60
67
|
}) => void;
|
|
61
68
|
/** `mayDecide` for a store method: throws the refusal instead of returning it. */
|
|
62
|
-
export declare const assertMayDecide: (row:
|
|
63
|
-
id: string;
|
|
64
|
-
proposedBy: Proposer;
|
|
65
|
-
ownerOnly?: boolean;
|
|
66
|
-
}, by: Decider, policy?: DecisionPolicy) => void;
|
|
69
|
+
export declare const assertMayDecide: (row: DecidedRow, by: Decider, policy?: DecisionPolicy) => void;
|
|
67
70
|
/** The author a decision is recorded under. */
|
|
68
71
|
export declare const deciderAuthor: (by: Decider) => "owner" | {
|
|
69
72
|
bot: string;
|
package/dist/store/decisions.js
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
|
+
import { MEETING_FINDINGS_KIND } from "../meetings/index.js";
|
|
3
|
+
import { MAIL_FINDINGS_KIND } from "./mail-findings.js";
|
|
2
4
|
export const DEFAULT_DECISION_POLICY = { agentDecisions: false, ruleProposalsOwnerOnly: true };
|
|
5
|
+
/**
|
|
6
|
+
* Stored proposal kinds only the owner decides, whatever `agent_decisions` says. Read from the row's kind, so a path
|
|
7
|
+
* that sees the row as something else — a generic action, a host's own kind — still refuses an agent.
|
|
8
|
+
*/
|
|
9
|
+
export const isOwnerOnlyKind = (kind) => kind === MAIL_FINDINGS_KIND || kind === MEETING_FINDINGS_KIND;
|
|
3
10
|
/**
|
|
4
11
|
* The one check every decide path runs before it confirms or rejects a proposal. `null` means allowed. Every Claude
|
|
5
12
|
* Code session is the bot `claude-code`, so comparing bot names keeps one session from deciding another's proposal.
|
|
6
13
|
*/
|
|
7
14
|
export const mayDecide = (
|
|
8
|
-
/** `ownerOnly`: what only the owner may say, such as that a payment was made. */
|
|
15
|
+
/** `ownerOnly`: what only the owner may say, such as that a payment was made; `kind`: the stored kind. */
|
|
9
16
|
row, by, policy = DEFAULT_DECISION_POLICY) => {
|
|
10
17
|
if (by === "owner")
|
|
11
18
|
return null;
|
|
@@ -15,7 +22,7 @@ row, by, policy = DEFAULT_DECISION_POLICY) => {
|
|
|
15
22
|
reason,
|
|
16
23
|
message,
|
|
17
24
|
});
|
|
18
|
-
if (row.ownerOnly)
|
|
25
|
+
if (row.ownerOnly || isOwnerOnlyKind(row.kind))
|
|
19
26
|
return refuse("permission_error", "proposal.owner-only", `only the owner decides ${row.id}`);
|
|
20
27
|
if (!policy.agentDecisions)
|
|
21
28
|
return refuse("permission_error", "proposals.agent-decisions-off", "agent decisions are turned off; the owner decides");
|
package/dist/store/index.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export type { FindingEvidence, FindingOutcome, MeetingFactFinding, MeetingFindin
|
|
|
9
9
|
export { PAYMENT_DIRECTIONS, PAYMENT_STATES, type Payment, paymentTitle, validatePayment } from "../meetings/index.js";
|
|
10
10
|
export { CLOSED_STATES, isClosedState, PRIORITY_LABELS, type PriorityLabel, priorityLabel, priorityOf, priorityOption, TASK_STATES, type TaskState, } from "../tasks/index.js";
|
|
11
11
|
export type { AccountMail, AccountMeetings, AccountStore } from "./account-store.js";
|
|
12
|
-
export { assertMayDecide, DEFAULT_DECISION_POLICY, type Decider, type DecisionPolicy, mayDecide, type Proposer, type Refusal, } from "./decisions.js";
|
|
12
|
+
export { assertMayDecide, DEFAULT_DECISION_POLICY, type Decider, type DecisionPolicy, isOwnerOnlyKind, mayDecide, type Proposer, type Refusal, } from "./decisions.js";
|
|
13
13
|
export type { DocumentTransferOptions, DocumentTransferPreview, DocumentTransferReceipt, } from "./document-transfer/index.js";
|
|
14
14
|
export { applyDocumentTransfer, previewDocumentTransfer } from "./document-transfer/index.js";
|
|
15
15
|
export type { CacheDatabase, CacheStatement, OpenDatabase, SqlValue } from "./driver.js";
|
|
@@ -54,7 +54,7 @@ export type { NoteIndexState } from "./sqlite/note-index.js";
|
|
|
54
54
|
export { type NearestNote, NOTE_FIELDS, type NoteChunkToEmbed, type NoteHit, type NoteQuery, type NoteSearch, } from "./sqlite/note-search.js";
|
|
55
55
|
export type { FileNoteInput, Link, LinkInput, LinkListFilter, Note, NoteFolder, NotesStore, NoteTag, } from "./sqlite/notes.js";
|
|
56
56
|
export type { PrivateContact, PrivateContactNote } from "./sqlite/private-people.js";
|
|
57
|
-
export { PROPOSAL_STATUSES, type ProposalInput, type ProposedAction, type ProposedActionsStore, proposerOfAction, } from "./sqlite/proposed-actions.js";
|
|
57
|
+
export { assertGenericAction, PROPOSAL_STATUSES, type ProposalInput, type ProposedAction, type ProposedActionsStore, proposerOfAction, } from "./sqlite/proposed-actions.js";
|
|
58
58
|
export type { RecordKey, RecordOutcome, RecordStore, RecordsWritten, SourceRecord, } from "./sqlite/records.js";
|
|
59
59
|
export type { LinkDecision, RelationUpdate } from "./sqlite/relation-proposals.js";
|
|
60
60
|
export { fillSearchIndex, resetSearchIndex, type SearchIndexFill, type SearchIndexState, searchIndexState, } from "./sqlite/search-index.js";
|
|
@@ -72,6 +72,6 @@ export type { TaskRelation, TaskRelationKind, TaskRelationsStore } from "./sqlit
|
|
|
72
72
|
export { TASK_RELATION_KINDS } from "./sqlite/task-relations.js";
|
|
73
73
|
export type { StoreTaskStore } from "./sqlite/tasks.js";
|
|
74
74
|
export type { TagPreflight, VocabularyAlias, VocabularyAliasReceipt, VocabularyAudit, VocabularyAuditOptions, VocabularyCatalog, VocabularyEntry, VocabularyKind, VocabularyOptions, VocabularyResolution, VocabularyStore, VocabularyTarget, } from "./sqlite/vocabulary.js";
|
|
75
|
-
export type { BoardCounts, BoardOptions, NewWorkTask, RevisionCheck, TaskAssignment, TaskDetails, TaskEvent, TaskFields, TaskMove, TaskMoved, TaskRole, TaskType, WorkTaskType, } from "./sqlite/work-tasks.js";
|
|
75
|
+
export type { BoardCounts, BoardOptions, EditedTask, MovedSubtask, NewWorkTask, RevisionCheck, TaskAssignment, TaskDetails, TaskEvent, TaskFields, TaskMove, TaskMoved, TaskRole, TaskType, WorkTaskType, } from "./sqlite/work-tasks.js";
|
|
76
76
|
export { TASK_EVENT_KINDS, TASK_ROLES } from "./sqlite/work-tasks.js";
|
|
77
77
|
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";
|