@wirecat/cli-core 0.394.0 → 0.396.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 +77 -0
- package/dist/cli/commands-command.js +6 -0
- package/dist/cli/config-command.js +5 -2
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/input-fields.d.ts +3 -0
- package/dist/cli/input-fields.js +27 -0
- package/dist/cli/messenger/account-command.js +8 -3
- package/dist/cli/messenger/account-sessions-command.js +7 -2
- package/dist/cli/messenger/attachments-command.js +16 -4
- package/dist/cli/messenger/conversation-answers.d.ts +5 -0
- package/dist/cli/messenger/conversation-examples.d.ts +2 -0
- package/dist/cli/messenger/conversation-examples.js +25 -0
- package/dist/cli/messenger/conversations-command.js +2 -0
- package/dist/cli/messenger/mcp-command.js +12 -3
- package/dist/cli/messenger/message-answers.d.ts +5 -0
- package/dist/cli/messenger/message-writes.d.ts +5 -0
- package/dist/cli/messenger/models-command.js +15 -6
- package/dist/cli/messenger/polls-command.js +20 -5
- package/dist/cli/messenger/records-command.js +10 -2
- package/dist/cli/messenger/search-answers.js +2 -0
- package/dist/cli/messenger/search-command.js +12 -0
- package/dist/cli/messenger/searches-command.d.ts +2 -0
- package/dist/cli/messenger/searches-command.js +2 -1
- package/dist/cli/messenger/server-command.js +19 -7
- package/dist/cli/messenger/store-maintenance-command.d.ts +1 -1
- package/dist/cli/messenger/tasks-command.js +18 -6
- package/dist/cli/program.js +5 -2
- package/dist/cli/runs/command.js +11 -4
- package/dist/cli/skill-command.js +6 -1
- package/dist/entities/evidence.d.ts +40 -0
- package/dist/entities/index.d.ts +72 -2
- package/dist/entities/index.js +2 -0
- package/dist/entities/message.d.ts +9 -0
- package/dist/entities/message.js +6 -0
- package/dist/entities/saved-search.d.ts +1 -1
- package/dist/entities/saved-search.js +1 -1
- package/dist/errors/reasons.d.ts +1 -0
- package/dist/errors/reasons.js +16 -0
- package/dist/meetings/cli/search.js +1 -1
- package/dist/meetings/hit-query.d.ts +10 -0
- package/dist/meetings/hit-query.js +85 -1
- package/dist/meetings/index.d.ts +1 -1
- package/dist/meetings/index.js +1 -1
- package/dist/meetings/mcp/index.d.ts +15 -15
- package/dist/search/lucene/registry.js +2 -2
- package/dist/services/index.d.ts +3 -2
- package/dist/services/index.js +2 -1
- package/dist/services/meeting-combined.d.ts +1 -1
- package/dist/services/meeting-combined.js +4 -3
- package/dist/services/meeting-hit-search.d.ts +1 -1
- package/dist/services/meeting-hit-search.js +4 -2
- package/dist/services/meeting-searches.d.ts +25 -0
- package/dist/services/meeting-searches.js +35 -0
- package/dist/services/messages-search.js +13 -2
- package/dist/services/messages.d.ts +5 -0
- package/dist/services/search-record.d.ts +1 -1
- package/dist/services/searches.d.ts +1 -0
- package/dist/services/searches.js +17 -4
- package/dist/store/search-index.d.ts +4 -1
- package/dist/store/search-index.js +10 -1
- package/dist/store/searches.d.ts +5 -4
- package/dist/store/searches.js +4 -4
- package/dist/store/sqlite/hit-places.d.ts +12 -0
- package/dist/store/sqlite/hit-places.js +76 -0
- package/dist/store/sqlite/lucene-fields.d.ts +8 -0
- package/dist/store/sqlite/lucene-fields.js +6 -4
- package/dist/store/sqlite/lucene.js +25 -70
- package/dist/store/sqlite/manifest.js +2 -0
- package/dist/store/sqlite/migrations.generated.js +19 -0
- package/dist/store/sqlite/note-index.d.ts +11 -1
- package/dist/store/sqlite/note-index.js +18 -6
- package/dist/store/sqlite/schema.d.ts +39 -0
- package/dist/store/sqlite/schema.js +4 -0
- package/dist/store/sqlite/searches.d.ts +4 -4
- package/dist/store/sqlite/searches.js +21 -6
- package/dist/store/sqlite/transcript-query.d.ts +40 -0
- package/dist/store/sqlite/transcript-query.js +108 -0
- package/dist/store/sqlite/words.d.ts +6 -0
- package/package.json +3 -3
|
@@ -35,7 +35,7 @@ export const createMeetingSearchCommand = (program) => {
|
|
|
35
35
|
const command = search
|
|
36
36
|
.command("meetings")
|
|
37
37
|
.description("find words in stored meetings, indexing new ones first")
|
|
38
|
-
.argument("<query>", 'words, OR, (groups), -word, "a phrase", "a b"~N (within N lines), exact:word, from:"Name" (who said the line); a word also matches its other forms and spellings speech recognition garbled')
|
|
38
|
+
.argument("<query>", 'words, OR, (groups), -word, "a phrase", "a b"~N (within N lines), exact:word, from:"Name" (who said the line); date>=30d, project:, tag:, topic: choose meetings; a word also matches its other forms and spellings speech recognition garbled')
|
|
39
39
|
.option("--meeting-id <id>", "only this stored meeting", integer)
|
|
40
40
|
.option("--identity-id <id>", "meetings with this participant identity; does not say who spoke a hit", integer)
|
|
41
41
|
.option("--person-id <id>", "meetings with a participant linked to this person", integer)
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
import { type QueryAst } from "@wirecat/cli-base/query";
|
|
2
|
+
import type { MeetingHitSearchRequest } from "./search-store.js";
|
|
2
3
|
/** A transcript line holds a sentence or two; further than this is a different part of the meeting. */
|
|
3
4
|
export declare const MOST_LINES_APART = 20;
|
|
4
5
|
/** The messages query language over meeting text: the same fields, a narrower set of operators. */
|
|
5
6
|
export declare const MEETING_HIT_QUERY: import("@wirecat/cli-base/query").QueryRegistry;
|
|
7
|
+
/**
|
|
8
|
+
* `budget project:RES date>=30d` → the words, and the meetings to look in as filters. A meeting field must be
|
|
9
|
+
* joined to the words with AND and given once, in the query or as an option. Text the language refuses is left as
|
|
10
|
+
* typed, for the caller's own error or for a search by meaning.
|
|
11
|
+
*/
|
|
12
|
+
export declare const liftMeetingFields: (query: string, request: MeetingHitSearchRequest, now?: number) => {
|
|
13
|
+
query: string;
|
|
14
|
+
request: MeetingHitSearchRequest;
|
|
15
|
+
};
|
|
6
16
|
/** What a person typed: the CLI, MCP and batch queries. */
|
|
7
17
|
export declare const parseMeetingQuery: (query: string) => QueryAst;
|
|
8
18
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CliError } from "@wirecat/cli-base";
|
|
2
|
+
import { dateRange, defineQuery, parseLucene, queryError, timezoneOf, validateQuery, } from "@wirecat/cli-base/query";
|
|
2
3
|
const context = {
|
|
3
4
|
command: "search meetings",
|
|
4
5
|
guide: "https://github.com/WireCatLabs/cli-core/blob/main/docs/search/query-language.md#встречи",
|
|
@@ -40,6 +41,89 @@ export const MEETING_HIT_QUERY = defineQuery({
|
|
|
40
41
|
},
|
|
41
42
|
],
|
|
42
43
|
});
|
|
44
|
+
/** Fields that choose meetings rather than lines: lifted out of the query into the search's own filters. */
|
|
45
|
+
const MEETING_FIELDS = defineQuery({
|
|
46
|
+
entity: "meeting_hit",
|
|
47
|
+
backend: "sql",
|
|
48
|
+
fields: [
|
|
49
|
+
...MEETING_HIT_QUERY.fields,
|
|
50
|
+
{
|
|
51
|
+
name: "date",
|
|
52
|
+
type: "time",
|
|
53
|
+
operators: ["term", "range"],
|
|
54
|
+
example: "date>=30d",
|
|
55
|
+
relation: "meetings that started then; the same as --since-time and --until-time",
|
|
56
|
+
},
|
|
57
|
+
...["project", "tag", "topic"].map((name) => ({
|
|
58
|
+
name,
|
|
59
|
+
type: "kw",
|
|
60
|
+
operators: words,
|
|
61
|
+
example: `${name}:"Example name"`,
|
|
62
|
+
relation: `meetings in this ${name}, by name or alias; the same as --${name}`,
|
|
63
|
+
})),
|
|
64
|
+
],
|
|
65
|
+
});
|
|
66
|
+
const LIFTED = new Set(["date", "project", "tag", "topic"]);
|
|
67
|
+
/**
|
|
68
|
+
* `budget project:RES date>=30d` → the words, and the meetings to look in as filters. A meeting field must be
|
|
69
|
+
* joined to the words with AND and given once, in the query or as an option. Text the language refuses is left as
|
|
70
|
+
* typed, for the caller's own error or for a search by meaning.
|
|
71
|
+
*/
|
|
72
|
+
export const liftMeetingFields = (query, request, now) => {
|
|
73
|
+
let ast;
|
|
74
|
+
try {
|
|
75
|
+
ast = validateQuery(parseLucene(query, { defaultField: "text", proximity: true, ...context }), MEETING_FIELDS, context);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
if (error instanceof CliError)
|
|
79
|
+
return { query, request };
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
const top = ast.root.kind === "boolean" ? ast.root.clauses : [{ occur: "must", node: ast.root }];
|
|
83
|
+
const lifted = top.filter((clause) => clause.node.kind === "predicate" && LIFTED.has(clause.node.field));
|
|
84
|
+
const misplaced = (node, nested) => node.kind === "predicate"
|
|
85
|
+
? nested && LIFTED.has(node.field)
|
|
86
|
+
? node
|
|
87
|
+
: undefined
|
|
88
|
+
: node.clauses.map((clause) => misplaced(clause.node, true)).find(Boolean);
|
|
89
|
+
const wrong = lifted.find((clause) => clause.occur !== "must")?.node ??
|
|
90
|
+
top.map((clause) => (clause.node.kind === "boolean" ? misplaced(clause.node, true) : undefined)).find(Boolean);
|
|
91
|
+
if (wrong)
|
|
92
|
+
queryError("query.unsupported-field", wrong.span, `${wrong.field}: chooses meetings; join it to the words with AND, outside OR, NOT and groups`, context);
|
|
93
|
+
if (!lifted.length)
|
|
94
|
+
return { query, request };
|
|
95
|
+
const next = { ...request };
|
|
96
|
+
const once = (name, flag) => {
|
|
97
|
+
if (next[name] !== undefined)
|
|
98
|
+
throw new CliError("validation_error", `give ${flag.replace(/^--/, "")} once: as ${flag} or in the query`);
|
|
99
|
+
};
|
|
100
|
+
for (const { node } of lifted) {
|
|
101
|
+
if (node.field === "date") {
|
|
102
|
+
once("since", "--since-time");
|
|
103
|
+
once("until", "--until-time");
|
|
104
|
+
const range = dateRange(node.value, node.operator === "range" ? (node.upper ?? "*") : node.value, node.lower_inclusive !== false, node.upper_inclusive !== false, timezoneOf(), node.span, now);
|
|
105
|
+
if (range.lower !== undefined)
|
|
106
|
+
next.since = range.lower + (range.lower_inclusive ? 0 : 1);
|
|
107
|
+
if (range.upper !== undefined)
|
|
108
|
+
next.until = range.upper - (range.upper_inclusive ? 0 : 1);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
const field = node.field;
|
|
112
|
+
once(field, `--${field}`);
|
|
113
|
+
next[field] = node.value;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
let rest = query;
|
|
117
|
+
for (const { node } of [...lifted].sort((a, b) => b.node.span.start - a.node.span.start))
|
|
118
|
+
rest = `${rest.slice(0, node.span.start)}${rest.slice(node.span.end)}`;
|
|
119
|
+
rest = rest
|
|
120
|
+
.replace(/^(?:\s*AND\b)+|(?:\bAND\s*)+$/g, "")
|
|
121
|
+
.replace(/\bAND(\s+AND\b)+/g, "AND")
|
|
122
|
+
.trim();
|
|
123
|
+
if (!rest)
|
|
124
|
+
throw new CliError("validation_error", "give words to find besides date:, project:, tag: and topic:");
|
|
125
|
+
return { query: rest, request: next };
|
|
126
|
+
};
|
|
43
127
|
/** What a person typed: the CLI, MCP and batch queries. */
|
|
44
128
|
export const parseMeetingQuery = (query) => validateQuery(parseLucene(query, { defaultField: "text", proximity: true, ...context }), MEETING_HIT_QUERY, context);
|
|
45
129
|
/**
|
package/dist/meetings/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export { linkEvent } from "./events.js";
|
|
|
6
6
|
export { exportMeeting, type MeetingExport, type MeetingExportFormat, type MeetingExportOptions, serializeVtt, } from "./export.js";
|
|
7
7
|
export { type FindingEvidence, type FindingOutcome, findingHash, MEETING_FINDINGS_KIND, type MeetingFactFinding, type MeetingFinding, type MeetingFindingsInput, type MeetingFindingsReceipt, type MeetingTaskFinding, meetingFindingFingerprint, validateMeetingFindings, } from "./findings.js";
|
|
8
8
|
export { type Finding, type FindingsInput, type FindingsReceipt, type FindingsRules, findingFingerprint, validateFindings, } from "./findings-rules.js";
|
|
9
|
-
export { MEETING_HIT_QUERY, meetingWordsQuery, parseMeetingQuery } from "./hit-query.js";
|
|
9
|
+
export { liftMeetingFields, MEETING_HIT_QUERY, meetingWordsQuery, parseMeetingQuery } from "./hit-query.js";
|
|
10
10
|
export { type ImportFile, importFiles } from "./import.js";
|
|
11
11
|
export { MEETING_ISSUES, MEETING_QUERY, type MeetingIssue, type MeetingItem, type MeetingOwner, meeting, meetingIssuesOf, meetingItem, } from "./item.js";
|
|
12
12
|
export { CHAT_MESSAGES_READ, type MeetingChatLine, type MeetingChatLinesInput, type MeetingChatLinesStore, } from "./links.js";
|
package/dist/meetings/index.js
CHANGED
|
@@ -4,7 +4,7 @@ export { linkEvent } from "./events.js";
|
|
|
4
4
|
export { exportMeeting, serializeVtt, } from "./export.js";
|
|
5
5
|
export { findingHash, MEETING_FINDINGS_KIND, meetingFindingFingerprint, validateMeetingFindings, } from "./findings.js";
|
|
6
6
|
export { findingFingerprint, validateFindings, } from "./findings-rules.js";
|
|
7
|
-
export { MEETING_HIT_QUERY, meetingWordsQuery, parseMeetingQuery } from "./hit-query.js";
|
|
7
|
+
export { liftMeetingFields, MEETING_HIT_QUERY, meetingWordsQuery, parseMeetingQuery } from "./hit-query.js";
|
|
8
8
|
export { importFiles } from "./import.js";
|
|
9
9
|
export { MEETING_ISSUES, MEETING_QUERY, meeting, meetingIssuesOf, meetingItem, } from "./item.js";
|
|
10
10
|
export { CHAT_MESSAGES_READ, } from "./links.js";
|
|
@@ -38,7 +38,6 @@ export declare const meetingTools: ({
|
|
|
38
38
|
max_read_bytes?: undefined;
|
|
39
39
|
cursor?: undefined;
|
|
40
40
|
} | {
|
|
41
|
-
limit?: undefined;
|
|
42
41
|
event_id?: undefined;
|
|
43
42
|
series_id?: undefined;
|
|
44
43
|
page?: undefined;
|
|
@@ -54,11 +53,11 @@ export declare const meetingTools: ({
|
|
|
54
53
|
format?: undefined;
|
|
55
54
|
transcript_id?: undefined;
|
|
56
55
|
query?: undefined;
|
|
56
|
+
limit?: undefined;
|
|
57
57
|
scan_limit?: undefined;
|
|
58
58
|
max_read_bytes?: undefined;
|
|
59
59
|
cursor?: undefined;
|
|
60
60
|
} | {
|
|
61
|
-
limit?: undefined;
|
|
62
61
|
event_id?: undefined;
|
|
63
62
|
series_id?: undefined;
|
|
64
63
|
page?: undefined;
|
|
@@ -76,11 +75,11 @@ export declare const meetingTools: ({
|
|
|
76
75
|
format?: undefined;
|
|
77
76
|
transcript_id?: undefined;
|
|
78
77
|
query?: undefined;
|
|
78
|
+
limit?: undefined;
|
|
79
79
|
scan_limit?: undefined;
|
|
80
80
|
max_read_bytes?: undefined;
|
|
81
81
|
cursor?: undefined;
|
|
82
82
|
} | {
|
|
83
|
-
limit?: undefined;
|
|
84
83
|
event_id?: undefined;
|
|
85
84
|
series_id?: undefined;
|
|
86
85
|
page?: undefined;
|
|
@@ -102,6 +101,7 @@ export declare const meetingTools: ({
|
|
|
102
101
|
minimum: number;
|
|
103
102
|
};
|
|
104
103
|
query?: undefined;
|
|
104
|
+
limit?: undefined;
|
|
105
105
|
scan_limit?: undefined;
|
|
106
106
|
max_read_bytes?: undefined;
|
|
107
107
|
cursor?: undefined;
|
|
@@ -188,7 +188,6 @@ export declare const meetingTools: ({
|
|
|
188
188
|
max_read_bytes?: undefined;
|
|
189
189
|
cursor?: undefined;
|
|
190
190
|
} | {
|
|
191
|
-
limit?: undefined;
|
|
192
191
|
event_id?: undefined;
|
|
193
192
|
series_id?: undefined;
|
|
194
193
|
page?: undefined;
|
|
@@ -204,11 +203,11 @@ export declare const meetingTools: ({
|
|
|
204
203
|
format?: undefined;
|
|
205
204
|
transcript_id?: undefined;
|
|
206
205
|
query?: undefined;
|
|
206
|
+
limit?: undefined;
|
|
207
207
|
scan_limit?: undefined;
|
|
208
208
|
max_read_bytes?: undefined;
|
|
209
209
|
cursor?: undefined;
|
|
210
210
|
} | {
|
|
211
|
-
limit?: undefined;
|
|
212
211
|
event_id?: undefined;
|
|
213
212
|
series_id?: undefined;
|
|
214
213
|
page?: undefined;
|
|
@@ -226,11 +225,11 @@ export declare const meetingTools: ({
|
|
|
226
225
|
format?: undefined;
|
|
227
226
|
transcript_id?: undefined;
|
|
228
227
|
query?: undefined;
|
|
228
|
+
limit?: undefined;
|
|
229
229
|
scan_limit?: undefined;
|
|
230
230
|
max_read_bytes?: undefined;
|
|
231
231
|
cursor?: undefined;
|
|
232
232
|
} | {
|
|
233
|
-
limit?: undefined;
|
|
234
233
|
event_id?: undefined;
|
|
235
234
|
series_id?: undefined;
|
|
236
235
|
page?: undefined;
|
|
@@ -252,6 +251,7 @@ export declare const meetingTools: ({
|
|
|
252
251
|
minimum: number;
|
|
253
252
|
};
|
|
254
253
|
query?: undefined;
|
|
254
|
+
limit?: undefined;
|
|
255
255
|
scan_limit?: undefined;
|
|
256
256
|
max_read_bytes?: undefined;
|
|
257
257
|
cursor?: undefined;
|
|
@@ -338,7 +338,6 @@ export declare const meetingTools: ({
|
|
|
338
338
|
max_read_bytes?: undefined;
|
|
339
339
|
cursor?: undefined;
|
|
340
340
|
} | {
|
|
341
|
-
limit?: undefined;
|
|
342
341
|
event_id?: undefined;
|
|
343
342
|
series_id?: undefined;
|
|
344
343
|
page?: undefined;
|
|
@@ -354,11 +353,11 @@ export declare const meetingTools: ({
|
|
|
354
353
|
format?: undefined;
|
|
355
354
|
transcript_id?: undefined;
|
|
356
355
|
query?: undefined;
|
|
356
|
+
limit?: undefined;
|
|
357
357
|
scan_limit?: undefined;
|
|
358
358
|
max_read_bytes?: undefined;
|
|
359
359
|
cursor?: undefined;
|
|
360
360
|
} | {
|
|
361
|
-
limit?: undefined;
|
|
362
361
|
event_id?: undefined;
|
|
363
362
|
series_id?: undefined;
|
|
364
363
|
page?: undefined;
|
|
@@ -376,11 +375,11 @@ export declare const meetingTools: ({
|
|
|
376
375
|
format?: undefined;
|
|
377
376
|
transcript_id?: undefined;
|
|
378
377
|
query?: undefined;
|
|
378
|
+
limit?: undefined;
|
|
379
379
|
scan_limit?: undefined;
|
|
380
380
|
max_read_bytes?: undefined;
|
|
381
381
|
cursor?: undefined;
|
|
382
382
|
} | {
|
|
383
|
-
limit?: undefined;
|
|
384
383
|
event_id?: undefined;
|
|
385
384
|
series_id?: undefined;
|
|
386
385
|
page?: undefined;
|
|
@@ -402,6 +401,7 @@ export declare const meetingTools: ({
|
|
|
402
401
|
minimum: number;
|
|
403
402
|
};
|
|
404
403
|
query?: undefined;
|
|
404
|
+
limit?: undefined;
|
|
405
405
|
scan_limit?: undefined;
|
|
406
406
|
max_read_bytes?: undefined;
|
|
407
407
|
cursor?: undefined;
|
|
@@ -488,7 +488,6 @@ export declare const meetingTools: ({
|
|
|
488
488
|
max_read_bytes?: undefined;
|
|
489
489
|
cursor?: undefined;
|
|
490
490
|
} | {
|
|
491
|
-
limit?: undefined;
|
|
492
491
|
event_id?: undefined;
|
|
493
492
|
series_id?: undefined;
|
|
494
493
|
page?: undefined;
|
|
@@ -504,11 +503,11 @@ export declare const meetingTools: ({
|
|
|
504
503
|
format?: undefined;
|
|
505
504
|
transcript_id?: undefined;
|
|
506
505
|
query?: undefined;
|
|
506
|
+
limit?: undefined;
|
|
507
507
|
scan_limit?: undefined;
|
|
508
508
|
max_read_bytes?: undefined;
|
|
509
509
|
cursor?: undefined;
|
|
510
510
|
} | {
|
|
511
|
-
limit?: undefined;
|
|
512
511
|
event_id?: undefined;
|
|
513
512
|
series_id?: undefined;
|
|
514
513
|
page?: undefined;
|
|
@@ -526,11 +525,11 @@ export declare const meetingTools: ({
|
|
|
526
525
|
format?: undefined;
|
|
527
526
|
transcript_id?: undefined;
|
|
528
527
|
query?: undefined;
|
|
528
|
+
limit?: undefined;
|
|
529
529
|
scan_limit?: undefined;
|
|
530
530
|
max_read_bytes?: undefined;
|
|
531
531
|
cursor?: undefined;
|
|
532
532
|
} | {
|
|
533
|
-
limit?: undefined;
|
|
534
533
|
event_id?: undefined;
|
|
535
534
|
series_id?: undefined;
|
|
536
535
|
page?: undefined;
|
|
@@ -552,6 +551,7 @@ export declare const meetingTools: ({
|
|
|
552
551
|
minimum: number;
|
|
553
552
|
};
|
|
554
553
|
query?: undefined;
|
|
554
|
+
limit?: undefined;
|
|
555
555
|
scan_limit?: undefined;
|
|
556
556
|
max_read_bytes?: undefined;
|
|
557
557
|
cursor?: undefined;
|
|
@@ -638,7 +638,6 @@ export declare const meetingTools: ({
|
|
|
638
638
|
max_read_bytes?: undefined;
|
|
639
639
|
cursor?: undefined;
|
|
640
640
|
} | {
|
|
641
|
-
limit?: undefined;
|
|
642
641
|
event_id?: undefined;
|
|
643
642
|
series_id?: undefined;
|
|
644
643
|
page?: undefined;
|
|
@@ -654,11 +653,11 @@ export declare const meetingTools: ({
|
|
|
654
653
|
format?: undefined;
|
|
655
654
|
transcript_id?: undefined;
|
|
656
655
|
query?: undefined;
|
|
656
|
+
limit?: undefined;
|
|
657
657
|
scan_limit?: undefined;
|
|
658
658
|
max_read_bytes?: undefined;
|
|
659
659
|
cursor?: undefined;
|
|
660
660
|
} | {
|
|
661
|
-
limit?: undefined;
|
|
662
661
|
event_id?: undefined;
|
|
663
662
|
series_id?: undefined;
|
|
664
663
|
page?: undefined;
|
|
@@ -676,11 +675,11 @@ export declare const meetingTools: ({
|
|
|
676
675
|
format?: undefined;
|
|
677
676
|
transcript_id?: undefined;
|
|
678
677
|
query?: undefined;
|
|
678
|
+
limit?: undefined;
|
|
679
679
|
scan_limit?: undefined;
|
|
680
680
|
max_read_bytes?: undefined;
|
|
681
681
|
cursor?: undefined;
|
|
682
682
|
} | {
|
|
683
|
-
limit?: undefined;
|
|
684
683
|
event_id?: undefined;
|
|
685
684
|
series_id?: undefined;
|
|
686
685
|
page?: undefined;
|
|
@@ -702,6 +701,7 @@ export declare const meetingTools: ({
|
|
|
702
701
|
minimum: number;
|
|
703
702
|
};
|
|
704
703
|
query?: undefined;
|
|
704
|
+
limit?: undefined;
|
|
705
705
|
scan_limit?: undefined;
|
|
706
706
|
max_read_bytes?: undefined;
|
|
707
707
|
cursor?: undefined;
|
|
@@ -294,11 +294,11 @@ export const QUERY_FIELDS = [
|
|
|
294
294
|
{
|
|
295
295
|
name: "transcript",
|
|
296
296
|
example: "transcript:invoice",
|
|
297
|
-
index: "attachment_words
|
|
297
|
+
index: "attachment_words ∪ transcript_stems; garbled spellings from transcript_words_vocab",
|
|
298
298
|
aliases: [],
|
|
299
299
|
type: "txt",
|
|
300
300
|
operators: terms,
|
|
301
|
-
normalization: "
|
|
301
|
+
normalization: "any form of the word (Snowball), widened to spellings speech recognition garbles; what a voice message said",
|
|
302
302
|
support: "A2",
|
|
303
303
|
validate: filled,
|
|
304
304
|
},
|
package/dist/services/index.d.ts
CHANGED
|
@@ -69,6 +69,7 @@ export { type MeetingLink, type MeetingLinksRequest, readMeetingLinks } from "./
|
|
|
69
69
|
export { createMeetingProjectCandidateReader } from "./meeting-project-candidates.js";
|
|
70
70
|
export { readMeetingProjectDiscovery } from "./meeting-project-discovery.js";
|
|
71
71
|
export { type MeetingReviewRequest, readMeetingReview } from "./meeting-review.js";
|
|
72
|
+
export { type MeetingSearchesService, type MeetingSearchParams, meetingSearchesService, type SavedMeetingSearch, } from "./meeting-searches.js";
|
|
72
73
|
export { type MeetingStatsRequest, readMeetingStats } from "./meeting-stats.js";
|
|
73
74
|
export { chatIdIn, storedChatId } from "./message-resolution.js";
|
|
74
75
|
export type { AroundWindow, FoundMessage, ListWindow, MessagesService, MessageTarget, Pinned, Reacted, SearchFound, SearchQuery, SendRequest, Starred, } from "./messages.js";
|
|
@@ -88,8 +89,8 @@ export type { RankedRow, RankingFound, RankingQuery, RankingsService } from "./r
|
|
|
88
89
|
export { rankingsService } from "./rankings.js";
|
|
89
90
|
export type { SearchParams } from "./search-record.js";
|
|
90
91
|
export { searchRecordOf } from "./search-record.js";
|
|
91
|
-
export type { ResolvedSearch, SearchesService } from "./searches.js";
|
|
92
|
-
export { searchesService } from "./searches.js";
|
|
92
|
+
export type { ResolvedSearch, SavedSearchItem, SearchesService } from "./searches.js";
|
|
93
|
+
export { savedSearchItem, searchesService } from "./searches.js";
|
|
93
94
|
export type { TagsAdded, TagsRemoved, TagsService, TagTargetInput, TagTargetView } from "./tags.js";
|
|
94
95
|
export { tagsService } from "./tags.js";
|
|
95
96
|
export type { ClosedTask, TaskCard, TaskItem, TaskList, TaskListRequest, TasksService, TaskView, } from "./tasks.js";
|
package/dist/services/index.js
CHANGED
|
@@ -50,6 +50,7 @@ export { readMeetingLinks } from "./meeting-links.js";
|
|
|
50
50
|
export { createMeetingProjectCandidateReader } from "./meeting-project-candidates.js";
|
|
51
51
|
export { readMeetingProjectDiscovery } from "./meeting-project-discovery.js";
|
|
52
52
|
export { readMeetingReview } from "./meeting-review.js";
|
|
53
|
+
export { meetingSearchesService, } from "./meeting-searches.js";
|
|
53
54
|
export { readMeetingStats } from "./meeting-stats.js";
|
|
54
55
|
export { chatIdIn, storedChatId } from "./message-resolution.js";
|
|
55
56
|
export { DELETE_AT_ONCE, messagesService } from "./messages.js";
|
|
@@ -61,7 +62,7 @@ export { PREPARE_BYTES, personPreparation } from "./person-preparation.js";
|
|
|
61
62
|
export { personTimeline, SCOPES, TIMELINE_ITEMS, TIMELINE_MAX } from "./person-timeline.js";
|
|
62
63
|
export { rankingsService } from "./rankings.js";
|
|
63
64
|
export { searchRecordOf } from "./search-record.js";
|
|
64
|
-
export { searchesService } from "./searches.js";
|
|
65
|
+
export { savedSearchItem, searchesService } from "./searches.js";
|
|
65
66
|
export { tagsService } from "./tags.js";
|
|
66
67
|
export { closedStateOf, taskStateOf, tasksService, taskTypeOf, taskView } from "./tasks.js";
|
|
67
68
|
export { topicsService } from "./topics.js";
|
|
@@ -38,7 +38,7 @@ export interface MeetingCombinedHit {
|
|
|
38
38
|
*/
|
|
39
39
|
export declare const searchMeetingsCombined: (store: Pick<MessageStore, "meetings" | "meetingVectors"> & {
|
|
40
40
|
knowledge?: MessageStore["knowledge"];
|
|
41
|
-
}, embedder: MeetingEmbedder,
|
|
41
|
+
}, embedder: MeetingEmbedder, typed: string, request: MeetingCombinedRequest) => Promise<{
|
|
42
42
|
items: {
|
|
43
43
|
/** A transcript line to cite: the best word hit in the passage, else the passage's first line. */
|
|
44
44
|
ref: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { CliError } from "@wirecat/cli-base";
|
|
3
3
|
import { meaningFloor } from "../embeddings/models.js";
|
|
4
|
-
import { formatMeetingReference, parseMeetingReference, } from "../meetings/index.js";
|
|
4
|
+
import { formatMeetingReference, liftMeetingFields, parseMeetingReference, } from "../meetings/index.js";
|
|
5
5
|
import { questionTerms } from "../search/question-plan.js";
|
|
6
6
|
import { refreshMeetingIndex, resolveMeetingSelection } from "./meeting-hit-search.js";
|
|
7
7
|
import { validateVector } from "./meeting-semantic.js";
|
|
@@ -27,8 +27,9 @@ const cursorOf = (value, key) => {
|
|
|
27
27
|
* («что решили по цене?») searches its content words; other text is the meeting query language. A word hit inside
|
|
28
28
|
* a passage the meaning search found joins it, so one discussion found both ways ranks first.
|
|
29
29
|
*/
|
|
30
|
-
export const searchMeetingsCombined = async (store, embedder,
|
|
31
|
-
const { maxChunks, maxRows, maxTextBytes, ...
|
|
30
|
+
export const searchMeetingsCombined = async (store, embedder, typed, request) => {
|
|
31
|
+
const { maxChunks, maxRows, maxTextBytes, ...chosen } = request;
|
|
32
|
+
const { query, request: words } = liftMeetingFields(typed, chosen);
|
|
32
33
|
const { input, selection } = await resolveMeetingSelection(store, words);
|
|
33
34
|
const { after, limit = 20, maxReadBytes, ...filters } = input;
|
|
34
35
|
const terms = questionTerms(query);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type MeetingHitSearchRequest } from "../meetings/index.js";
|
|
2
2
|
import type { VocabularyKind } from "../store/index.js";
|
|
3
3
|
import type { MessageStore } from "../store/store.js";
|
|
4
4
|
export type { MeetingHitSearchRequest } from "../meetings/index.js";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
|
+
import { liftMeetingFields } from "../meetings/index.js";
|
|
2
3
|
/** Each batch commits on its own, so a search cut short by its deadline keeps the progress it made. */
|
|
3
4
|
export const refreshMeetingIndex = async (meetings, accountId, signal) => {
|
|
4
5
|
let before = Number.POSITIVE_INFINITY;
|
|
@@ -54,10 +55,11 @@ export const resolveMeetingSelection = async (store, request) => {
|
|
|
54
55
|
return { input, selection };
|
|
55
56
|
};
|
|
56
57
|
export const searchMeetingHits = async (store, query, request, options = {}) => {
|
|
57
|
-
const
|
|
58
|
+
const lifted = liftMeetingFields(query, request);
|
|
59
|
+
const { input, selection } = await resolveMeetingSelection(store, lifted.request);
|
|
58
60
|
if (options.refreshIndex !== false)
|
|
59
61
|
await refreshMeetingIndex(store.meetings, input.accountId, input.signal);
|
|
60
|
-
const result = await store.meetings.searchHits(query, input);
|
|
62
|
+
const result = await store.meetings.searchHits(lifted.query, input);
|
|
61
63
|
input.signal?.throwIfAborted();
|
|
62
64
|
return { ...result, selection };
|
|
63
65
|
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { SearchesStore, StoredSearch } from "../store/store.js";
|
|
2
|
+
/** A meeting search as `search meetings` takes it: the query and the options typed with it, already checked. */
|
|
3
|
+
export interface MeetingSearchParams {
|
|
4
|
+
query: string;
|
|
5
|
+
options: Readonly<Record<string, string | number | boolean>>;
|
|
6
|
+
}
|
|
7
|
+
export interface SavedMeetingSearch extends MeetingSearchParams {
|
|
8
|
+
id: string;
|
|
9
|
+
name: string | null;
|
|
10
|
+
}
|
|
11
|
+
export interface MeetingSearchesService {
|
|
12
|
+
create(name: string, params: MeetingSearchParams, options?: {
|
|
13
|
+
replace?: boolean;
|
|
14
|
+
}): Promise<StoredSearch>;
|
|
15
|
+
list(): Promise<StoredSearch[]>;
|
|
16
|
+
show(reference: string): Promise<StoredSearch>;
|
|
17
|
+
delete(reference: string): Promise<StoredSearch>;
|
|
18
|
+
/** `--saved`: the stored query and options, counted as one more run. */
|
|
19
|
+
run(reference: string): Promise<SavedMeetingSearch>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Saved meeting searches, in the table every CLI on the store shares: only rows of their own kind are listed, and a
|
|
23
|
+
* saved message search is refused by name rather than run as a meeting one.
|
|
24
|
+
*/
|
|
25
|
+
export declare const meetingSearchesService: (searches: () => Promise<SearchesStore>) => MeetingSearchesService;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { CliError, singleLine } from "@wirecat/cli-base";
|
|
2
|
+
import { searchRecordOf } from "./search-record.js";
|
|
3
|
+
import { nameOf } from "./searches.js";
|
|
4
|
+
const KIND = ["meetings-search"];
|
|
5
|
+
/**
|
|
6
|
+
* Saved meeting searches, in the table every CLI on the store shares: only rows of their own kind are listed, and a
|
|
7
|
+
* saved message search is refused by name rather than run as a meeting one.
|
|
8
|
+
*/
|
|
9
|
+
export const meetingSearchesService = (searches) => {
|
|
10
|
+
const found = async (reference) => {
|
|
11
|
+
const search = await (await searches()).storedSearch(reference.trim().toLowerCase());
|
|
12
|
+
if (!search)
|
|
13
|
+
throw new CliError("not_found", `no saved search "${singleLine(reference)}"`);
|
|
14
|
+
if (search.command !== "meetings-search")
|
|
15
|
+
throw new CliError("validation_error", `"${singleLine(reference)}" is a saved search of messages, not meetings`, {
|
|
16
|
+
reason: "search.kind",
|
|
17
|
+
command: search.command,
|
|
18
|
+
});
|
|
19
|
+
return search;
|
|
20
|
+
};
|
|
21
|
+
const record = ({ query, options }) => searchRecordOf("meetings-search", { ...options, text: query });
|
|
22
|
+
return {
|
|
23
|
+
create: async (name, params, { replace = false } = {}) => (await searches()).saveSearch(nameOf(name), record(params), { replace }),
|
|
24
|
+
list: async () => (await searches()).savedSearches(KIND),
|
|
25
|
+
show: found,
|
|
26
|
+
delete: async (reference) => (await searches()).deleteSearch((await found(reference)).id),
|
|
27
|
+
run: async (reference) => {
|
|
28
|
+
const search = await found(reference);
|
|
29
|
+
const { text, ...options } = search.params;
|
|
30
|
+
const query = { query: String(text ?? ""), options };
|
|
31
|
+
await (await searches()).recordSearch(record(query), { saved: search.id });
|
|
32
|
+
return { id: search.id, name: search.name, ...query };
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
};
|
|
@@ -331,7 +331,9 @@ export const searchLucene = async (store, account, request, messenger = {}) => {
|
|
|
331
331
|
// Coverage counts messenger chats fetched from a server; mail is imported whole, so for mail it says nothing.
|
|
332
332
|
const { completeness, coverage } = request.kind === "mail" ? { completeness: [], coverage: undefined } : await coverageOf(store, prepared, messenger);
|
|
333
333
|
const items = await Promise.all(found.items.map(async (item) => {
|
|
334
|
-
|
|
334
|
+
// A garbled spelling is not among the typed words, so the excerpt looks for it too.
|
|
335
|
+
const garbled = item.corrections?.flatMap(({ to }) => to) ?? [];
|
|
336
|
+
const hit = withExcerpt(item, [request.text ?? "", ...garbled].join(" "));
|
|
335
337
|
if (!request.context)
|
|
336
338
|
return hit;
|
|
337
339
|
const locator = parseLocator(hit.locator);
|
|
@@ -347,7 +349,7 @@ export const searchLucene = async (store, account, request, messenger = {}) => {
|
|
|
347
349
|
return {
|
|
348
350
|
...found,
|
|
349
351
|
items,
|
|
350
|
-
corrections:
|
|
352
|
+
corrections: correctionsOf(found.items),
|
|
351
353
|
wordsReady: prepared.wordsReady,
|
|
352
354
|
stemsReady: prepared.stemsReady,
|
|
353
355
|
completeness,
|
|
@@ -359,6 +361,15 @@ export const searchLucene = async (store, account, request, messenger = {}) => {
|
|
|
359
361
|
: {}),
|
|
360
362
|
};
|
|
361
363
|
};
|
|
364
|
+
/** The garbled spellings voice-note hits were found through, one entry per query word. */
|
|
365
|
+
const correctionsOf = (items) => {
|
|
366
|
+
const found = new Map();
|
|
367
|
+
for (const { corrections = [] } of items)
|
|
368
|
+
for (const { from, to } of corrections)
|
|
369
|
+
for (const word of to)
|
|
370
|
+
found.set(from, (found.get(from) ?? new Set()).add(word));
|
|
371
|
+
return [...found].map(([from, to]) => ({ from, to: [...to] }));
|
|
372
|
+
};
|
|
362
373
|
const QUERY_PAGE_LIMIT = 1000;
|
|
363
374
|
const oldestFetch = (chats) => chats.length === 0 || chats.some(({ fetchedAt }) => fetchedAt === null)
|
|
364
375
|
? null
|
|
@@ -81,6 +81,11 @@ export type FoundMessage = StoredHit & {
|
|
|
81
81
|
parent?: string;
|
|
82
82
|
};
|
|
83
83
|
match?: "words" | "beginnings" | "corrected";
|
|
84
|
+
/** The garbled spellings a `corrected` hit's transcript holds, per query word. */
|
|
85
|
+
corrections?: {
|
|
86
|
+
from: string;
|
|
87
|
+
to: string[];
|
|
88
|
+
}[];
|
|
84
89
|
/** Where the words were: the text, a voice note's transcript, a file's name or a file's text. */
|
|
85
90
|
matchedIn?: MatchPlace;
|
|
86
91
|
score?: number | null;
|
|
@@ -21,4 +21,4 @@ export interface SearchParams extends RankingInput {
|
|
|
21
21
|
by?: StatsGrouping;
|
|
22
22
|
}
|
|
23
23
|
/** One run or one saved search as the store keeps it; the same parameters always give the same text. */
|
|
24
|
-
export declare const searchRecordOf: (command: SearchCommand, params: SearchParams) => SearchRecord;
|
|
24
|
+
export declare const searchRecordOf: (command: SearchCommand, params: SearchParams | Readonly<Record<string, unknown>>) => SearchRecord;
|
|
@@ -17,8 +17,16 @@ export const savedSearchItem = (one) => ({
|
|
|
17
17
|
last_run_at: one.lastRunAt,
|
|
18
18
|
runs: one.runs,
|
|
19
19
|
});
|
|
20
|
+
/** What the messenger commands run; the table is shared, and another CLI's rows are not theirs to list or run. */
|
|
21
|
+
const MESSAGE_SEARCH_COMMANDS = [
|
|
22
|
+
"search",
|
|
23
|
+
"stats",
|
|
24
|
+
"message-top",
|
|
25
|
+
"author-top",
|
|
26
|
+
"admin-statistics",
|
|
27
|
+
];
|
|
20
28
|
const NAME = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
21
|
-
const nameOf = (value) => {
|
|
29
|
+
export const nameOf = (value) => {
|
|
22
30
|
const name = value.trim().toLowerCase();
|
|
23
31
|
if (!NAME.test(name) || /^\d+$/.test(name))
|
|
24
32
|
throw new CliError("validation_error", `"${singleLine(value)}" cannot name a saved search — use up to 64 letters a–z, digits and hyphens, not only digits`, { reason: "name.invalid" });
|
|
@@ -63,6 +71,11 @@ export const searchesService = (deps) => {
|
|
|
63
71
|
const search = await store.searches.storedSearch(reference.trim().toLowerCase());
|
|
64
72
|
if (!search)
|
|
65
73
|
throw new CliError("not_found", `no saved search or run "${singleLine(reference)}"`);
|
|
74
|
+
if (!MESSAGE_SEARCH_COMMANDS.includes(search.command))
|
|
75
|
+
throw new CliError("validation_error", `"${singleLine(reference)}" is a saved meeting search, not one of messages`, {
|
|
76
|
+
reason: "search.kind",
|
|
77
|
+
command: search.command,
|
|
78
|
+
});
|
|
66
79
|
return search;
|
|
67
80
|
};
|
|
68
81
|
return {
|
|
@@ -87,13 +100,13 @@ export const searchesService = (deps) => {
|
|
|
87
100
|
});
|
|
88
101
|
}),
|
|
89
102
|
show: (reference) => inStore((store) => found(store, reference)),
|
|
90
|
-
list: () => inStore((store) => store.searches.savedSearches()),
|
|
103
|
+
list: () => inStore((store) => store.searches.savedSearches(MESSAGE_SEARCH_COMMANDS)),
|
|
91
104
|
history: (limit, offset = 0) => inStore(async (store) => {
|
|
92
|
-
const rows = await store.searches.searchHistory(limit, offset);
|
|
105
|
+
const rows = await store.searches.searchHistory(limit, offset, MESSAGE_SEARCH_COMMANDS);
|
|
93
106
|
return { items: rows.slice(0, limit), has_more: rows.length > limit };
|
|
94
107
|
}),
|
|
95
108
|
delete: (reference) => inStore(async (store) => store.searches.deleteSearch((await found(store, reference)).id)),
|
|
96
|
-
clear: () => inStore(async (store) => ({ cleared: await store.searches.clearSearchHistory() })),
|
|
109
|
+
clear: () => inStore(async (store) => ({ cleared: await store.searches.clearSearchHistory(MESSAGE_SEARCH_COMMANDS) })),
|
|
97
110
|
resolve: (reference, { text: words, ...typed }) => inStore(async (store) => {
|
|
98
111
|
const search = await found(store, reference);
|
|
99
112
|
const stored = search.params;
|
|
@@ -24,7 +24,10 @@ export interface SearchIndexStore {
|
|
|
24
24
|
}): Promise<SearchIndexFill>;
|
|
25
25
|
/** How far the stems are built, and by which stemmer choices; `undefined` on a file before they existed. */
|
|
26
26
|
stemsState(): Promise<StemsState | undefined>;
|
|
27
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Builds the stems towards "ready" in short batches until done or `until` says stop; never rebuilds. Then the
|
|
29
|
+
* voice-note transcripts written since, which are few, so they keep up with any stemmer choice.
|
|
30
|
+
*/
|
|
28
31
|
fillStems(options?: {
|
|
29
32
|
until?: () => boolean;
|
|
30
33
|
}): Promise<StemsFill>;
|