@wirecat/cli-core 0.563.0 → 0.565.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 +125 -0
- package/dist/calendar/busy.d.ts +10 -10
- package/dist/calendar/busy.js +9 -9
- package/dist/calendar/cli/answers.d.ts +7 -8
- package/dist/calendar/cli/answers.js +4 -5
- package/dist/calendar/cli/bodies.d.ts +33 -0
- package/dist/calendar/cli/bodies.js +52 -0
- package/dist/calendar/cli/files.d.ts +8 -0
- package/dist/calendar/cli/files.js +41 -0
- package/dist/calendar/cli/index.d.ts +58 -51
- package/dist/calendar/cli/index.js +311 -284
- package/dist/calendar/cli/remind.d.ts +2 -11
- package/dist/calendar/cli/remind.js +81 -81
- package/dist/calendar/cli/search-all.d.ts +3 -8
- package/dist/calendar/cli/search-all.js +47 -42
- package/dist/calendar/mcp/index.d.ts +1 -1
- package/dist/calendar/prepare.d.ts +1 -1
- package/dist/calendar/testing/answers.d.ts +27 -5
- package/dist/calendar/testing/answers.js +88 -17
- package/dist/calendar/testing/writer.d.ts +1 -0
- package/dist/calendar/testing/writer.js +6 -0
- package/dist/calendar/writer.d.ts +6 -1
- package/dist/calendar/writes.d.ts +3 -1
- package/dist/calendar/writes.js +42 -20
- package/dist/cli/config-command.js +1 -1
- package/dist/cli/config-issues.d.ts +3 -1
- package/dist/cli/config-issues.js +2 -2
- package/dist/cli/contract-evidence.d.ts +1 -1
- package/dist/cli/early-refusal.d.ts +2 -1
- package/dist/cli/early-refusal.js +4 -2
- package/dist/cli/entry.d.ts +6 -2
- package/dist/cli/entry.js +4 -1
- package/dist/cli/envelope.d.ts +2 -2
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +1 -0
- package/dist/cli/messenger/archive-commands.js +17 -0
- package/dist/cli/messenger/catch-up-answers.d.ts +5 -0
- package/dist/cli/messenger/catch-up-answers.js +3 -0
- package/dist/cli/messenger/search-answers.d.ts +1 -0
- package/dist/cli/messenger/search-answers.js +1 -1
- package/dist/cli/messenger/stats-answers.d.ts +3 -3
- package/dist/cli/messenger/stats-answers.js +21 -9
- package/dist/cli/messenger/store-maintenance-command.d.ts +1 -1
- package/dist/cli/messenger/tasks-command.js +35 -14
- package/dist/cli/permission-keys.d.ts +3 -1
- package/dist/cli/permission-keys.js +2 -1
- package/dist/cli/preview.d.ts +12 -0
- package/dist/cli/preview.js +8 -1
- package/dist/cli/settings.d.ts +2 -0
- package/dist/cli/settings.js +8 -1
- package/dist/conversations/mail-text.d.ts +6 -0
- package/dist/conversations/mail-text.js +73 -0
- package/dist/domain/calendar.d.ts +2 -0
- package/dist/domain/calendar.js +6 -0
- package/dist/entities/chat-stats.d.ts +16 -2
- package/dist/entities/chat-stats.js +18 -3
- package/dist/entities/index.d.ts +50 -802
- package/dist/entities/index.js +23 -16
- package/dist/entities/mail-counts.d.ts +15 -0
- package/dist/entities/mail-counts.js +16 -0
- package/dist/entities/meeting.d.ts +1 -1
- package/dist/entities/meeting.js +1 -1
- package/dist/entities/store-records.d.ts +8 -0
- package/dist/entities/store-records.js +8 -0
- package/dist/errors/reasons.js +19 -1
- package/dist/mail/cli/index.d.ts +5 -0
- package/dist/mail/cli/index.js +27 -3
- package/dist/mail/cli/review.js +11 -4
- package/dist/mail/index.d.ts +2 -0
- package/dist/mail/index.js +2 -0
- package/dist/mail/stats.d.ts +50 -0
- package/dist/mail/stats.js +60 -0
- package/dist/mail/whole.d.ts +4 -4
- package/dist/mail/whole.js +4 -3
- package/dist/mcp/meeting-preparation.d.ts +1 -1
- package/dist/mcp/meeting-project-candidates.d.ts +1 -1
- package/dist/mcp/meeting-series-inventory.d.ts +1 -1
- package/dist/meetings/index.d.ts +2 -2
- package/dist/meetings/index.js +2 -2
- package/dist/meetings/mcp/index.d.ts +15 -15
- package/dist/meetings/mcp/passage.d.ts +1 -1
- package/dist/meetings/mcp/project-evidence.d.ts +1 -1
- package/dist/meetings/mcp/search.d.ts +1 -1
- package/dist/meetings/read-answers.d.ts +22 -1
- package/dist/meetings/read-answers.js +38 -0
- package/dist/meetings/stats.d.ts +44 -0
- package/dist/meetings/stats.js +77 -7
- package/dist/search/lucene/registry.js +2 -1
- package/dist/search/question-plan.d.ts +16 -0
- package/dist/search/question-plan.js +34 -0
- package/dist/services/events-search.d.ts +5 -1
- package/dist/services/events-search.js +10 -4
- package/dist/services/index.d.ts +1 -1
- package/dist/services/index.js +1 -1
- package/dist/services/meeting-stats.d.ts +12 -1
- package/dist/services/meeting-stats.js +12 -0
- package/dist/services/messages-discovery.d.ts +1 -5
- package/dist/services/messages-discovery.js +5 -18
- package/dist/services/messages-meaning.js +25 -3
- package/dist/services/messages.js +4 -3
- package/dist/services/metadata.d.ts +1 -1
- package/dist/services/notes-search.d.ts +5 -1
- package/dist/services/notes-search.js +16 -5
- package/dist/services/proposals/ids.d.ts +2 -2
- package/dist/services/proposals/ids.js +5 -3
- package/dist/services/proposals/kinds/findings.d.ts +1 -1
- package/dist/services/proposals/kinds/findings.js +1 -1
- package/dist/services/proposals/kinds/meeting-findings.d.ts +7 -0
- package/dist/services/proposals/kinds/meeting-findings.js +73 -0
- package/dist/services/proposals/kinds/task-close.d.ts +7 -0
- package/dist/services/proposals/kinds/task-close.js +76 -0
- package/dist/services/proposals/registry.js +4 -0
- package/dist/services/search-all.d.ts +5 -0
- package/dist/services/search-all.js +47 -14
- package/dist/services/task-stats.d.ts +41 -0
- package/dist/services/task-stats.js +126 -0
- package/dist/services/tasks.d.ts +17 -4
- package/dist/services/tasks.js +31 -14
- package/dist/store/chats.d.ts +3 -1
- package/dist/store/chats.js +6 -0
- package/dist/store/decisions.d.ts +4 -2
- package/dist/store/decisions.js +5 -3
- package/dist/store/exports.d.ts +1 -1
- package/dist/store/exports.js +1 -1
- package/dist/store/index.d.ts +2 -1
- package/dist/store/index.js +1 -1
- package/dist/store/sqlite/completeness.d.ts +17 -0
- package/dist/store/sqlite/completeness.js +49 -13
- package/dist/store/sqlite/lucene-fields.js +2 -0
- package/dist/store/sqlite/lucene-groups.d.ts +15 -0
- package/dist/store/sqlite/lucene-groups.js +21 -0
- package/dist/store/sqlite/lucene.d.ts +2 -9
- package/dist/store/sqlite/lucene.js +1 -12
- package/dist/store/sqlite/meeting-stats.d.ts +2 -0
- package/dist/store/sqlite/meeting-stats.js +27 -1
- package/dist/store/sqlite/retention.js +2 -7
- package/dist/store/sqlite/task-board.js +2 -0
- package/dist/store/sqlite/task-stats.d.ts +32 -0
- package/dist/store/sqlite/task-stats.js +45 -0
- package/dist/store/sqlite/tasks.d.ts +2 -1
- package/dist/store/sqlite/tasks.js +2 -0
- package/package.json +3 -3
- package/parity.json +13 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { MeetingHitSearchRequest, MeetingStats } from "../meetings/index.js";
|
|
1
|
+
import type { MeetingCounts, MeetingCountsGrouping, MeetingHitSearchRequest, MeetingStats } from "../meetings/index.js";
|
|
2
2
|
import type { MessageStore } from "../store/store.js";
|
|
3
3
|
import { resolveMeetingSelection } from "./meeting-hit-search.js";
|
|
4
4
|
export type MeetingStatsRequest = Omit<MeetingHitSearchRequest, "limit" | "after" | "maxReadBytes"> & {
|
|
@@ -11,3 +11,14 @@ export declare const readMeetingStats: (store: Pick<MessageStore, "meetings"> &
|
|
|
11
11
|
}, request: MeetingStatsRequest) => Promise<MeetingStats & {
|
|
12
12
|
selection: Awaited<ReturnType<typeof resolveMeetingSelection>>["selection"];
|
|
13
13
|
}>;
|
|
14
|
+
export type MeetingCountsRequest = Omit<MeetingHitSearchRequest, "limit" | "after" | "maxReadBytes"> & {
|
|
15
|
+
by: MeetingCountsGrouping;
|
|
16
|
+
timeZone?: string;
|
|
17
|
+
limit?: number;
|
|
18
|
+
};
|
|
19
|
+
/** Meetings, minutes and transcripts per day, week or series over the meetings search would read. */
|
|
20
|
+
export declare const readMeetingCounts: (store: Pick<MessageStore, "meetings"> & {
|
|
21
|
+
knowledge?: MessageStore["knowledge"];
|
|
22
|
+
}, request: MeetingCountsRequest) => Promise<MeetingCounts & {
|
|
23
|
+
selection: Awaited<ReturnType<typeof resolveMeetingSelection>>["selection"];
|
|
24
|
+
}>;
|
|
@@ -10,3 +10,15 @@ export const readMeetingStats = async (store, request) => {
|
|
|
10
10
|
});
|
|
11
11
|
return { ...stats, selection };
|
|
12
12
|
};
|
|
13
|
+
/** Meetings, minutes and transcripts per day, week or series over the meetings search would read. */
|
|
14
|
+
export const readMeetingCounts = async (store, request) => {
|
|
15
|
+
const { by, timeZone, limit, ...selecting } = request;
|
|
16
|
+
const { input, selection } = await resolveMeetingSelection(store, selecting);
|
|
17
|
+
const counts = await store.meetings.meetingCounts({
|
|
18
|
+
...input,
|
|
19
|
+
by,
|
|
20
|
+
...(timeZone === undefined ? {} : { timeZone }),
|
|
21
|
+
...(limit === undefined ? {} : { limit }),
|
|
22
|
+
});
|
|
23
|
+
return { ...counts, selection };
|
|
24
|
+
};
|
|
@@ -70,11 +70,7 @@ within?: () => Promise<number[] | undefined>) => Promise<{
|
|
|
70
70
|
export declare const RANK_WITHIN_MESSAGES = 100000;
|
|
71
71
|
/** What a hit not found by the query's words must match: as a search request's text or AST. */
|
|
72
72
|
type Scope = Pick<SearchQuery, "text" | "ast">;
|
|
73
|
-
/**
|
|
74
|
-
* The query discovery searches. A question's own "?" would parse as a one-character wildcard and send it to strict
|
|
75
|
-
* search (BUG-395); the colon of a time or ratio ("22:00", "1:1") would parse as a field and fail the whole search,
|
|
76
|
-
* and escaped it is syntax again, so it splits the numbers (BUG-450). A quoted timestamp keeps its colons (BUG-458).
|
|
77
|
-
*/
|
|
73
|
+
/** The query discovery searches: a question's text cleaned as `questionText` says. */
|
|
78
74
|
export declare const discoveryRequest: (asked: SearchQuery) => SearchQuery;
|
|
79
75
|
export declare const searchDiscovery: (store: MessageStore, account: AccountKey, asked: SearchQuery, messenger?: Partial<Pick<MessengerTraits, "savedChatId" | "app" | "history" | "provider">>, meaning?: MeaningLane, rerank?: RerankLane) => Promise<SearchFound>;
|
|
80
76
|
/** The nearest messages the question's filters allow, as hits, nearest first. */
|
|
@@ -4,7 +4,7 @@ import { parseLocator } from "../domain/locator.js";
|
|
|
4
4
|
import { fieldsOfQuery, meaningWords } from "../search/lucene/meaning-parts.js";
|
|
5
5
|
import { validateFields } from "../search/lucene/registry.js";
|
|
6
6
|
import { exhausted, MESSAGE_PARSING, outOfTime, QUERY_LIMITS } from "../search/lucene/types.js";
|
|
7
|
-
import { planQuestion, words } from "../search/question-plan.js";
|
|
7
|
+
import { anyOfTerms, lookupTerms, planQuestion, questionText, words } from "../search/question-plan.js";
|
|
8
8
|
import { searchCombined } from "./messages-combined.js";
|
|
9
9
|
import { prepareLucene, searchLucene } from "./messages-search.js";
|
|
10
10
|
/** How many of the messages nearest in meaning join the fusion: the benchmark's pool (performance/search/evidence-pipeline). */
|
|
@@ -49,19 +49,8 @@ const EVERY_MESSAGE = {
|
|
|
49
49
|
upper_inclusive: true,
|
|
50
50
|
};
|
|
51
51
|
const scopeOf = (root) => root === undefined ? {} : { ast: { version: 1, language: "lucene-v1", root } };
|
|
52
|
-
/**
|
|
53
|
-
|
|
54
|
-
* search (BUG-395); the colon of a time or ratio ("22:00", "1:1") would parse as a field and fail the whole search,
|
|
55
|
-
* and escaped it is syntax again, so it splits the numbers (BUG-450). A quoted timestamp keeps its colons (BUG-458).
|
|
56
|
-
*/
|
|
57
|
-
export const discoveryRequest = (asked) => asked.ast !== undefined || asked.exact || !asked.text
|
|
58
|
-
? asked
|
|
59
|
-
: {
|
|
60
|
-
...asked,
|
|
61
|
-
text: asked.text
|
|
62
|
-
.replace(/(?<=\p{L})\?(?=\s|$)/gu, "")
|
|
63
|
-
.replace(/"(?:[^"\\]|\\.)*"|(?<=\d):(?=\d)/gu, (found) => (found === ":" ? " " : found)),
|
|
64
|
-
};
|
|
52
|
+
/** The query discovery searches: a question's text cleaned as `questionText` says. */
|
|
53
|
+
export const discoveryRequest = (asked) => asked.ast !== undefined || asked.exact || !asked.text ? asked : { ...asked, text: questionText(asked.text) };
|
|
65
54
|
export const searchDiscovery = async (store, account, asked, messenger = {}, meaning, rerank) => {
|
|
66
55
|
const request = discoveryRequest(asked);
|
|
67
56
|
const original = await prepareLucene(store, account, request, messenger);
|
|
@@ -82,9 +71,7 @@ export const searchDiscovery = async (store, account, asked, messenger = {}, mea
|
|
|
82
71
|
const question = root === undefined ? text : meaningWords(root);
|
|
83
72
|
if (!question)
|
|
84
73
|
return searchLucene(store, account, request, messenger);
|
|
85
|
-
const terms = root === undefined
|
|
86
|
-
? [...new Set(plan.terms.concat(plan.aliases.flatMap((alias) => words(alias.split(" -> ")[1] ?? ""))))]
|
|
87
|
-
: [...new Set(words(question))];
|
|
74
|
+
const terms = root === undefined ? lookupTerms(plan) : [...new Set(words(question))];
|
|
88
75
|
if (!terms.length)
|
|
89
76
|
return searchLucene(store, account, request, messenger);
|
|
90
77
|
if (terms.length > 64)
|
|
@@ -125,7 +112,7 @@ export const searchDiscovery = async (store, account, asked, messenger = {}, mea
|
|
|
125
112
|
truncated ||= found.hasMore || found.query.combined.truncated;
|
|
126
113
|
}
|
|
127
114
|
check();
|
|
128
|
-
const relaxed = root === undefined ?
|
|
115
|
+
const relaxed = root === undefined ? anyOfTerms(terms, plan.scope) : text;
|
|
129
116
|
const partialRequest = { ...request, text: relaxed, ast: undefined, limit: 300, context: 0, thread: undefined };
|
|
130
117
|
const partial = await searchLucene(store, account, partialRequest, messenger);
|
|
131
118
|
add(partial.items);
|
|
@@ -16,9 +16,19 @@ export const meaningLane = (deps) => async (question, accounts, shared, within)
|
|
|
16
16
|
const store = await deps.store();
|
|
17
17
|
const ready = [];
|
|
18
18
|
const without = [];
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
// Mail keeps its vectors by thread, not by message: its accounts are read through the mail store.
|
|
20
|
+
const mail = new Map();
|
|
21
|
+
const stored = await store.accounts.storedAccounts();
|
|
22
|
+
for (const one of accounts) {
|
|
23
|
+
const id = one.provider === "email"
|
|
24
|
+
? stored.find(({ provider, account }) => provider === one.provider && account === one.account)?.id
|
|
25
|
+
: undefined;
|
|
26
|
+
if (id !== undefined)
|
|
27
|
+
mail.set(one, Number(id));
|
|
28
|
+
else
|
|
29
|
+
((await store.vectors.hasMessageVectors(one, target.key)) ? ready : without).push(one);
|
|
30
|
+
}
|
|
31
|
+
if (ready.length === 0 && mail.size === 0)
|
|
22
32
|
return skipped("no message has a vector yet", "conversations.rebuild");
|
|
23
33
|
const words = plainWords(question);
|
|
24
34
|
try {
|
|
@@ -38,12 +48,24 @@ export const meaningLane = (deps) => async (question, accounts, shared, within)
|
|
|
38
48
|
for (const { chatId, messageId, score } of near)
|
|
39
49
|
ranked.push({ ...one, chatId, id: messageId, score });
|
|
40
50
|
}
|
|
51
|
+
for (const [one, accountId] of mail) {
|
|
52
|
+
const near = await store.mail.nearest(target.key, query, {
|
|
53
|
+
accountId,
|
|
54
|
+
limit: MEANING_DEPTH,
|
|
55
|
+
});
|
|
56
|
+
if (near.length === 0)
|
|
57
|
+
without.push(one);
|
|
58
|
+
for (const { threadExternalId, externalId, score } of near)
|
|
59
|
+
ranked.push({ ...one, chatId: threadExternalId, id: externalId, score });
|
|
60
|
+
}
|
|
41
61
|
};
|
|
42
62
|
if (shared && !deps.embedders)
|
|
43
63
|
await lookUp(await shared.get(target.key, () => target.open()));
|
|
44
64
|
else
|
|
45
65
|
await withEmbedder(deps, target, DEFAULT_TEXT_MODEL, {}, lookUp);
|
|
46
66
|
}
|
|
67
|
+
if (ranked.length === 0 && without.length === accounts.length)
|
|
68
|
+
return skipped("no message has a vector yet", "conversations.rebuild");
|
|
47
69
|
return {
|
|
48
70
|
model: target.id,
|
|
49
71
|
ranked: ranked.sort((a, b) => b.score - a.score).slice(0, MEANING_DEPTH),
|
|
@@ -40,7 +40,7 @@ export const messagesService = (deps) => {
|
|
|
40
40
|
return;
|
|
41
41
|
await ensureAccount({ text: target.text, account: target.account }, { account: account.account, ...(deps.profile === undefined ? {} : { profile: deps.profile }) });
|
|
42
42
|
};
|
|
43
|
-
const serverThenStore = async (store, account, query) => {
|
|
43
|
+
const serverThenStore = async (store, account, query, lanes = true) => {
|
|
44
44
|
const server = await searchServer(deps, query);
|
|
45
45
|
const only = query.backend === "server" && server
|
|
46
46
|
? [...server.sources.keys()].map((key) => {
|
|
@@ -48,12 +48,13 @@ export const messagesService = (deps) => {
|
|
|
48
48
|
return { ...account, chatId, id };
|
|
49
49
|
})
|
|
50
50
|
: undefined;
|
|
51
|
-
const found = await searchStore(store, account, only ? { ...query, only } : query, deps.messenger, query.discover && query.kind !== "mail" ? meaningLane(deps) : undefined, query.discover && query.kind !== "mail" ? rerankLane(deps) : undefined);
|
|
51
|
+
const found = await searchStore(store, account, only ? { ...query, only } : query, deps.messenger, lanes && query.discover && query.kind !== "mail" ? meaningLane(deps) : undefined, lanes && query.discover && query.kind !== "mail" ? rerankLane(deps) : undefined);
|
|
52
52
|
return { found, server };
|
|
53
53
|
};
|
|
54
54
|
const unifiedMessages = (store, account) => async (query) => {
|
|
55
55
|
await topUp(store);
|
|
56
|
-
|
|
56
|
+
// `search all` adds meaning itself, and a reranker's load would cost it seconds on every question.
|
|
57
|
+
const { found, server } = await serverThenStore(store, account, query, false);
|
|
57
58
|
return withServer(found, account, server);
|
|
58
59
|
};
|
|
59
60
|
/**
|
|
@@ -27,7 +27,7 @@ export declare const metadataService: (deps: ServiceDeps) => {
|
|
|
27
27
|
source?: undefined;
|
|
28
28
|
chatId: string;
|
|
29
29
|
error: {
|
|
30
|
-
code: "authentication_error" | "cancelled" | "configuration_error" | "confirmation_required" | "invalid_response" | "network_error" | "not_found" | "outcome_unknown" | "permission_error" | "provider_error" | "provider_unavailable" | "rate_limited" | "timeout" | "validation_error";
|
|
30
|
+
code: "authentication_error" | "cancelled" | "configuration_error" | "confirmation_required" | "conflict" | "invalid_response" | "network_error" | "not_found" | "outcome_unknown" | "permission_error" | "provider_error" | "provider_unavailable" | "rate_limited" | "timeout" | "validation_error";
|
|
31
31
|
message: string;
|
|
32
32
|
};
|
|
33
33
|
})[];
|
|
@@ -105,6 +105,8 @@ export interface FoundNote {
|
|
|
105
105
|
}[];
|
|
106
106
|
/** Which halves of the search found it. */
|
|
107
107
|
found_by: ("title" | "words" | "meaning")[];
|
|
108
|
+
/** The cosine of the nearest chunk, when meaning found it: comparable with other records under the same model. */
|
|
109
|
+
meaning_score?: number;
|
|
108
110
|
}
|
|
109
111
|
export interface LinkedRecord {
|
|
110
112
|
/** `person:…`, `note:…`, `entity:…`, or `null` while the name written in the notes matches nobody yet. */
|
|
@@ -147,6 +149,8 @@ export interface NotesQuery {
|
|
|
147
149
|
withInternal?: boolean;
|
|
148
150
|
source?: Note["source"];
|
|
149
151
|
exact?: boolean;
|
|
152
|
+
/** A question typed as a sentence finds what holds some of its words, as `search messages --discover` does. */
|
|
153
|
+
discover?: boolean;
|
|
150
154
|
timezone?: string;
|
|
151
155
|
env?: NodeJS.ProcessEnv;
|
|
152
156
|
/** The tool's command, named in the hint when the model is missing. */
|
|
@@ -182,7 +186,7 @@ export declare const RANK_WITHIN = 2000;
|
|
|
182
186
|
* each hit says which half found it, and the answer lists what the notes found link to. Every field of the query and
|
|
183
187
|
* of `filter` holds for every hit, found by words or by meaning; a query of fields alone is not searched by meaning.
|
|
184
188
|
*/
|
|
185
|
-
export declare const searchNotes: (store: MessageStore, query: string, { limit, offset, tag, filter: expression, folderIds, withInternal, source: given, exact, timezone, env, command, embedders, pictures: withPictures, pictureLimit, pictureEmbedder, }?: NotesQuery) => Promise<NotesFound>;
|
|
189
|
+
export declare const searchNotes: (store: MessageStore, query: string, { limit, offset, tag, filter: expression, folderIds, withInternal, source: given, exact, discover, timezone, env, command, embedders, pictures: withPictures, pictureLimit, pictureEmbedder, }?: NotesQuery) => Promise<NotesFound>;
|
|
186
190
|
/** What `notes:internal` and `notes:<folder id>` ask of the store: one query, so a page of it is one page. */
|
|
187
191
|
export declare const notesScopeOf: (accounts: readonly AccountKey[]) => Pick<NotesQuery, "folderIds" | "withInternal" | "source">;
|
|
188
192
|
export interface NotesFoundOf extends NotesFound {
|
|
@@ -10,6 +10,7 @@ import { fieldsOfQuery as fieldsOf, meaningWords } from "../search/lucene/meanin
|
|
|
10
10
|
import { validateFields } from "../search/lucene/registry.js";
|
|
11
11
|
import { hasStems } from "../search/lucene/resolved.js";
|
|
12
12
|
import { isQueryLimit, SEARCH_QUERY } from "../search/lucene/types.js";
|
|
13
|
+
import { questionLookup, questionText } from "../search/question-plan.js";
|
|
13
14
|
import { createStemmer, DEFAULT_STEMMERS } from "../search/stem.js";
|
|
14
15
|
import { TOTAL_CAP, totalOf } from "../search/total.js";
|
|
15
16
|
import { openPictureEmbedderFor } from "./document-pictures.js";
|
|
@@ -324,20 +325,22 @@ const notesTotal = async (store, text, { exact, scope, shown, hasMore, }, others
|
|
|
324
325
|
* each hit says which half found it, and the answer lists what the notes found link to. Every field of the query and
|
|
325
326
|
* of `filter` holds for every hit, found by words or by meaning; a query of fields alone is not searched by meaning.
|
|
326
327
|
*/
|
|
327
|
-
export const searchNotes = async (store, query, { limit = 20, offset = 0, tag, filter: expression, folderIds, withInternal, source: given, exact = false, timezone, env, command, embedders, pictures: withPictures = false, pictureLimit = 5, pictureEmbedder, } = {}) => {
|
|
328
|
+
export const searchNotes = async (store, query, { limit = 20, offset = 0, tag, filter: expression, folderIds, withInternal, source: given, exact = false, discover = false, timezone, env, command, embedders, pictures: withPictures = false, pictureLimit = 5, pictureEmbedder, } = {}) => {
|
|
328
329
|
const typed = noteSourceOfQuery(query);
|
|
329
330
|
const source = given ?? typed.source;
|
|
330
331
|
const rest = typed.source !== undefined && typed.source === source ? typed.rest : query;
|
|
331
332
|
const label = tag === undefined ? undefined : normalizeTag(tag);
|
|
332
333
|
const filter = [expression ? `(${expression})` : "", label === undefined ? "" : `tag:${label}`].filter(Boolean).join(" AND ") ||
|
|
333
334
|
undefined;
|
|
334
|
-
const
|
|
335
|
+
const lookup = discover && !exact ? questionLookup(query) : undefined;
|
|
336
|
+
const parsed = query.trim() ? parsedQuery(lookup === undefined ? query : questionText(query), exact) : undefined;
|
|
335
337
|
const words = parsed === undefined ? "" : meaningWords(parsed);
|
|
336
338
|
const narrowing = allOf([
|
|
337
339
|
...(parsed === undefined ? [] : [fieldsOfQuery(parsed)].filter((node) => node !== undefined)),
|
|
338
340
|
...(filter === undefined ? [] : [parsedQuery(filter)]),
|
|
339
341
|
]);
|
|
340
|
-
const
|
|
342
|
+
const asked = lookup ?? query;
|
|
343
|
+
const text = [asked.trim() ? `(${asked})` : "", filter ?? ""].filter(Boolean).join(" AND ");
|
|
341
344
|
const window = offset + limit;
|
|
342
345
|
const scope = {
|
|
343
346
|
...(folderIds === undefined ? {} : { folderIds }),
|
|
@@ -347,6 +350,7 @@ export const searchNotes = async (store, query, { limit = 20, offset = 0, tag, f
|
|
|
347
350
|
};
|
|
348
351
|
let meaningSkipped;
|
|
349
352
|
let meaning;
|
|
353
|
+
const cosines = new Map();
|
|
350
354
|
/** Every note found by meaning; `meaningComplete` is false when the nearest overall were cut at the depth. */
|
|
351
355
|
let meaningRefs;
|
|
352
356
|
let meaningComplete = true;
|
|
@@ -375,8 +379,10 @@ export const searchNotes = async (store, query, { limit = 20, offset = 0, tag, f
|
|
|
375
379
|
nearest = ranked.slice(0, depth);
|
|
376
380
|
}
|
|
377
381
|
const allowed = await matching(store, nearest.map(({ ref }) => ref), narrowing, scope);
|
|
378
|
-
meaning = nearest.flatMap(({ ref }) => {
|
|
382
|
+
meaning = nearest.flatMap(({ ref, score }) => {
|
|
379
383
|
const found = allowed.get(ref);
|
|
384
|
+
if (found)
|
|
385
|
+
cosines.set(ref, score);
|
|
380
386
|
return found ? [{ ref, ...found }] : [];
|
|
381
387
|
});
|
|
382
388
|
meaningRefs ??= new Set(meaning.map(({ ref }) => ref));
|
|
@@ -410,9 +416,13 @@ export const searchNotes = async (store, query, { limit = 20, offset = 0, tag, f
|
|
|
410
416
|
if (tierRefs.has(ref))
|
|
411
417
|
return;
|
|
412
418
|
const held = ranked.get(ref) ?? { note, at, score: 0, foundBy: [] };
|
|
413
|
-
held.score += 1 / (RRF_K + rank + 1);
|
|
414
419
|
held.foundBy.push(by);
|
|
415
420
|
ranked.set(ref, held);
|
|
421
|
+
// A question's any-word list holds nearly every note meaning finds; a second vote there buried notes found by
|
|
422
|
+
// words alone (recall@10 0.55 against 1.00, cli-testing knowledge-graph-memory, MEMO-36).
|
|
423
|
+
if (by === "meaning" && lookup !== undefined && held.foundBy.includes("words"))
|
|
424
|
+
return;
|
|
425
|
+
held.score += 1 / (RRF_K + rank + 1);
|
|
416
426
|
});
|
|
417
427
|
add(matched.items, "words");
|
|
418
428
|
if (meaning !== undefined)
|
|
@@ -446,6 +456,7 @@ export const searchNotes = async (store, query, { limit = 20, offset = 0, tag, f
|
|
|
446
456
|
excerpt: firstLine(note.text, words),
|
|
447
457
|
links,
|
|
448
458
|
found_by: foundBy,
|
|
459
|
+
...(cosines.has(ref) ? { meaning_score: cosines.get(ref) } : {}),
|
|
449
460
|
});
|
|
450
461
|
for (const link of new Map(links.map((link) => [link.to ?? `?${link.target_text}`, link])).values()) {
|
|
451
462
|
const key = link.to ?? `?${link.target_text}`;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Proposal ids from the command line, before any reference parsing: `17070-17079,fact:500-502,17080/f1`
|
|
3
|
-
* each id once, in the order given.
|
|
2
|
+
* Proposal ids from the command line, before any reference parsing: `17070-17079,fact:500-502,17080/f1,close:Q-15`
|
|
3
|
+
* becomes each id once, in the order given. A task key after a prefix is one id, never a range.
|
|
4
4
|
*/
|
|
5
5
|
export declare const parseProposalIds: (text: readonly string[]) => string[];
|
|
@@ -18,6 +18,8 @@ const one = (token) => {
|
|
|
18
18
|
const part = /^([1-9]\d*)\/([A-Za-z0-9_-]{1,64})$/.exec(token);
|
|
19
19
|
if (part)
|
|
20
20
|
return [token];
|
|
21
|
+
if (/^[a-z][a-z_]*:[A-Za-z][A-Za-z0-9]*-[1-9]\d*$/.test(token))
|
|
22
|
+
return [token];
|
|
21
23
|
const prefixed = /^([a-z][a-z_]*):([1-9]\d*)(?:-(?:([a-z][a-z_]*):)?([1-9]\d*))?$/.exec(token);
|
|
22
24
|
if (prefixed?.[1] && prefixed[2]) {
|
|
23
25
|
const [, prefix, from, other, to] = prefixed;
|
|
@@ -27,11 +29,11 @@ const one = (token) => {
|
|
|
27
29
|
}
|
|
28
30
|
if (/^[1-9]\d*-[a-z]/.test(token))
|
|
29
31
|
return refuse(`a range stays inside one prefix: ${token}`);
|
|
30
|
-
return refuse(`not a proposal id: ${token}; use 17070, 17070-17079, fact:512 or 17080/f2`);
|
|
32
|
+
return refuse(`not a proposal id: ${token}; use 17070, 17070-17079, fact:512, close:Q-15 or 17080/f2`);
|
|
31
33
|
};
|
|
32
34
|
/**
|
|
33
|
-
* Proposal ids from the command line, before any reference parsing: `17070-17079,fact:500-502,17080/f1`
|
|
34
|
-
* each id once, in the order given.
|
|
35
|
+
* Proposal ids from the command line, before any reference parsing: `17070-17079,fact:500-502,17080/f1,close:Q-15`
|
|
36
|
+
* becomes each id once, in the order given. A task key after a prefix is one id, never a range.
|
|
35
37
|
*/
|
|
36
38
|
export const parseProposalIds = (text) => {
|
|
37
39
|
const ids = [...new Set(text.flatMap((arg) => arg.split(",")).flatMap((token) => one(token.trim())))];
|
|
@@ -5,6 +5,6 @@ import type { ProposalKind } from "../types.js";
|
|
|
5
5
|
export declare const FACT_SUMMARIES: Record<string, (finding: Finding) => string>;
|
|
6
6
|
/**
|
|
7
7
|
* An agent's batch of cited facts and tasks from stored messages, mail and documents. Confirming accepts the parts
|
|
8
|
-
* named, or every finding not rejected before; the rest are rejected. Meeting findings
|
|
8
|
+
* named, or every finding not rejected before; the rest are rejected. Meeting findings have their own kind.
|
|
9
9
|
*/
|
|
10
10
|
export declare const findingsKind: (store: Pick<MessageStore, "evidenceFindings" | "proposedActions">) => ProposalKind;
|
|
@@ -35,7 +35,7 @@ const rowOf = (batch, createdAt) => {
|
|
|
35
35
|
};
|
|
36
36
|
/**
|
|
37
37
|
* An agent's batch of cited facts and tasks from stored messages, mail and documents. Confirming accepts the parts
|
|
38
|
-
* named, or every finding not rejected before; the rest are rejected. Meeting findings
|
|
38
|
+
* named, or every finding not rejected before; the rest are rejected. Meeting findings have their own kind.
|
|
39
39
|
*/
|
|
40
40
|
export const findingsKind = (store) => {
|
|
41
41
|
// The batch view carries no staging time; its stored proposal does.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type MessageStore } from "../../../store/index.js";
|
|
2
|
+
import type { ProposalKind } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* A meeting's cited findings, staged by `meetings actions add`: the same review as `meetings actions confirm`, with
|
|
5
|
+
* the batch's current fingerprint. Only the owner decides it.
|
|
6
|
+
*/
|
|
7
|
+
export declare const meetingFindingsKind: (store: Pick<MessageStore, "meetingFindings" | "proposedActions">) => ProposalKind;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { CliError } from "@wirecat/cli-base";
|
|
2
|
+
import { MEETING_FINDINGS_KIND } from "../../../meetings/index.js";
|
|
3
|
+
import { proposerOfAction, } from "../../../store/index.js";
|
|
4
|
+
import { FACT_SUMMARIES } from "./findings.js";
|
|
5
|
+
import { kept } from "./shared.js";
|
|
6
|
+
const rowOf = (action, batch) => {
|
|
7
|
+
const findings = batch.input.findings;
|
|
8
|
+
const projects = [...new Set(findings.map((one) => ("project" in one ? one.project : undefined)))];
|
|
9
|
+
const outcome = (id) => batch.receipt?.items.find((item) => item.id === id)?.status ?? null;
|
|
10
|
+
const references = [...new Set(findings.flatMap((finding) => finding.evidence.map((one) => one.reference)))];
|
|
11
|
+
return {
|
|
12
|
+
id: action.id,
|
|
13
|
+
kind: "meeting_findings",
|
|
14
|
+
status: batch.status === "proposed" ? "proposed" : batch.receipt?.accepted.length ? "confirmed" : "rejected",
|
|
15
|
+
project: projects.length === 1 && typeof projects[0] === "string" ? projects[0] : null,
|
|
16
|
+
subject: references[0] ?? `meeting_findings:${action.id}`,
|
|
17
|
+
object: null,
|
|
18
|
+
summary: `${findings.length} finding${findings.length === 1 ? "" : "s"}`,
|
|
19
|
+
proposedBy: proposerOfAction(action),
|
|
20
|
+
reason: action.reason,
|
|
21
|
+
evidence: references.slice(0, 5),
|
|
22
|
+
score: null,
|
|
23
|
+
signals: null,
|
|
24
|
+
parts: findings.map((finding) => ({
|
|
25
|
+
id: finding.id,
|
|
26
|
+
kind: finding.kind,
|
|
27
|
+
summary: FACT_SUMMARIES[finding.kind]?.(finding) ?? finding.kind,
|
|
28
|
+
outcome: outcome(finding.id),
|
|
29
|
+
})),
|
|
30
|
+
createdAt: action.createdAt,
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* A meeting's cited findings, staged by `meetings actions add`: the same review as `meetings actions confirm`, with
|
|
35
|
+
* the batch's current fingerprint. Only the owner decides it.
|
|
36
|
+
*/
|
|
37
|
+
export const meetingFindingsKind = (store) => {
|
|
38
|
+
const batchOf = async (action) => {
|
|
39
|
+
if (action.account === null)
|
|
40
|
+
throw new CliError("not_found", `meeting findings ${action.id} name no account`);
|
|
41
|
+
return { account: action.account, batch: await store.meetingFindings.get(action.account, action.id) };
|
|
42
|
+
};
|
|
43
|
+
return {
|
|
44
|
+
kind: "meeting_findings",
|
|
45
|
+
stored: [MEETING_FINDINGS_KIND],
|
|
46
|
+
parts: true,
|
|
47
|
+
ownerOnly: true,
|
|
48
|
+
list: async (filter) => {
|
|
49
|
+
const actions = await store.proposedActions.list({
|
|
50
|
+
kinds: [MEETING_FINDINGS_KIND],
|
|
51
|
+
...((filter.status ?? "proposed") === "proposed" ? { status: "proposed" } : {}),
|
|
52
|
+
...(filter.by === undefined ? {} : { proposedBy: filter.by }),
|
|
53
|
+
limit: Math.min(filter.offset + filter.limit, 500),
|
|
54
|
+
});
|
|
55
|
+
const rows = await Promise.all(actions.map(async (action) => rowOf(action, (await batchOf(action)).batch)));
|
|
56
|
+
return kept(rows, { ...filter, limit: filter.offset + filter.limit });
|
|
57
|
+
},
|
|
58
|
+
get: async (id) => {
|
|
59
|
+
const action = await store.proposedActions.get(id);
|
|
60
|
+
return rowOf(action, (await batchOf(action)).batch);
|
|
61
|
+
},
|
|
62
|
+
decide: async (id, { confirm, by, parts }) => {
|
|
63
|
+
const { account, batch } = await batchOf(await store.proposedActions.get(id));
|
|
64
|
+
const turnedDown = new Set(batch.previous.filter((one) => one.status === "rejected").map((one) => one.id));
|
|
65
|
+
const accept = !confirm
|
|
66
|
+
? []
|
|
67
|
+
: (parts ?? batch.input.findings.map((finding) => finding.id).filter((one) => !turnedDown.has(one)));
|
|
68
|
+
const reviewed = await store.meetingFindings.review(account, id, { fingerprint: batch.fingerprint, accept, by });
|
|
69
|
+
const made = (reviewed.receipt?.items ?? []).flatMap((item) => item.status === "accepted" && item.reference && !item.reused ? [item.reference] : []);
|
|
70
|
+
return { id, kind: "meeting_findings", status: accept.length ? "confirmed" : "rejected", made };
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { MessageStore } from "../../../store/index.js";
|
|
2
|
+
import type { ProposalKind } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* An agent's close of an open task, waiting on the owner (`close_suggested_*`). Confirming closes it as suggested;
|
|
5
|
+
* rejecting clears the suggestion, as `tasks reject` does. Its id is `close:<task key>`; only the owner decides it.
|
|
6
|
+
*/
|
|
7
|
+
export declare const taskCloseKind: (store: Pick<MessageStore, "tasks">) => ProposalKind;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { CliError } from "@wirecat/cli-base";
|
|
2
|
+
import { deciderAuthor, TASK_CLOSE_KIND } from "../../../store/decisions.js";
|
|
3
|
+
import { createTaskService } from "../../../tasks/index.js";
|
|
4
|
+
import { kept } from "./shared.js";
|
|
5
|
+
const rowOf = (task) => {
|
|
6
|
+
const suggestion = task.closeSuggestion;
|
|
7
|
+
if (suggestion === null)
|
|
8
|
+
return undefined;
|
|
9
|
+
return {
|
|
10
|
+
id: `close:${task.key}`,
|
|
11
|
+
kind: TASK_CLOSE_KIND,
|
|
12
|
+
status: "proposed",
|
|
13
|
+
project: task.project,
|
|
14
|
+
subject: task.ref,
|
|
15
|
+
object: null,
|
|
16
|
+
summary: `close as ${suggestion.as}: ${task.title}`,
|
|
17
|
+
proposedBy: { type: "bot", id: suggestion.by, name: suggestion.by, kind: "agent" },
|
|
18
|
+
reason: suggestion.reason,
|
|
19
|
+
evidence: task.source === null ? [] : [task.source],
|
|
20
|
+
score: suggestion.certainty,
|
|
21
|
+
signals: null,
|
|
22
|
+
parts: null,
|
|
23
|
+
createdAt: suggestion.at,
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* An agent's close of an open task, waiting on the owner (`close_suggested_*`). Confirming closes it as suggested;
|
|
28
|
+
* rejecting clears the suggestion, as `tasks reject` does. Its id is `close:<task key>`; only the owner decides it.
|
|
29
|
+
*/
|
|
30
|
+
export const taskCloseKind = (store) => {
|
|
31
|
+
const taskOf = async (id) => {
|
|
32
|
+
const key = id.slice("close:".length);
|
|
33
|
+
const task = await store.tasks.details(key).catch(() => undefined);
|
|
34
|
+
const row = task && rowOf(task);
|
|
35
|
+
if (!task || !row)
|
|
36
|
+
throw new CliError("not_found", `no close waits on task ${key}`);
|
|
37
|
+
return { task, row };
|
|
38
|
+
};
|
|
39
|
+
return {
|
|
40
|
+
kind: TASK_CLOSE_KIND,
|
|
41
|
+
prefix: "close",
|
|
42
|
+
ownerOnly: true,
|
|
43
|
+
list: async (filter) => {
|
|
44
|
+
if ((filter.status ?? "proposed") !== "proposed" && filter.status !== "all")
|
|
45
|
+
return [];
|
|
46
|
+
const tasks = await store.tasks.find({
|
|
47
|
+
status: "open",
|
|
48
|
+
where: { sql: "t.close_suggested_as IS NOT NULL", params: [] },
|
|
49
|
+
limit: filter.offset + filter.limit,
|
|
50
|
+
});
|
|
51
|
+
return kept(tasks.flatMap((task) => rowOf(task) ?? []), { ...filter, limit: filter.offset + filter.limit });
|
|
52
|
+
},
|
|
53
|
+
get: async (id) => (await taskOf(id)).row,
|
|
54
|
+
decide: async (id, { confirm, by, reason }) => {
|
|
55
|
+
const { task } = await taskOf(id);
|
|
56
|
+
const suggestion = task.closeSuggestion;
|
|
57
|
+
if (!confirm || suggestion === null) {
|
|
58
|
+
await store.tasks.suggestClose(task.id, null, "owner");
|
|
59
|
+
return { id, kind: TASK_CLOSE_KIND, status: "rejected", made: [] };
|
|
60
|
+
}
|
|
61
|
+
// A project task has no row in the message task service; a message task closes there, keeping the
|
|
62
|
+
// suggestion's reason as `tasks close` does.
|
|
63
|
+
if ((await store.tasks.get(task.id)) === undefined)
|
|
64
|
+
await store.tasks.edit(task.id, { status: suggestion.as, ...(reason === undefined ? {} : { reason }) }, deciderAuthor(by));
|
|
65
|
+
else {
|
|
66
|
+
const why = reason ?? suggestion.reason ?? undefined;
|
|
67
|
+
await createTaskService({ store: store.tasks }).close(task.id, {
|
|
68
|
+
as: suggestion.as,
|
|
69
|
+
by: "owner",
|
|
70
|
+
...(why === undefined ? {} : { reason: why }),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return { id, kind: TASK_CLOSE_KIND, status: "confirmed", made: [task.ref] };
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
};
|
|
@@ -6,9 +6,11 @@ import { invoiceKind, paymentKind } from "./kinds/finance.js";
|
|
|
6
6
|
import { findingsKind } from "./kinds/findings.js";
|
|
7
7
|
import { keyDocumentKind } from "./kinds/key-document.js";
|
|
8
8
|
import { mailFindingsKind } from "./kinds/mail-findings.js";
|
|
9
|
+
import { meetingFindingsKind } from "./kinds/meeting-findings.js";
|
|
9
10
|
import { memoryKind } from "./kinds/memory.js";
|
|
10
11
|
import { relationshipKind } from "./kinds/relationship.js";
|
|
11
12
|
import { relationshipUpdateKind } from "./kinds/relationship-update.js";
|
|
13
|
+
import { taskCloseKind } from "./kinds/task-close.js";
|
|
12
14
|
const PREFIX = /^([a-z][a-z_]*):/;
|
|
13
15
|
const marked = (kind, row) => kind.ownerOnly ? { ...row, ownerOnly: true } : row;
|
|
14
16
|
const refusalOf = (id, error) => {
|
|
@@ -133,6 +135,8 @@ export const proposalRegistry = (store) => {
|
|
|
133
135
|
memoryKind(store),
|
|
134
136
|
findingsKind(store),
|
|
135
137
|
mailFindingsKind(store),
|
|
138
|
+
meetingFindingsKind(store),
|
|
139
|
+
taskCloseKind(store),
|
|
136
140
|
])
|
|
137
141
|
registry.register(kind);
|
|
138
142
|
registry.register(actionKind(store, registry.claimed));
|
|
@@ -79,6 +79,11 @@ export interface SearchAllFound {
|
|
|
79
79
|
by: "words" | "words and meaning";
|
|
80
80
|
meaningSkipped?: MeaningSkipped;
|
|
81
81
|
};
|
|
82
|
+
/** How the mail was searched, when it was; never by meaning with `exact`. */
|
|
83
|
+
mail?: {
|
|
84
|
+
by: "words" | "words and meaning";
|
|
85
|
+
meaningSkipped?: MeaningSkipped;
|
|
86
|
+
};
|
|
82
87
|
/** The messenger's server step for messages, as `search messages` reports it. */
|
|
83
88
|
server?: ServerSearched;
|
|
84
89
|
/** What the store held of the chats the messages were searched in; absent when messages were not searched. */
|