@wirecat/cli-core 0.452.0 → 0.454.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 +71 -0
- package/dist/cli/mcp-server-command.js +5 -1
- package/dist/cli/messenger/search-answers.d.ts +1 -1
- package/dist/cli/messenger/search-answers.js +1 -1
- package/dist/cli/messenger/search-command.js +6 -3
- package/dist/entities/contact-context.d.ts +42 -0
- package/dist/entities/contact-context.js +11 -0
- package/dist/entities/hit.d.ts +3 -3
- package/dist/entities/hit.js +3 -3
- package/dist/entities/index.d.ts +61 -21
- package/dist/entities/task-results.d.ts +20 -20
- package/dist/entities/task-results.js +3 -15
- package/dist/mcp/http/activity.d.ts +24 -0
- package/dist/mcp/http/activity.js +86 -0
- package/dist/mcp/http/serve.js +14 -0
- package/dist/mcp/http/state.d.ts +5 -0
- package/dist/mcp/http/state.js +14 -4
- package/dist/meetings/event-store.d.ts +23 -0
- package/dist/meetings/index.d.ts +1 -1
- package/dist/search/query.js +1 -3
- package/dist/services/events-search.d.ts +67 -0
- package/dist/services/events-search.js +131 -0
- package/dist/services/index.d.ts +3 -2
- package/dist/services/index.js +2 -1
- package/dist/services/notes-search.d.ts +25 -1
- package/dist/services/notes-search.js +24 -11
- package/dist/services/person-context.d.ts +20 -1
- package/dist/services/person-context.js +46 -1
- package/dist/services/search-all-meetings.d.ts +1 -1
- package/dist/services/search-all-meetings.js +7 -2
- package/dist/services/search-all.d.ts +12 -7
- package/dist/services/search-all.js +39 -4
- package/dist/store/index.d.ts +1 -0
- package/dist/store/index.js +1 -0
- package/dist/store/project-transfer/request.d.ts +1 -1
- package/dist/store/project-transfer/schema.d.ts +2 -2
- package/dist/store/project-transfer/snapshot-model.d.ts +2 -2
- package/dist/store/sqlite/calendar.js +85 -11
- package/dist/store/sqlite/event-search.d.ts +66 -0
- package/dist/store/sqlite/event-search.js +163 -0
- package/dist/store/sqlite/files.d.ts +4 -0
- package/dist/store/sqlite/files.js +28 -0
- package/dist/store/sqlite/link-kinds.d.ts +5 -1
- package/dist/store/sqlite/link-kinds.js +5 -0
- package/dist/store/sqlite/note-index.d.ts +17 -1
- package/dist/store/sqlite/note-index.js +32 -8
- package/dist/store/sqlite/note-search.d.ts +43 -0
- package/dist/store/sqlite/note-search.js +24 -24
- package/dist/store/sqlite/notes.d.ts +4 -5
- package/dist/store/sqlite/notes.js +2 -2
- package/dist/store/sqlite/project-transfer/snapshot.d.ts +1 -1
- package/dist/store/store.d.ts +2 -1
- package/dist/store/store.js +2 -1
- package/package.json +1 -1
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { CliError } from "@wirecat/cli-base";
|
|
2
|
+
import { kindIn } from "../domain/providers.js";
|
|
3
|
+
import { meaningFloor } from "../embeddings/models.js";
|
|
4
|
+
import { excerpt } from "./excerpt.js";
|
|
5
|
+
import { localModel, RRF_K, storeQuery, withLocal } from "./notes-search.js";
|
|
6
|
+
import { plainQueryWords } from "./notes-title-tier.js";
|
|
7
|
+
const MAX_EVENTS_OFFSET = 1000;
|
|
8
|
+
const iso = (at) => (at === null ? null : new Date(at).toISOString());
|
|
9
|
+
/** The stored ids of the calendar accounts named; a name the store does not hold reads nothing. */
|
|
10
|
+
const accountIdsOf = async (store, accounts) => {
|
|
11
|
+
const held = await store.accounts.storedAccounts();
|
|
12
|
+
return held
|
|
13
|
+
.filter((one) => accounts.some(({ provider, account }) => one.provider === provider && one.account === account))
|
|
14
|
+
.map(({ id }) => String(id));
|
|
15
|
+
};
|
|
16
|
+
/** The calendar accounts the store holds: every account whose provider is of the `events` kind. */
|
|
17
|
+
export const calendarAccounts = async (store) => (await store.accounts.storedAccounts())
|
|
18
|
+
.filter(({ provider }) => kindIn(provider) === "events")
|
|
19
|
+
.map(({ provider, account }) => ({ provider, account }));
|
|
20
|
+
export const foundEvent = (hit, query, foundBy) => ({
|
|
21
|
+
ref: hit.ref,
|
|
22
|
+
provider: hit.account?.provider ?? null,
|
|
23
|
+
account: hit.account?.account ?? null,
|
|
24
|
+
title: hit.event.title,
|
|
25
|
+
starts_at: iso(hit.event.startsAt),
|
|
26
|
+
ends_at: iso(hit.event.endsAt),
|
|
27
|
+
all_day: hit.event.allDay === true,
|
|
28
|
+
status: hit.event.status ?? null,
|
|
29
|
+
location: hit.event.location,
|
|
30
|
+
...(hit.url ? { url: hit.url } : {}),
|
|
31
|
+
excerpt: excerpt(hit.text || (hit.event.title ?? ""), query),
|
|
32
|
+
occurrences: hit.occurrences,
|
|
33
|
+
updated_at: new Date(hit.event.updatedAt).toISOString(),
|
|
34
|
+
found_by: foundBy,
|
|
35
|
+
});
|
|
36
|
+
/** How deep each half is read before the two are merged. */
|
|
37
|
+
const CANDIDATES = 50;
|
|
38
|
+
/** Events nearest in meaning to plain words, one per series, best first; only events already embedded are found. */
|
|
39
|
+
const nearestEvents = async (store, words, { limit, accountIds, env, command, embedders, }) => {
|
|
40
|
+
const { model, directory, key } = localModel(undefined, env, command);
|
|
41
|
+
if (!(await store.calendar.embedded(key, accountIds ? { accountIds } : {})))
|
|
42
|
+
throw new CliError("not_found", `no event has vectors of ${model.id} yet, so only words were matched`);
|
|
43
|
+
const [vector] = await withLocal(model, directory, undefined, (embedder) => embedder.embed([words], "query"), embedders);
|
|
44
|
+
const floor = meaningFloor(key);
|
|
45
|
+
return (await store.calendar.nearest(key, vector, { limit, ...(accountIds ? { accountIds } : {}) })).filter(({ score }) => score > floor);
|
|
46
|
+
};
|
|
47
|
+
/** One hit per series, whichever half found it. */
|
|
48
|
+
const keyOf = ({ event }) => event.eventSeriesId === null ? `e${event.id}` : `s${event.eventSeriesId}`;
|
|
49
|
+
/**
|
|
50
|
+
* Calendar events by words and, when the local text model is there and the query is plain words, by meaning, merged
|
|
51
|
+
* by reciprocal rank: title, cleaned description, place and attendees' names. The occurrences of one series are one
|
|
52
|
+
* hit. Working location, focus time, out of office, birthdays and busy-only slots are never found.
|
|
53
|
+
*/
|
|
54
|
+
export const searchEvents = async (store, query, { limit = 20, offset = 0, accounts, exact, newest, timezone, signal, candidates, env, command, embedders, } = {}) => {
|
|
55
|
+
if (!query.trim())
|
|
56
|
+
throw new CliError("validation_error", "say what to find in the events");
|
|
57
|
+
const { root, stemmer } = await storeQuery(store, query, {
|
|
58
|
+
...(exact ? { exact: true } : {}),
|
|
59
|
+
...(timezone === undefined ? {} : { timezone }),
|
|
60
|
+
});
|
|
61
|
+
const accountIds = accounts === undefined ? undefined : await accountIdsOf(store, accounts);
|
|
62
|
+
if (accountIds?.length === 0)
|
|
63
|
+
return { query, by: "words", hits: [], has_more: false };
|
|
64
|
+
const words = plainQueryWords(query);
|
|
65
|
+
const window = offset + limit;
|
|
66
|
+
let meaningSkipped;
|
|
67
|
+
let meaning;
|
|
68
|
+
if (exact)
|
|
69
|
+
meaningSkipped = "--exact searches words as written";
|
|
70
|
+
else if (newest)
|
|
71
|
+
meaningSkipped = "newest first orders by time";
|
|
72
|
+
else if (words === undefined)
|
|
73
|
+
meaningSkipped = "the query has fields or operators, which meaning cannot follow";
|
|
74
|
+
else
|
|
75
|
+
try {
|
|
76
|
+
meaning = await nearestEvents(store, words, {
|
|
77
|
+
limit: Math.max(window, CANDIDATES),
|
|
78
|
+
...(accountIds ? { accountIds } : {}),
|
|
79
|
+
...(env === undefined ? {} : { env }),
|
|
80
|
+
...(command === undefined ? {} : { command }),
|
|
81
|
+
...(embedders === undefined ? {} : { embedders }),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
if (!(error instanceof CliError && error.code === "not_found"))
|
|
86
|
+
throw error;
|
|
87
|
+
meaningSkipped = error.message;
|
|
88
|
+
}
|
|
89
|
+
const found = await store.calendar.search({
|
|
90
|
+
root,
|
|
91
|
+
limit: meaning === undefined ? limit : Math.max(window, CANDIDATES),
|
|
92
|
+
offset: meaning === undefined ? offset : 0,
|
|
93
|
+
...(accountIds === undefined ? {} : { accountIds }),
|
|
94
|
+
...(newest ? { newest: true } : {}),
|
|
95
|
+
...(stemmer ? { stemmer } : {}),
|
|
96
|
+
...(signal ? { signal } : {}),
|
|
97
|
+
...(candidates === undefined ? {} : { candidates }),
|
|
98
|
+
});
|
|
99
|
+
const truncated = found.truncated ? { truncated: true } : {};
|
|
100
|
+
const by = meaning === undefined ? "words" : "words and meaning";
|
|
101
|
+
const skipped = meaningSkipped === undefined ? {} : { meaning_skipped: meaningSkipped };
|
|
102
|
+
if (meaning === undefined)
|
|
103
|
+
return {
|
|
104
|
+
query,
|
|
105
|
+
by,
|
|
106
|
+
...skipped,
|
|
107
|
+
hits: found.items.map((hit) => foundEvent(hit, query, ["words"])),
|
|
108
|
+
has_more: found.hasMore,
|
|
109
|
+
...(found.hasMore && window <= MAX_EVENTS_OFFSET ? { next_offset: window } : {}),
|
|
110
|
+
...truncated,
|
|
111
|
+
};
|
|
112
|
+
const ranked = new Map();
|
|
113
|
+
const add = (list, half) => list.forEach((hit, rank) => {
|
|
114
|
+
const held = ranked.get(keyOf(hit)) ?? { hit, score: 0, by: [] };
|
|
115
|
+
held.score += 1 / (RRF_K + rank + 1);
|
|
116
|
+
held.by.push(half);
|
|
117
|
+
ranked.set(keyOf(hit), held);
|
|
118
|
+
});
|
|
119
|
+
add(found.items, "words");
|
|
120
|
+
add(meaning, "meaning");
|
|
121
|
+
const ordered = [...ranked.values()].sort((a, b) => b.score - a.score);
|
|
122
|
+
const hasMore = ordered.length > window || found.hasMore;
|
|
123
|
+
return {
|
|
124
|
+
query,
|
|
125
|
+
by,
|
|
126
|
+
hits: ordered.slice(offset, window).map(({ hit, by: halves }) => foundEvent(hit, query, halves)),
|
|
127
|
+
has_more: hasMore,
|
|
128
|
+
...(hasMore && window <= MAX_EVENTS_OFFSET ? { next_offset: window } : {}),
|
|
129
|
+
...truncated,
|
|
130
|
+
};
|
|
131
|
+
};
|
package/dist/services/index.d.ts
CHANGED
|
@@ -84,7 +84,7 @@ export type { ModerateOptions, ModerationService, ShownRules } from "./moderatio
|
|
|
84
84
|
export { moderationService } from "./moderation.js";
|
|
85
85
|
export type { ContactSync, PeopleService } from "./people.js";
|
|
86
86
|
export { peopleService, phoneOf } from "./people.js";
|
|
87
|
-
export type { ContextMessage, ContextOptions, NotReadReason, PersonContext, SharedChat, } from "./person-context.js";
|
|
87
|
+
export type { ContextEvent, ContextMessage, ContextOptions, NotReadReason, PersonContext, SharedChat, } from "./person-context.js";
|
|
88
88
|
export { CONTEXT_BYTES, CONTEXT_MESSAGES, identityIn, personContext } from "./person-context.js";
|
|
89
89
|
export type { PersonPreparation, WaitingDialog } from "./person-preparation.js";
|
|
90
90
|
export { PREPARE_BYTES, personPreparation } from "./person-preparation.js";
|
|
@@ -149,6 +149,7 @@ export type { CounterQuery, CountersService } from "./counters.js";
|
|
|
149
149
|
export { countersService } from "./counters.js";
|
|
150
150
|
export { type DocumentTarget, openPictureEmbedderFor, type PictureOptions, removePicture, savePicture, } from "./document-pictures.js";
|
|
151
151
|
export type { ModelChoice } from "./embeddings.js";
|
|
152
|
+
export { calendarAccounts, type EventsFound, type EventsQuery, type FoundEvent, foundEvent, searchEvents, } from "./events-search.js";
|
|
152
153
|
export { createMeetingBatchReader } from "./meeting-batch.js";
|
|
153
154
|
export { type MeetingCombinedHit, type MeetingCombinedRequest, searchMeetingsCombined, } from "./meeting-combined.js";
|
|
154
155
|
export type { MeetingCommunicationCoverage, MeetingCommunicationExcerpt, MeetingCommunications, MeetingCommunicationsPort, MeetingCommunicationsRequest, } from "./meeting-communications-types.js";
|
|
@@ -168,7 +169,7 @@ export type { MeaningLane, MeaningSkipped } from "./messages-discovery.js";
|
|
|
168
169
|
export { meaningLane } from "./messages-meaning.js";
|
|
169
170
|
export type { QueryMetadata, SearchCoverage } from "./messages-search.js";
|
|
170
171
|
export { metadataService } from "./metadata.js";
|
|
171
|
-
export { embedMail, embedNotes, type FoundNote, type LinkedRecord, type NotesEmbedded, type NotesEmbedOptions, type NotesFound, type NotesFoundOf, type NotesQuery, type NotesSearchRequest, nearestNotes, searchNotes, searchNotesOf, searchNotesQuery, } from "./notes-search.js";
|
|
172
|
+
export { embedEvents, embedMail, embedNotes, type FoundNote, type LinkedRecord, type NotesEmbedded, type NotesEmbedOptions, type NotesFound, type NotesFoundOf, type NotesQuery, type NotesSearchRequest, nearestNotes, searchNotes, searchNotesOf, searchNotesQuery, storeQuery, } from "./notes-search.js";
|
|
172
173
|
export { type PackageUpgradeOutcome, type PackageUpgradePorts, type PackageUpgradeResult, type ServerRestarts, upgradePackage, } from "./package-upgrade.js";
|
|
173
174
|
export { type PersonMeetingContext, type PersonMeetingContextItem, type PersonMeetingContextOptions, type PersonMeetingReadStore, personMeetingContext, } from "./person-meeting-context.js";
|
|
174
175
|
export { privatePeopleService } from "./private-people.js";
|
package/dist/services/index.js
CHANGED
|
@@ -111,6 +111,7 @@ export { adminStatisticsService } from "./admin-statistics.js";
|
|
|
111
111
|
export { GAP_BOUNDS, gapsService } from "./archive-gaps.js";
|
|
112
112
|
export { countersService } from "./counters.js";
|
|
113
113
|
export { openPictureEmbedderFor, removePicture, savePicture, } from "./document-pictures.js";
|
|
114
|
+
export { calendarAccounts, foundEvent, searchEvents, } from "./events-search.js";
|
|
114
115
|
export { createMeetingBatchReader } from "./meeting-batch.js";
|
|
115
116
|
export { searchMeetingsCombined, } from "./meeting-combined.js";
|
|
116
117
|
export { createMeetingContextService, } from "./meeting-context.js";
|
|
@@ -126,7 +127,7 @@ export { proposeMeetingTask } from "./meeting-task-proposal.js";
|
|
|
126
127
|
export { createTaskFromMeeting, readMeetingTask, } from "./meeting-tasks.js";
|
|
127
128
|
export { meaningLane } from "./messages-meaning.js";
|
|
128
129
|
export { metadataService } from "./metadata.js";
|
|
129
|
-
export { embedMail, embedNotes, nearestNotes, searchNotes, searchNotesOf, searchNotesQuery, } from "./notes-search.js";
|
|
130
|
+
export { embedEvents, embedMail, embedNotes, nearestNotes, searchNotes, searchNotesOf, searchNotesQuery, storeQuery, } from "./notes-search.js";
|
|
130
131
|
export { upgradePackage, } from "./package-upgrade.js";
|
|
131
132
|
export { personMeetingContext, } from "./person-meeting-context.js";
|
|
132
133
|
export { privatePeopleService } from "./private-people.js";
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { type WarmEmbedders } from "../embeddings/embed.js";
|
|
1
|
+
import { type Embedder, type WarmEmbedders } from "../embeddings/embed.js";
|
|
2
|
+
import { type TextModel } from "../embeddings/models.js";
|
|
2
3
|
import type { PictureEmbedder } from "../images/picture/embedder.js";
|
|
3
4
|
import type { AppIdentity } from "../ports/app.js";
|
|
4
5
|
import { type SkippedSource } from "../search/account-list.js";
|
|
6
|
+
import { type ResolvedNode } from "../search/lucene/resolved.js";
|
|
7
|
+
import { type Stemmer } from "../search/stem.js";
|
|
5
8
|
import type { Link, NearestNote, Note, NoteHit } from "../store/index.js";
|
|
6
9
|
import type { AccountKey, MessageStore } from "../store/store.js";
|
|
7
10
|
export interface NotesSearchRequest {
|
|
@@ -19,6 +22,17 @@ export interface NotesSearchRequest {
|
|
|
19
22
|
timezone?: string;
|
|
20
23
|
signal?: AbortSignal;
|
|
21
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Text in the query language messages use, parsed for a store search: `date:` read in `timezone`, and the store's
|
|
27
|
+
* stemmer when a word is stemmed.
|
|
28
|
+
*/
|
|
29
|
+
export declare const storeQuery: (store: MessageStore, text: string, { exact, timezone }?: {
|
|
30
|
+
exact?: boolean;
|
|
31
|
+
timezone?: string;
|
|
32
|
+
}) => Promise<{
|
|
33
|
+
root: ResolvedNode;
|
|
34
|
+
stemmer?: Stemmer;
|
|
35
|
+
}>;
|
|
22
36
|
/** A notes search in the query language messages use, for `search notes` and `search all` to call. */
|
|
23
37
|
export declare const searchNotesQuery: (store: MessageStore, request: NotesSearchRequest) => Promise<{
|
|
24
38
|
items: NoteHit[];
|
|
@@ -38,9 +52,19 @@ export interface NotesEmbedded {
|
|
|
38
52
|
/** Chunks still without a vector, when `maxChunks` stopped the run. */
|
|
39
53
|
left: boolean;
|
|
40
54
|
}
|
|
55
|
+
export declare const localModel: (choice: string | undefined, env: NodeJS.ProcessEnv | undefined, command: string | undefined) => {
|
|
56
|
+
model: TextModel;
|
|
57
|
+
directory: string;
|
|
58
|
+
key: string;
|
|
59
|
+
};
|
|
60
|
+
export declare const withLocal: <T>(model: TextModel, directory: string, threads: number | undefined, work: (embedder: Embedder) => Promise<T>, shared?: Pick<WarmEmbedders, "get">) => Promise<T>;
|
|
41
61
|
export declare const embedNotes: (store: MessageStore, options?: NotesEmbedOptions & {
|
|
42
62
|
command?: string;
|
|
43
63
|
}) => Promise<NotesEmbedded>;
|
|
64
|
+
/** Embeds calendar events' pieces as notes' are embedded; the occurrences of a series share one vector. */
|
|
65
|
+
export declare const embedEvents: (store: MessageStore, options?: NotesEmbedOptions & {
|
|
66
|
+
command?: string;
|
|
67
|
+
}) => Promise<NotesEmbedded>;
|
|
44
68
|
/** Embeds mail's chunks as notes' are embedded: same model and key, so search by meaning reads both. */
|
|
45
69
|
export declare const embedMail: (store: MessageStore, options?: NotesEmbedOptions & {
|
|
46
70
|
command?: string;
|
|
@@ -15,12 +15,13 @@ import { documentSource } from "./document-source.js";
|
|
|
15
15
|
import { plainQueryWords, titleQuery, titleTiers } from "./notes-title-tier.js";
|
|
16
16
|
import { PICTURE_FLOOR, plainWords } from "./pictures.js";
|
|
17
17
|
import { listContextOf } from "./search-kind.js";
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
18
|
+
/**
|
|
19
|
+
* Text in the query language messages use, parsed for a store search: `date:` read in `timezone`, and the store's
|
|
20
|
+
* stemmer when a word is stemmed.
|
|
21
|
+
*/
|
|
22
|
+
export const storeQuery = async (store, text, { exact, timezone } = {}) => {
|
|
23
|
+
const ast = validateFields(parseLucene(text, { ...SEARCH_QUERY, defaultField: exact ? "exact" : "text" }));
|
|
24
|
+
const zone = timezoneOf(timezone);
|
|
24
25
|
const resolve = (node) => {
|
|
25
26
|
if (node.kind === "boolean")
|
|
26
27
|
return { ...node, clauses: node.clauses.map(({ occur, node }) => ({ occur, node: resolve(node) })) };
|
|
@@ -29,15 +30,25 @@ export const searchNotesQuery = async (store, request) => {
|
|
|
29
30
|
return {
|
|
30
31
|
...node,
|
|
31
32
|
resolution: {
|
|
32
|
-
date: dateRange(node.value, node.operator === "range" ? (node.upper ?? "*") : node.value, node.operator === "range" ? node.lower_inclusive === true : true, node.operator === "range" ? node.upper_inclusive === true : true,
|
|
33
|
+
date: dateRange(node.value, node.operator === "range" ? (node.upper ?? "*") : node.value, node.operator === "range" ? node.lower_inclusive === true : true, node.operator === "range" ? node.upper_inclusive === true : true, zone, node.span),
|
|
33
34
|
},
|
|
34
35
|
};
|
|
35
36
|
};
|
|
36
37
|
const stemmers = hasStems(ast.root) ? await store.searchIndex.stemmers() : undefined;
|
|
37
38
|
if (stemmers === null)
|
|
38
39
|
throw new CliError("validation_error", "the store asks for stemmers this tool does not know — upgrade this tool, or search exact forms with --exact", { reason: "stemmer.unknown" });
|
|
39
|
-
return
|
|
40
|
+
return {
|
|
40
41
|
root: resolve(ast.root),
|
|
42
|
+
...(hasStems(ast.root) ? { stemmer: createStemmer(stemmers ?? DEFAULT_STEMMERS) } : {}),
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
/** A notes search in the query language messages use, for `search notes` and `search all` to call. */
|
|
46
|
+
export const searchNotesQuery = async (store, request) => {
|
|
47
|
+
if (!request.text.trim())
|
|
48
|
+
throw new CliError("validation_error", "say what to find in the notes");
|
|
49
|
+
const { root, stemmer } = await storeQuery(store, request.text, request);
|
|
50
|
+
return store.notes.search({
|
|
51
|
+
root,
|
|
41
52
|
limit: request.limit,
|
|
42
53
|
...(request.offset === undefined ? {} : { offset: request.offset }),
|
|
43
54
|
...(request.newest ? { newest: true } : {}),
|
|
@@ -45,7 +56,7 @@ export const searchNotesQuery = async (store, request) => {
|
|
|
45
56
|
...(request.withInternal ? { withInternal: true } : {}),
|
|
46
57
|
...(request.source ? { source: request.source } : {}),
|
|
47
58
|
...(request.signal ? { signal: request.signal } : {}),
|
|
48
|
-
...(
|
|
59
|
+
...(stemmer ? { stemmer } : {}),
|
|
49
60
|
});
|
|
50
61
|
};
|
|
51
62
|
/** Chunks one batch embeds: one short transaction writes each, as for conversations. */
|
|
@@ -54,7 +65,7 @@ const NOTE_BATCH = 8;
|
|
|
54
65
|
const downloadHint = (model, command) => command === undefined
|
|
55
66
|
? `install it with a WireCat CLI that has \`models download\` (tg, max or wa); they share the model folder`
|
|
56
67
|
: `\`${command} models download ${model}\``;
|
|
57
|
-
const localModel = (choice, env, command) => {
|
|
68
|
+
export const localModel = (choice, env, command) => {
|
|
58
69
|
const model = textModel(choice ?? DEFAULT_TEXT_MODEL);
|
|
59
70
|
const directory = textModelsDirectory(env);
|
|
60
71
|
if (!isTextModelInstalled(model, directory))
|
|
@@ -64,7 +75,7 @@ const localModel = (choice, env, command) => {
|
|
|
64
75
|
});
|
|
65
76
|
return { model, directory, key: vectorModelKey(model) };
|
|
66
77
|
};
|
|
67
|
-
const withLocal = async (model, directory, threads, work, shared) => {
|
|
78
|
+
export const withLocal = async (model, directory, threads, work, shared) => {
|
|
68
79
|
if (shared)
|
|
69
80
|
return work(await shared.get(vectorModelKey(model), () => openEmbedder(model, directory, { threads: threads ?? defaultThreads() })));
|
|
70
81
|
const embedder = await openEmbedder(model, directory, { threads: threads ?? defaultThreads() });
|
|
@@ -104,6 +115,8 @@ const embedChunks = async (store, source, { model: choice, maxChunks = Number.PO
|
|
|
104
115
|
});
|
|
105
116
|
};
|
|
106
117
|
export const embedNotes = (store, options = {}) => embedChunks(store, store.notes, options);
|
|
118
|
+
/** Embeds calendar events' pieces as notes' are embedded; the occurrences of a series share one vector. */
|
|
119
|
+
export const embedEvents = (store, options = {}) => embedChunks(store, store.calendar, options);
|
|
107
120
|
/** Embeds mail's chunks as notes' are embedded: same model and key, so search by meaning reads both. */
|
|
108
121
|
export const embedMail = (store, options = {}) => embedChunks(store, store.mail, options);
|
|
109
122
|
/** Notes nearest in meaning to the query, best first; only notes already embedded can be found. */
|
|
@@ -23,6 +23,18 @@ export interface SharedChat {
|
|
|
23
23
|
kind: ChatKind;
|
|
24
24
|
lastMessageAt: string | null;
|
|
25
25
|
}
|
|
26
|
+
/** A calendar event the person is invited to, through any contact linked to them. */
|
|
27
|
+
export interface ContextEvent {
|
|
28
|
+
/** `event:<id>`: of a series, the one occurrence listed. */
|
|
29
|
+
ref: string;
|
|
30
|
+
title: string | null;
|
|
31
|
+
startsAt: string | null;
|
|
32
|
+
endsAt: string | null;
|
|
33
|
+
allDay: boolean;
|
|
34
|
+
location: string | null;
|
|
35
|
+
/** confirmed or tentative; a cancelled event is not listed. */
|
|
36
|
+
status: string | null;
|
|
37
|
+
}
|
|
26
38
|
/** `not_fetched`: nothing of its history was ever read into the store; `partial`: only some of it was. */
|
|
27
39
|
export type NotReadReason = "not_fetched" | "partial";
|
|
28
40
|
export interface PersonContext {
|
|
@@ -42,6 +54,11 @@ export interface PersonContext {
|
|
|
42
54
|
groups: ContextMessage[];
|
|
43
55
|
};
|
|
44
56
|
mentions: ContextMessage[];
|
|
57
|
+
/** Calendar events they are invited to, one occurrence per series: the next ones, then the latest past ones. */
|
|
58
|
+
events: {
|
|
59
|
+
upcoming: ContextEvent[];
|
|
60
|
+
recent: ContextEvent[];
|
|
61
|
+
};
|
|
45
62
|
limits: {
|
|
46
63
|
messages: number;
|
|
47
64
|
bytes: number;
|
|
@@ -59,6 +76,8 @@ export interface ContextOptions {
|
|
|
59
76
|
bytes?: number;
|
|
60
77
|
/** ms: nothing older. */
|
|
61
78
|
since?: number;
|
|
79
|
+
/** ms: what splits upcoming events from past ones; the clock when absent. */
|
|
80
|
+
now?: number;
|
|
62
81
|
}
|
|
63
82
|
export declare const toContext: (key: AccountKey, message: Message, chatTitle: string | null) => ContextMessage;
|
|
64
83
|
/**
|
|
@@ -74,7 +93,7 @@ export declare const readsOf: (record: PersonRecord, asked: AccountKey) => {
|
|
|
74
93
|
* What the store knows about one person, across every identity linked to them. Reads the store
|
|
75
94
|
* only: nothing is fetched, nothing marked read. Identity is by id, never by name.
|
|
76
95
|
*/
|
|
77
|
-
export declare const personContext: (store: MessageStore, asked: AccountKey, reference: string, { messages, bytes, since }?: ContextOptions) => Promise<PersonContext>;
|
|
96
|
+
export declare const personContext: (store: MessageStore, asked: AccountKey, reference: string, { messages, bytes, since, now }?: ContextOptions) => Promise<PersonContext>;
|
|
78
97
|
/** `max:Ana` names a person in another messenger of this store; a reference with no prefix, this one. */
|
|
79
98
|
export declare const identityIn: (store: MessageStore, asked: AccountKey, reference: string) => Promise<IdentityRef>;
|
|
80
99
|
export declare const CHAT_MESSAGES = 20;
|
|
@@ -44,6 +44,42 @@ const mailWith = async (store, key, address, limit, since) => {
|
|
|
44
44
|
return { items, hasMore: emails.length > limit };
|
|
45
45
|
};
|
|
46
46
|
const newestFirst = (a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp);
|
|
47
|
+
const iso = (at) => (at === null ? null : new Date(at).toISOString());
|
|
48
|
+
const contextEvent = (event) => ({
|
|
49
|
+
ref: `event:${event.id}`,
|
|
50
|
+
title: event.title,
|
|
51
|
+
startsAt: iso(event.startsAt),
|
|
52
|
+
endsAt: iso(event.endsAt),
|
|
53
|
+
allDay: event.allDay === true,
|
|
54
|
+
location: event.location,
|
|
55
|
+
status: event.status ?? null,
|
|
56
|
+
});
|
|
57
|
+
const EVENT_PAGE = 100;
|
|
58
|
+
/** A weekly meeting held for years is hundreds of occurrences; past this many rows the list stops, cut. */
|
|
59
|
+
const EVENT_ROWS = 1000;
|
|
60
|
+
/** Up to `limit` events of `filter`, in its order, the first occurrence of each series only and none cancelled. */
|
|
61
|
+
const eventsOnce = async (store, filter, limit) => {
|
|
62
|
+
const items = [];
|
|
63
|
+
const series = new Set();
|
|
64
|
+
for (let offset = 0; offset < EVENT_ROWS; offset += EVENT_PAGE) {
|
|
65
|
+
const page = await store.calendar.events({ ...filter, limit: EVENT_PAGE, offset });
|
|
66
|
+
for (const event of page) {
|
|
67
|
+
if (event.status === "cancelled")
|
|
68
|
+
continue;
|
|
69
|
+
if (event.eventSeriesId !== null) {
|
|
70
|
+
if (series.has(event.eventSeriesId))
|
|
71
|
+
continue;
|
|
72
|
+
series.add(event.eventSeriesId);
|
|
73
|
+
}
|
|
74
|
+
if (items.length === limit)
|
|
75
|
+
return { items, cut: true };
|
|
76
|
+
items.push(event);
|
|
77
|
+
}
|
|
78
|
+
if (page.length < EVENT_PAGE)
|
|
79
|
+
return { items, cut: false };
|
|
80
|
+
}
|
|
81
|
+
return { items, cut: true };
|
|
82
|
+
};
|
|
47
83
|
/**
|
|
48
84
|
* The accounts each identity is read in: for the messenger asked, only the account asked about — a
|
|
49
85
|
* second account of the same messenger is somebody else's view; for a linked messenger, every account
|
|
@@ -56,7 +92,7 @@ export const readsOf = (record, asked) => record.identities.flatMap((identity) =
|
|
|
56
92
|
* What the store knows about one person, across every identity linked to them. Reads the store
|
|
57
93
|
* only: nothing is fetched, nothing marked read. Identity is by id, never by name.
|
|
58
94
|
*/
|
|
59
|
-
export const personContext = async (store, asked, reference, { messages = CONTEXT_MESSAGES, bytes = CONTEXT_BYTES, since } = {}) => {
|
|
95
|
+
export const personContext = async (store, asked, reference, { messages = CONTEXT_MESSAGES, bytes = CONTEXT_BYTES, since, now = Date.now() } = {}) => {
|
|
60
96
|
const found = pickPerson(reference, await store.people.people(asked.provider, { account: asked.account }));
|
|
61
97
|
const record = await store.people.personOf({ provider: asked.provider, id: found.id });
|
|
62
98
|
if (!record)
|
|
@@ -121,6 +157,11 @@ export const personContext = async (store, asked, reference, { messages = CONTEX
|
|
|
121
157
|
.filter(recent));
|
|
122
158
|
cut ||= named.hasMore;
|
|
123
159
|
}
|
|
160
|
+
const personId = Number(record.uid);
|
|
161
|
+
const upcoming = await eventsOnce(store, { personId, from: now }, messages);
|
|
162
|
+
const upcomingIds = new Set(upcoming.items.map(({ id }) => id));
|
|
163
|
+
const past = await eventsOnce(store, { personId, to: now, newest: true, ...(since === undefined ? {} : { from: since }) }, messages);
|
|
164
|
+
cut ||= upcoming.cut || past.cut;
|
|
124
165
|
direct.sort(newestFirst);
|
|
125
166
|
const each = [...direct, ...beyond].sort(newestFirst);
|
|
126
167
|
theirs.sort(newestFirst);
|
|
@@ -145,6 +186,10 @@ export const personContext = async (store, asked, reference, { messages = CONTEX
|
|
|
145
186
|
},
|
|
146
187
|
recent: { direct: kept.direct, groups: kept.groups },
|
|
147
188
|
mentions: kept.mentions,
|
|
189
|
+
events: {
|
|
190
|
+
upcoming: upcoming.items.map(contextEvent),
|
|
191
|
+
recent: past.items.filter(({ id }) => !upcomingIds.has(id)).map(contextEvent),
|
|
192
|
+
},
|
|
148
193
|
limits: { messages, bytes },
|
|
149
194
|
hasMore: cut || kept.cut,
|
|
150
195
|
complete: notRead.length === 0,
|
|
@@ -3,7 +3,7 @@ import type { AccountKey, MessageStore } from "../store/store.js";
|
|
|
3
3
|
import type { SearchFound, SearchQuery } from "./messages.js";
|
|
4
4
|
import type { MeaningLane } from "./messages-discovery.js";
|
|
5
5
|
import { type SearchAllFound, type SearchAllItem, type SearchAllMessenger, type SearchAllRequest, type Skipped } from "./search-all.js";
|
|
6
|
-
export declare const RESOURCES_SEARCHED_WITH_MEETINGS: readonly ["messages", "mail", "notes", "meetings"];
|
|
6
|
+
export declare const RESOURCES_SEARCHED_WITH_MEETINGS: readonly ["messages", "mail", "notes", "events", "meetings"];
|
|
7
7
|
export type ResourceWithMeetings = (typeof RESOURCES_SEARCHED_WITH_MEETINGS)[number];
|
|
8
8
|
export interface MeetingSearchItem {
|
|
9
9
|
kind: "meeting";
|
|
@@ -131,7 +131,7 @@ export const searchAllIncludingMeetings = async (store, account, request, messen
|
|
|
131
131
|
const only = request.only ?? RESOURCES_SEARCHED_WITH_MEETINGS;
|
|
132
132
|
if (!only.length ||
|
|
133
133
|
only.some((resource) => !RESOURCES_SEARCHED_WITH_MEETINGS.includes(resource)))
|
|
134
|
-
throw new CliError("validation_error", "only takes messages, mail, notes and meetings");
|
|
134
|
+
throw new CliError("validation_error", "only takes messages, mail, notes, events and meetings");
|
|
135
135
|
if (request.meetingCursor) {
|
|
136
136
|
if (only.length !== 1 || only[0] !== "meetings")
|
|
137
137
|
throw new CliError("validation_error", "meeting cursor requires only: [meetings]");
|
|
@@ -261,7 +261,12 @@ const searchAllOfSource = async (store, account, source, request, messenger, sea
|
|
|
261
261
|
const found = await searchAllIncludingMeetings(store, account, {
|
|
262
262
|
...request,
|
|
263
263
|
only: read,
|
|
264
|
-
scope: {
|
|
264
|
+
scope: {
|
|
265
|
+
messages: named("messages"),
|
|
266
|
+
mail: named("mail"),
|
|
267
|
+
notes: named("notes"),
|
|
268
|
+
events: named("events"),
|
|
269
|
+
},
|
|
265
270
|
...(meetings[0] ? { meetingAccount: meetings[0] } : {}),
|
|
266
271
|
}, messenger, searchMessages, meaning);
|
|
267
272
|
const unnamed = only
|
|
@@ -5,16 +5,16 @@ import type { AccountKey, MessageStore } from "../store/store.js";
|
|
|
5
5
|
import type { SearchFound, SearchQuery } from "./messages.js";
|
|
6
6
|
import { type MeaningLane, type MeaningSkipped } from "./messages-discovery.js";
|
|
7
7
|
import type { Backend, ServerOptions, ServerSearched } from "./server-search.js";
|
|
8
|
-
export declare const RESOURCES_SEARCHED: readonly ["messages", "mail", "notes"];
|
|
8
|
+
export declare const RESOURCES_SEARCHED: readonly ["messages", "mail", "notes", "events"];
|
|
9
9
|
export type SearchedResource = (typeof RESOURCES_SEARCHED)[number];
|
|
10
10
|
export interface SearchAllItem {
|
|
11
|
-
kind: "message" | "mail" | "note" | "document";
|
|
12
|
-
/** `msg:…` for a message or a mail, `note:…` for a note. */
|
|
11
|
+
kind: "message" | "mail" | "note" | "document" | "event";
|
|
12
|
+
/** `msg:…` for a message or a mail, `note:…` for a note, `event:…` for a calendar event. */
|
|
13
13
|
ref: string;
|
|
14
14
|
provider: string;
|
|
15
15
|
/** The account it was found in; a note's folder id, or `internal` for a note written in memo. */
|
|
16
16
|
account: string;
|
|
17
|
-
/** The chat, the mail thread's subject or the
|
|
17
|
+
/** The chat, the mail thread's subject, the note's or the event's title. */
|
|
18
18
|
url?: string;
|
|
19
19
|
title: string | null;
|
|
20
20
|
timestamp: string;
|
|
@@ -63,6 +63,11 @@ export interface SearchAllFound {
|
|
|
63
63
|
by: "words" | "words and meaning";
|
|
64
64
|
meaningSkipped?: string;
|
|
65
65
|
};
|
|
66
|
+
/** How calendar events were searched, when they were. */
|
|
67
|
+
events?: {
|
|
68
|
+
by: "words" | "words and meaning";
|
|
69
|
+
meaningSkipped?: string;
|
|
70
|
+
};
|
|
66
71
|
/** How the messages were searched, when they were; never by meaning with `exact` or the server alone. */
|
|
67
72
|
messages?: {
|
|
68
73
|
by: "words" | "words and meaning";
|
|
@@ -79,7 +84,7 @@ export interface SearchAllRequest {
|
|
|
79
84
|
only?: readonly SearchedResource[];
|
|
80
85
|
exact?: boolean;
|
|
81
86
|
timezone?: string;
|
|
82
|
-
/** For messages only; mail and
|
|
87
|
+
/** For messages only; mail, notes and events are always the local store's. */
|
|
83
88
|
backend?: Backend;
|
|
84
89
|
server?: ServerOptions;
|
|
85
90
|
env?: NodeJS.ProcessEnv;
|
|
@@ -95,8 +100,8 @@ export type SearchAllMessenger = Partial<Pick<MessengerTraits, "savedChatId" | "
|
|
|
95
100
|
hintCommand?: string | null;
|
|
96
101
|
};
|
|
97
102
|
/**
|
|
98
|
-
* `search all`: messages, mail and
|
|
103
|
+
* `search all`: messages, mail, notes and calendar events from the local store — messages also from the messenger's
|
|
99
104
|
* server, as `search messages` asks it — each list best first, merged by reciprocal rank so no
|
|
100
|
-
* resource's own scores have to be compared with another's.
|
|
105
|
+
* resource's own scores have to be compared with another's. Events are read only when the store holds a calendar.
|
|
101
106
|
*/
|
|
102
107
|
export declare const searchAll: (store: MessageStore, account: AccountKey, request: SearchAllRequest, messenger?: SearchAllMessenger, searchMessages?: (query: SearchQuery) => Promise<SearchFound>, meaning?: MeaningLane) => Promise<SearchAllFound>;
|
|
@@ -6,11 +6,12 @@ import { runEmbedders } from "../embeddings/embed.js";
|
|
|
6
6
|
import { DEFAULT_TEXT_MODEL } from "../embeddings/models.js";
|
|
7
7
|
import { MESSAGE_PARSING } from "../search/lucene/types.js";
|
|
8
8
|
import { planQuestion } from "../search/question-plan.js";
|
|
9
|
+
import { calendarAccounts, searchEvents } from "./events-search.js";
|
|
9
10
|
import { excerpt } from "./excerpt.js";
|
|
10
11
|
import { MAX_SEARCH_OFFSET, searchStore } from "./messages-archive-search.js";
|
|
11
12
|
import { nearEligible } from "./messages-discovery.js";
|
|
12
13
|
import { notesScopeOf, RRF_K, searchNotes } from "./notes-search.js";
|
|
13
|
-
export const RESOURCES_SEARCHED = ["messages", "mail", "notes"];
|
|
14
|
+
export const RESOURCES_SEARCHED = ["messages", "mail", "notes", "events"];
|
|
14
15
|
/** A query a resource cannot answer is skipped with its reason, never a failure of the whole search. */
|
|
15
16
|
const skippable = (error) => error instanceof CliError && (error.code === "validation_error" || error.code === "not_found");
|
|
16
17
|
/**
|
|
@@ -28,9 +29,9 @@ const filtersBeside = (text) => {
|
|
|
28
29
|
: undefined;
|
|
29
30
|
};
|
|
30
31
|
/**
|
|
31
|
-
* `search all`: messages, mail and
|
|
32
|
+
* `search all`: messages, mail, notes and calendar events from the local store — messages also from the messenger's
|
|
32
33
|
* server, as `search messages` asks it — each list best first, merged by reciprocal rank so no
|
|
33
|
-
* resource's own scores have to be compared with another's.
|
|
34
|
+
* resource's own scores have to be compared with another's. Events are read only when the store holds a calendar.
|
|
34
35
|
*/
|
|
35
36
|
export const searchAll = async (store, account, request, messenger = {}, searchMessages = (query) => searchStore(store, account, query, messenger), meaning) => {
|
|
36
37
|
if (!request.text.trim())
|
|
@@ -48,6 +49,7 @@ export const searchAll = async (store, account, request, messenger = {}, searchM
|
|
|
48
49
|
const skipped = [];
|
|
49
50
|
let hasMore = false;
|
|
50
51
|
let notes;
|
|
52
|
+
let events;
|
|
51
53
|
let messagesBy;
|
|
52
54
|
let server;
|
|
53
55
|
const messagesOf = async (kind) => {
|
|
@@ -133,7 +135,7 @@ export const searchAll = async (store, account, request, messenger = {}, searchM
|
|
|
133
135
|
});
|
|
134
136
|
return [...fused.values()].sort((a, b) => b.score - a.score).map(({ item }) => item);
|
|
135
137
|
};
|
|
136
|
-
// Messages and
|
|
138
|
+
// Messages, notes and events all look up by meaning with the same model: loading it costs most of a second.
|
|
137
139
|
const embedders = runEmbedders();
|
|
138
140
|
try {
|
|
139
141
|
for (const resource of only) {
|
|
@@ -172,6 +174,38 @@ export const searchAll = async (store, account, request, messenger = {}, searchM
|
|
|
172
174
|
];
|
|
173
175
|
accounts.push(...read.map((account) => ({ resource, provider: "notes", account })));
|
|
174
176
|
}
|
|
177
|
+
else if (resource === "events") {
|
|
178
|
+
const read = request.scope?.events ?? (await calendarAccounts(store));
|
|
179
|
+
if (!read.length)
|
|
180
|
+
continue;
|
|
181
|
+
const found = await searchEvents(store, request.text, {
|
|
182
|
+
limit: window,
|
|
183
|
+
accounts: read,
|
|
184
|
+
embedders,
|
|
185
|
+
...(command === undefined ? {} : { command }),
|
|
186
|
+
...(request.env === undefined ? {} : { env: request.env }),
|
|
187
|
+
...(request.exact ? { exact: true } : {}),
|
|
188
|
+
...(request.timezone === undefined ? {} : { timezone: request.timezone }),
|
|
189
|
+
...(request.signal === undefined ? {} : { signal: request.signal }),
|
|
190
|
+
});
|
|
191
|
+
hasMore ||= found.has_more;
|
|
192
|
+
events = {
|
|
193
|
+
by: found.by,
|
|
194
|
+
...(found.meaning_skipped === undefined ? {} : { meaningSkipped: found.meaning_skipped }),
|
|
195
|
+
};
|
|
196
|
+
lists.push(found.hits.map((hit) => ({
|
|
197
|
+
kind: "event",
|
|
198
|
+
ref: hit.ref,
|
|
199
|
+
provider: hit.provider ?? "calendar",
|
|
200
|
+
account: hit.account ?? "",
|
|
201
|
+
...(hit.url ? { url: hit.url } : {}),
|
|
202
|
+
title: hit.title,
|
|
203
|
+
timestamp: hit.starts_at ?? hit.updated_at,
|
|
204
|
+
text: hit.excerpt,
|
|
205
|
+
foundBy: hit.found_by,
|
|
206
|
+
})));
|
|
207
|
+
accounts.push(...read.map(({ provider, account }) => ({ resource, provider, account })));
|
|
208
|
+
}
|
|
175
209
|
else
|
|
176
210
|
lists.push(await messagesOf(resource));
|
|
177
211
|
searched.push(resource);
|
|
@@ -204,6 +238,7 @@ export const searchAll = async (store, account, request, messenger = {}, searchM
|
|
|
204
238
|
accounts,
|
|
205
239
|
skipped,
|
|
206
240
|
...(notes === undefined ? {} : { notes }),
|
|
241
|
+
...(events === undefined ? {} : { events }),
|
|
207
242
|
...(messagesBy === undefined ? {} : { messages: messagesBy }),
|
|
208
243
|
...(server === undefined ? {} : { server }),
|
|
209
244
|
};
|
package/dist/store/index.d.ts
CHANGED
|
@@ -30,6 +30,7 @@ export type { CatalogActor, CatalogEntry, CatalogInput, CatalogQuery, CatalogSto
|
|
|
30
30
|
export { CATALOG_QUERY } from "./sqlite/catalog.js";
|
|
31
31
|
export type { ChatMetadata } from "./sqlite/chats.js";
|
|
32
32
|
export type { Email, EmailAddress, EmailAttachment, EmailFacts, EmailFilter, EmailInput, EmailInThread, EmailRecipient, EmailRef, EmailThread, EmailVectorHit, Mailbox, MailboxInput, MailFilter, MailStore, SenderScore, ThreadDetails, ThreadSave, } from "./sqlite/emails.js";
|
|
33
|
+
export { EVENT_FIELDS, type EventChunkToEmbed, type EventHit, type EventQuery, type EventSearch, type NearestEvent, } from "./sqlite/event-search.js";
|
|
33
34
|
export type { Fact, FactInput, FactsStore } from "./sqlite/facts.js";
|
|
34
35
|
export type { FileListQuery, FilesStore, StoredFile } from "./sqlite/files.js";
|
|
35
36
|
export { IMPORTED_DOCUMENT_QUERY, type ImportedDocuments } from "./sqlite/imported-documents.js";
|
package/dist/store/index.js
CHANGED
|
@@ -16,6 +16,7 @@ export { planProjectAnnotationSnapshot } from "./project-transfer/snapshot-plan.
|
|
|
16
16
|
export { resetAttachmentWords } from "./sqlite/attachment-texts.js";
|
|
17
17
|
export { backfillNormalized, pendingNormalization } from "./sqlite/backfill.js";
|
|
18
18
|
export { CATALOG_QUERY } from "./sqlite/catalog.js";
|
|
19
|
+
export { EVENT_FIELDS, } from "./sqlite/event-search.js";
|
|
19
20
|
export { IMPORTED_DOCUMENT_QUERY } from "./sqlite/imported-documents.js";
|
|
20
21
|
export { ORGANIZATION_KINDS, PROJECT_TYPES, SCOPES } from "./sqlite/knowledge.js";
|
|
21
22
|
export { NOTE_FIELDS, } from "./sqlite/note-search.js";
|
|
@@ -22,7 +22,7 @@ export declare const projectAnnotationRequest: (request: ProjectAnnotationTarget
|
|
|
22
22
|
ref: string;
|
|
23
23
|
from: string;
|
|
24
24
|
to: string | null;
|
|
25
|
-
kind: "about" | "answered-by" | "assigned-to" | "blocked-by" | "created-from" | "duplicate-of" | "evidence" | "follow-up-of" | "labelled" | "links-to" | "member-of" | "related-to";
|
|
25
|
+
kind: "about" | "answered-by" | "assigned-to" | "attached" | "blocked-by" | "created-from" | "duplicate-of" | "evidence" | "follow-up-of" | "labelled" | "links-to" | "member-of" | "related-to";
|
|
26
26
|
origin: "file" | "message" | "owner" | "suggested";
|
|
27
27
|
anchor: string | null;
|
|
28
28
|
target_text: string | null;
|