@wirecat/cli-core 0.494.0 → 0.496.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 CHANGED
@@ -7,6 +7,51 @@ shape of this file.
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ ## 0.496.0 — 31.10.2026
11
+
12
+ ### Changed — may break callers
13
+
14
+ - **A discovery follow-up carries an `excerpt` of up to 240 characters, as the hit itself, instead of its whole
15
+ `text`.** On a real archive one follow-up (a pasted long write-up) added 7 KB to a five-hit answer, a sixth of it.
16
+ `items[].follow_ups[].text` is now `items[].follow_ups[].excerpt`; `messages context <ref>` shows the whole message.
17
+ The terminal output is unchanged.
18
+
19
+ ### Fixed
20
+
21
+ - **Discovery search keeps the colons of a quoted timestamp in a date filter.** 0.492.0 turned a colon between digits
22
+ into a space so that "22:00" in a question would not fail, but it also did so inside quotes, and
23
+ `date:["2026-08-21T21:30:00Z" TO "…"]` failed with `query.invalid-date` under `discover`. Quoted text is now left as is.
24
+ - **`search notes`, the notes of `search all` and drive's `search` keep the query's fields for hits found by
25
+ meaning.** `date:today` or `tag:work` without `--exact` returned about 20 notes that matched neither: the meaning
26
+ half embedded the whole query, fields included, and checked only `--filter`. Now only the query's words and phrases
27
+ are searched by meaning, and every field and every excluded word (`-draft`) holds for every hit; a field joined to
28
+ the words by `OR` narrows nothing. A query of fields alone is not searched by meaning and lists the matching notes,
29
+ newest first. `--filter` also checks every meaning candidate: before, it kept only the newest 500 notes it matched,
30
+ so an older note found by meaning was dropped.
31
+ - **A Drive document's `date:` and its shown `updated_at` are one time.** `date:` read when the store's copy changed,
32
+ while the hit showed the document's edit time on Drive, so of 20 hits for `date>=30d` 16 showed an older date. Both
33
+ are now the edit time on Drive when the store has it, and the store's copy otherwise, as `context` evidence already
34
+ reads it. `date:` on a Drive document therefore matches by its edit time, and the time is always written as
35
+ `2026-01-01T00:00:00.000Z`, as every other time is, where a front-matter time was shown as written.
36
+
37
+ ### Added
38
+
39
+ - **`NoteQuery.refs` checks a list of notes against a query, and `NoteHit.at` is the time `date:` read.**
40
+
41
+ ## 0.495.0 — 31.10.2026
42
+
43
+ ### Changed — may break callers
44
+
45
+ - **`gatherProjectEvidence` says what the store holds of each linked chat, and which ones the run never reached.** A new
46
+ optional port `coverage(account, chatIds)` (pass `chatCoverage` over the store) fills each `linkedChats` row with
47
+ `title` (the chat row's, also for a chat with no stored message), `completeness` and `held`; without it they are `null` and `completeness` stays `"unknown"`. The top-level
48
+ `completeness` is `partial` when any linked chat is `partial`, `unknown` when none is but one is `unknown` (a chat the
49
+ store has no row for counts), and `complete` only when every linked chat in scope is. `complete` keeps its meaning,
50
+ "the planned reads finished"; a host's `meta.complete` is `complete && completeness === "complete"`. A linked chat
51
+ none of whose reads has started is `state: "pending"` and has no `items` or `capped`, where it said `in-scope` with
52
+ `items: 0, capped: false`. `ChatCoverage.title` now comes from the chat row when the chat has no message, through the
53
+ new `store.chats.chatTitle`.
54
+
10
55
  ## 0.494.0 — 31.10.2026
11
56
 
12
57
  ### Security
@@ -80,7 +80,7 @@ export declare const conversationShown: (account: AccountKey, { summary, message
80
80
  chat_title: string | null;
81
81
  timestamp: string;
82
82
  sender_name: string | null;
83
- text: string;
83
+ excerpt: string;
84
84
  }[] | undefined;
85
85
  outgoing: boolean | null;
86
86
  attachments: {
@@ -57,7 +57,7 @@ export declare const threadAnswer: ({ items, ...thread }: ThreadContext, reader:
57
57
  chat_title: string | null;
58
58
  timestamp: string;
59
59
  sender_name: string | null;
60
- text: string;
60
+ excerpt: string;
61
61
  }[] | undefined;
62
62
  outgoing: boolean | null;
63
63
  attachments: {
@@ -61,7 +61,7 @@ export declare const writtenAnswer: ({ operationId, sendId, message, messages, s
61
61
  chat_title: string | null;
62
62
  timestamp: string;
63
63
  sender_name: string | null;
64
- text: string;
64
+ excerpt: string;
65
65
  }[] | undefined;
66
66
  outgoing: boolean | null;
67
67
  attachments: {
@@ -280,7 +280,7 @@ export declare const writtenAnswer: ({ operationId, sendId, message, messages, s
280
280
  chat_title: string | null;
281
281
  timestamp: string;
282
282
  sender_name: string | null;
283
- text: string;
283
+ excerpt: string;
284
284
  }[] | undefined;
285
285
  outgoing: boolean | null;
286
286
  attachments: {
@@ -20,7 +20,7 @@ const MESSAGE_HIT_UNTRUSTED = [
20
20
  "context.text",
21
21
  "follow_ups.chat_title",
22
22
  "follow_ups.sender_name",
23
- "follow_ups.text",
23
+ "follow_ups.excerpt",
24
24
  ];
25
25
  const MAIL_HIT_UNTRUSTED = [
26
26
  "email.subject",
@@ -81,7 +81,7 @@ export const messageHitItem = (hit, query = "") => {
81
81
  chat_title: one.chatTitle ?? null,
82
82
  timestamp: one.timestamp,
83
83
  sender_name: one.senderName,
84
- text: one.text,
84
+ excerpt: excerpt(one.text, query),
85
85
  })),
86
86
  untrusted: hit.email ? [...MESSAGE_HIT_UNTRUSTED, ...MAIL_HIT_UNTRUSTED] : MESSAGE_HIT_UNTRUSTED,
87
87
  });
@@ -107,11 +107,13 @@ const notesCommand = (messenger) => annotate(new Command("notes"), { effects: LO
107
107
  "text model, by meaning; each hit says which found it and what it links to")
108
108
  .argument("<query...>", 'strict Lucene query: words, "phrases", AND/OR/NOT, tag:, source:internal (written in memo) or source:file ' +
109
109
  "(from a notes folder) and date ranges")
110
- .addHelpText("after", "tag: in --filter keeps the notes with that tag and leaves the search by meaning as it is — --filter 'tag:work'. " +
110
+ .addHelpText("after", "Fields in the query or in --filter (tag:, date:, source:…) hold for every hit, found by words or by meaning; " +
111
+ "only the words are searched by meaning, and a query of fields alone lists the matching notes, newest first — " +
112
+ "'budget tag:work', 'date:today'. updated_at is the time date: reads: a Drive document's edit time. " +
111
113
  "--json: the list is items; each note's reference is ref.")
112
114
  .option("--source <accounts>", "only these notes accounts, separated by commas: notes:internal for notes written in memo, notes:<folder id> " +
113
115
  "as a hit shows it, notes, documents or group:<name>")
114
- .option("--filter <query>", "a query every hit must also match; it does not change the search by meaning")
116
+ .option("--filter <query>", "a query every hit must also match, found by words or by meaning")
115
117
  .option("--limit <n>", "how many", positiveCount("--limit"))
116
118
  .option("--page <n>", "which page, starting at 1", positiveCount("--page"))
117
119
  .option("--exact", "words as written only; meaning is not searched")
@@ -56,7 +56,7 @@ export declare const conversationDetails: v.UnionSchema<[v.StrictObjectSchema<{
56
56
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
57
57
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
58
58
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
59
- readonly text: v.StringSchema<undefined>;
59
+ readonly excerpt: v.StringSchema<undefined>;
60
60
  }, undefined>, undefined>, undefined>;
61
61
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
62
62
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -273,7 +273,7 @@ export declare const conversationDetails: v.UnionSchema<[v.StrictObjectSchema<{
273
273
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
274
274
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
275
275
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
276
- readonly text: v.StringSchema<undefined>;
276
+ readonly excerpt: v.StringSchema<undefined>;
277
277
  }, undefined>, undefined>, undefined>;
278
278
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
279
279
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -47,7 +47,7 @@ export declare const contribution: v.StrictObjectSchema<{
47
47
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
48
48
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
49
49
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
50
- readonly text: v.StringSchema<undefined>;
50
+ readonly excerpt: v.StringSchema<undefined>;
51
51
  }, undefined>, undefined>, undefined>;
52
52
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
53
53
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -264,7 +264,7 @@ export declare const contribution: v.StrictObjectSchema<{
264
264
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
265
265
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
266
266
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
267
- readonly text: v.StringSchema<undefined>;
267
+ readonly excerpt: v.StringSchema<undefined>;
268
268
  }, undefined>, undefined>, undefined>;
269
269
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
270
270
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -519,7 +519,7 @@ export declare const contribution: v.StrictObjectSchema<{
519
519
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
520
520
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
521
521
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
522
- readonly text: v.StringSchema<undefined>;
522
+ readonly excerpt: v.StringSchema<undefined>;
523
523
  }, undefined>, undefined>, undefined>;
524
524
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
525
525
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -736,7 +736,7 @@ export declare const contribution: v.StrictObjectSchema<{
736
736
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
737
737
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
738
738
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
739
- readonly text: v.StringSchema<undefined>;
739
+ readonly excerpt: v.StringSchema<undefined>;
740
740
  }, undefined>, undefined>, undefined>;
741
741
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
742
742
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -1027,7 +1027,7 @@ export declare const evidence: v.UnionSchema<[v.StrictObjectSchema<{
1027
1027
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
1028
1028
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
1029
1029
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
1030
- readonly text: v.StringSchema<undefined>;
1030
+ readonly excerpt: v.StringSchema<undefined>;
1031
1031
  }, undefined>, undefined>, undefined>;
1032
1032
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
1033
1033
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -1244,7 +1244,7 @@ export declare const evidence: v.UnionSchema<[v.StrictObjectSchema<{
1244
1244
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
1245
1245
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
1246
1246
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
1247
- readonly text: v.StringSchema<undefined>;
1247
+ readonly excerpt: v.StringSchema<undefined>;
1248
1248
  }, undefined>, undefined>, undefined>;
1249
1249
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
1250
1250
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -1499,7 +1499,7 @@ export declare const evidence: v.UnionSchema<[v.StrictObjectSchema<{
1499
1499
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
1500
1500
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
1501
1501
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
1502
- readonly text: v.StringSchema<undefined>;
1502
+ readonly excerpt: v.StringSchema<undefined>;
1503
1503
  }, undefined>, undefined>, undefined>;
1504
1504
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
1505
1505
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -1716,7 +1716,7 @@ export declare const evidence: v.UnionSchema<[v.StrictObjectSchema<{
1716
1716
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
1717
1717
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
1718
1718
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
1719
- readonly text: v.StringSchema<undefined>;
1719
+ readonly excerpt: v.StringSchema<undefined>;
1720
1720
  }, undefined>, undefined>, undefined>;
1721
1721
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
1722
1722
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -270,7 +270,7 @@ export declare const ENTITIES: {
270
270
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
271
271
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
272
272
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
273
- readonly text: v.StringSchema<undefined>;
273
+ readonly excerpt: v.StringSchema<undefined>;
274
274
  }, undefined>, undefined>, undefined>;
275
275
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
276
276
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -487,7 +487,7 @@ export declare const ENTITIES: {
487
487
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
488
488
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
489
489
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
490
- readonly text: v.StringSchema<undefined>;
490
+ readonly excerpt: v.StringSchema<undefined>;
491
491
  }, undefined>, undefined>, undefined>;
492
492
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
493
493
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -709,7 +709,7 @@ export declare const ENTITIES: {
709
709
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
710
710
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
711
711
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
712
- readonly text: v.StringSchema<undefined>;
712
+ readonly excerpt: v.StringSchema<undefined>;
713
713
  }, undefined>, undefined>, undefined>;
714
714
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
715
715
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -1192,7 +1192,7 @@ export declare const ENTITIES: {
1192
1192
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
1193
1193
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
1194
1194
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
1195
- readonly text: v.StringSchema<undefined>;
1195
+ readonly excerpt: v.StringSchema<undefined>;
1196
1196
  }, undefined>, undefined>, undefined>;
1197
1197
  readonly email: v.OptionalSchema<v.StrictObjectSchema<{
1198
1198
  readonly subject: v.NullableSchema<v.StringSchema<undefined>, undefined>;
@@ -1279,7 +1279,7 @@ export declare const ENTITIES: {
1279
1279
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
1280
1280
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
1281
1281
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
1282
- readonly text: v.StringSchema<undefined>;
1282
+ readonly excerpt: v.StringSchema<undefined>;
1283
1283
  }, undefined>, undefined>, undefined>;
1284
1284
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
1285
1285
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -1496,7 +1496,7 @@ export declare const ENTITIES: {
1496
1496
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
1497
1497
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
1498
1498
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
1499
- readonly text: v.StringSchema<undefined>;
1499
+ readonly excerpt: v.StringSchema<undefined>;
1500
1500
  }, undefined>, undefined>, undefined>;
1501
1501
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
1502
1502
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -1944,7 +1944,7 @@ export declare const ENTITIES: {
1944
1944
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
1945
1945
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
1946
1946
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
1947
- readonly text: v.StringSchema<undefined>;
1947
+ readonly excerpt: v.StringSchema<undefined>;
1948
1948
  }, undefined>, undefined>, undefined>;
1949
1949
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
1950
1950
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -2161,7 +2161,7 @@ export declare const ENTITIES: {
2161
2161
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
2162
2162
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
2163
2163
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
2164
- readonly text: v.StringSchema<undefined>;
2164
+ readonly excerpt: v.StringSchema<undefined>;
2165
2165
  }, undefined>, undefined>, undefined>;
2166
2166
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
2167
2167
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -2382,7 +2382,7 @@ export declare const ENTITIES: {
2382
2382
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
2383
2383
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
2384
2384
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
2385
- readonly text: v.StringSchema<undefined>;
2385
+ readonly excerpt: v.StringSchema<undefined>;
2386
2386
  }, undefined>, undefined>, undefined>;
2387
2387
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
2388
2388
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -2599,7 +2599,7 @@ export declare const ENTITIES: {
2599
2599
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
2600
2600
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
2601
2601
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
2602
- readonly text: v.StringSchema<undefined>;
2602
+ readonly excerpt: v.StringSchema<undefined>;
2603
2603
  }, undefined>, undefined>, undefined>;
2604
2604
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
2605
2605
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -6854,7 +6854,7 @@ export declare const ENTITIES: {
6854
6854
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
6855
6855
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
6856
6856
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
6857
- readonly text: v.StringSchema<undefined>;
6857
+ readonly excerpt: v.StringSchema<undefined>;
6858
6858
  }, undefined>, undefined>, undefined>;
6859
6859
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
6860
6860
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -7071,7 +7071,7 @@ export declare const ENTITIES: {
7071
7071
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
7072
7072
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
7073
7073
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
7074
- readonly text: v.StringSchema<undefined>;
7074
+ readonly excerpt: v.StringSchema<undefined>;
7075
7075
  }, undefined>, undefined>, undefined>;
7076
7076
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
7077
7077
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -7531,7 +7531,7 @@ export declare const ENTITIES: {
7531
7531
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
7532
7532
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
7533
7533
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
7534
- readonly text: v.StringSchema<undefined>;
7534
+ readonly excerpt: v.StringSchema<undefined>;
7535
7535
  }, undefined>, undefined>, undefined>;
7536
7536
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
7537
7537
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -8039,7 +8039,7 @@ export declare const ENTITIES: {
8039
8039
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
8040
8040
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
8041
8041
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
8042
- readonly text: v.StringSchema<undefined>;
8042
+ readonly excerpt: v.StringSchema<undefined>;
8043
8043
  }, undefined>, undefined>, undefined>;
8044
8044
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
8045
8045
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -8723,7 +8723,7 @@ export declare const ENTITIES: {
8723
8723
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
8724
8724
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
8725
8725
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
8726
- readonly text: v.StringSchema<undefined>;
8726
+ readonly excerpt: v.StringSchema<undefined>;
8727
8727
  }, undefined>, undefined>, undefined>;
8728
8728
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
8729
8729
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -10394,7 +10394,7 @@ export declare const ENTITIES: {
10394
10394
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
10395
10395
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
10396
10396
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
10397
- readonly text: v.StringSchema<undefined>;
10397
+ readonly excerpt: v.StringSchema<undefined>;
10398
10398
  }, undefined>, undefined>, undefined>;
10399
10399
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
10400
10400
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -10611,7 +10611,7 @@ export declare const ENTITIES: {
10611
10611
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
10612
10612
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
10613
10613
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
10614
- readonly text: v.StringSchema<undefined>;
10614
+ readonly excerpt: v.StringSchema<undefined>;
10615
10615
  }, undefined>, undefined>, undefined>;
10616
10616
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
10617
10617
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -10866,7 +10866,7 @@ export declare const ENTITIES: {
10866
10866
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
10867
10867
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
10868
10868
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
10869
- readonly text: v.StringSchema<undefined>;
10869
+ readonly excerpt: v.StringSchema<undefined>;
10870
10870
  }, undefined>, undefined>, undefined>;
10871
10871
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
10872
10872
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -11083,7 +11083,7 @@ export declare const ENTITIES: {
11083
11083
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
11084
11084
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
11085
11085
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
11086
- readonly text: v.StringSchema<undefined>;
11086
+ readonly excerpt: v.StringSchema<undefined>;
11087
11087
  }, undefined>, undefined>, undefined>;
11088
11088
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
11089
11089
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -46,7 +46,7 @@ export declare const watchedMessage: v.StrictObjectSchema<{
46
46
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
47
47
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
48
48
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
49
- readonly text: v.StringSchema<undefined>;
49
+ readonly excerpt: v.StringSchema<undefined>;
50
50
  }, undefined>, undefined>, undefined>;
51
51
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
52
52
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -555,7 +555,7 @@ export declare const messageEvent: v.VariantSchema<"event", [v.StrictObjectSchem
555
555
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
556
556
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
557
557
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
558
- readonly text: v.StringSchema<undefined>;
558
+ readonly excerpt: v.StringSchema<undefined>;
559
559
  }, undefined>, undefined>, undefined>;
560
560
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
561
561
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -1171,7 +1171,7 @@ export declare const watchItem: v.UnionSchema<[v.StrictObjectSchema<{
1171
1171
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
1172
1172
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
1173
1173
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
1174
- readonly text: v.StringSchema<undefined>;
1174
+ readonly excerpt: v.StringSchema<undefined>;
1175
1175
  }, undefined>, undefined>, undefined>;
1176
1176
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
1177
1177
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -1679,7 +1679,7 @@ export declare const watchItem: v.UnionSchema<[v.StrictObjectSchema<{
1679
1679
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
1680
1680
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
1681
1681
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
1682
- readonly text: v.StringSchema<undefined>;
1682
+ readonly excerpt: v.StringSchema<undefined>;
1683
1683
  }, undefined>, undefined>, undefined>;
1684
1684
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
1685
1685
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -51,7 +51,7 @@ export declare const messageThread: v.StrictObjectSchema<{
51
51
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
52
52
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
53
53
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
54
- readonly text: v.StringSchema<undefined>;
54
+ readonly excerpt: v.StringSchema<undefined>;
55
55
  }, undefined>, undefined>, undefined>;
56
56
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
57
57
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -292,7 +292,7 @@ export declare const messageContext: v.UnionSchema<[v.StrictObjectSchema<{
292
292
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
293
293
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
294
294
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
295
- readonly text: v.StringSchema<undefined>;
295
+ readonly excerpt: v.StringSchema<undefined>;
296
296
  }, undefined>, undefined>, undefined>;
297
297
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
298
298
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -514,7 +514,7 @@ export declare const messageContext: v.UnionSchema<[v.StrictObjectSchema<{
514
514
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
515
515
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
516
516
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
517
- readonly text: v.StringSchema<undefined>;
517
+ readonly excerpt: v.StringSchema<undefined>;
518
518
  }, undefined>, undefined>, undefined>;
519
519
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
520
520
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -47,7 +47,7 @@ export declare const messageWrite: v.StrictObjectSchema<{
47
47
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
48
48
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
49
49
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
50
- readonly text: v.StringSchema<undefined>;
50
+ readonly excerpt: v.StringSchema<undefined>;
51
51
  }, undefined>, undefined>, undefined>;
52
52
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
53
53
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -265,7 +265,7 @@ export declare const messageWrite: v.StrictObjectSchema<{
265
265
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
266
266
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
267
267
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
268
- readonly text: v.StringSchema<undefined>;
268
+ readonly excerpt: v.StringSchema<undefined>;
269
269
  }, undefined>, undefined>, undefined>;
270
270
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
271
271
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -491,7 +491,7 @@ export declare const messageSend: v.UnionSchema<[v.StrictObjectSchema<{
491
491
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
492
492
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
493
493
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
494
- readonly text: v.StringSchema<undefined>;
494
+ readonly excerpt: v.StringSchema<undefined>;
495
495
  }, undefined>, undefined>, undefined>;
496
496
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
497
497
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -709,7 +709,7 @@ export declare const messageSend: v.UnionSchema<[v.StrictObjectSchema<{
709
709
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
710
710
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
711
711
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
712
- readonly text: v.StringSchema<undefined>;
712
+ readonly excerpt: v.StringSchema<undefined>;
713
713
  }, undefined>, undefined>, undefined>;
714
714
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
715
715
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -57,14 +57,14 @@ export declare const message: v.StrictObjectSchema<{
57
57
  }, undefined>, undefined>, undefined>;
58
58
  /**
59
59
  * A discovery hit's later messages, from any chat the search reads, that share a rare word with it, latest first:
60
- * one may change or cancel what the hit says.
60
+ * one may change or cancel what the hit says. An excerpt, as the hit's own: `messages context <ref>` has the whole text.
61
61
  */
62
62
  readonly follow_ups: v.OptionalSchema<v.ArraySchema<v.StrictObjectSchema<{
63
63
  readonly ref: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
64
64
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
65
65
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
66
66
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
67
- readonly text: v.StringSchema<undefined>;
67
+ readonly excerpt: v.StringSchema<undefined>;
68
68
  }, undefined>, undefined>, undefined>;
69
69
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
70
70
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -61,14 +61,14 @@ export const message = v.strictObject({
61
61
  context: v.optional(v.array(v.strictObject({ ref: reference, timestamp: time, sender_name: v.nullable(text), text }))),
62
62
  /**
63
63
  * A discovery hit's later messages, from any chat the search reads, that share a rare word with it, latest first:
64
- * one may change or cancel what the hit says.
64
+ * one may change or cancel what the hit says. An excerpt, as the hit's own: `messages context <ref>` has the whole text.
65
65
  */
66
66
  follow_ups: v.optional(v.array(v.strictObject({
67
67
  ref: reference,
68
68
  chat_title: v.nullable(text),
69
69
  timestamp: time,
70
70
  sender_name: v.nullable(text),
71
- text,
71
+ excerpt: text,
72
72
  }))),
73
73
  outgoing: v.nullable(v.boolean()),
74
74
  attachments: v.array(attachment),
@@ -70,7 +70,7 @@ export declare const storeExport: v.UnionSchema<[v.StrictObjectSchema<{
70
70
  readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
71
71
  readonly timestamp: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.IsoTimestampAction<string, "an ISO 8601 time in UTC">, v.EndsWithAction<string, "Z", "an ISO 8601 time in UTC, ending in `Z`">]>;
72
72
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
73
- readonly text: v.StringSchema<undefined>;
73
+ readonly excerpt: v.StringSchema<undefined>;
74
74
  }, undefined>, undefined>, undefined>;
75
75
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
76
76
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
@@ -3,7 +3,7 @@ import type { AccountKey, ChatCompleteness, MessageStore } from "../store/store.
3
3
  /** What the store holds of one chat, and how far that goes: never message text. */
4
4
  export interface ChatCoverage {
5
5
  chatId: Id;
6
- /** `null` while the store holds no message of the chat: titles come with the stats. */
6
+ /** The chat row's title; `null` when the store has no row for the chat or the row has none. */
7
7
  title: string | null;
8
8
  completeness: ChatCompleteness;
9
9
  held: {
@@ -9,6 +9,7 @@ export const chatCoverage = async (store, account, chatIds) => {
9
9
  const found = new Map((await store.chats.chatCompleteness(account, ids)).map((one) => [one.chatId, one]));
10
10
  return Promise.all(ids.map(async (chatId) => {
11
11
  const [stats] = await store.chats.chatStats(account, chatId);
12
+ const title = stats?.title ?? (await store.chats.chatTitle(account, chatId));
12
13
  const completeness = found.get(chatId) ?? {
13
14
  chatId,
14
15
  state: "unknown",
@@ -19,7 +20,7 @@ export const chatCoverage = async (store, account, chatIds) => {
19
20
  };
20
21
  return {
21
22
  chatId,
22
- title: stats?.title ?? null,
23
+ title,
23
24
  completeness,
24
25
  held: { messages: stats?.messages ?? 0, oldestAt: stats?.oldestAt ?? null, newestAt: stats?.newestAt ?? null },
25
26
  };
@@ -1,14 +1,13 @@
1
1
  import type { Profiles } from "../search/account-list.js";
2
2
  import type { Note, StoredAccount, StoredFile } from "../store/index.js";
3
3
  /**
4
- * Where a hit came from. The document's preferred file says it: its account's provider, its link and its edit time.
4
+ * Where a hit came from. The document's preferred file says it: its account's provider and its link.
5
5
  * A Drive document under a notes folder (before version 31 moves it) is still known by its front matter.
6
- * Indexing time belongs to the local row.
6
+ * The hit's time is the store search's: the one `date:` reads.
7
7
  */
8
8
  export declare const documentSource: (note: Note, accounts: readonly StoredAccount[], profiles: Profiles, file?: StoredFile) => {
9
9
  kind: "document" | "note";
10
10
  provider: string;
11
11
  account: string;
12
12
  url?: string | undefined;
13
- updated_at: string;
14
13
  };
@@ -1,8 +1,8 @@
1
1
  import { isFolderProvider, kindIn } from "../domain/providers.js";
2
2
  /**
3
- * Where a hit came from. The document's preferred file says it: its account's provider, its link and its edit time.
3
+ * Where a hit came from. The document's preferred file says it: its account's provider and its link.
4
4
  * A Drive document under a notes folder (before version 31 moves it) is still known by its front matter.
5
- * Indexing time belongs to the local row.
5
+ * The hit's time is the store search's: the one `date:` reads.
6
6
  */
7
7
  export const documentSource = (note, accounts, profiles, file) => {
8
8
  const metadata = note.frontMatter;
@@ -16,15 +16,10 @@ export const documentSource = (note, accounts, profiles, file) => {
16
16
  (isFolderProvider(key.provider) && key.account === account.name))
17
17
  : undefined;
18
18
  const url = file?.url ?? (remote && typeof metadata?.sourceUrl === "string" ? metadata.sourceUrl : undefined);
19
- const modified = file?.externalUpdatedAt ??
20
- (remote && typeof metadata?.modifiedAt === "string" && Number.isFinite(Date.parse(metadata.modifiedAt))
21
- ? metadata.modifiedAt
22
- : undefined);
23
19
  return {
24
20
  kind: remote ? "document" : "note",
25
21
  provider: profile ? profile.command : "notes",
26
22
  account: profile?.name ?? note.folderId ?? "internal",
27
23
  ...(remote && url !== undefined ? { url } : {}),
28
- updated_at: remote && modified !== undefined ? modified : note.updatedAt,
29
24
  };
30
25
  };
@@ -31,10 +31,15 @@ const FOLLOW_UP_CANDIDATES = 20;
31
31
  export const searchDiscovery = async (store, account, asked, messenger = {}, meaning, rerank) => {
32
32
  // A question's own "?" would parse as a one-character wildcard and send it to strict search (BUG-395); the
33
33
  // colon of a time or ratio ("22:00", "1:1") would parse as a field and fail the whole search, and escaped it is
34
- // syntax again, so it splits the numbers (BUG-450).
34
+ // syntax again, so it splits the numbers (BUG-450). A quoted timestamp keeps its colons (BUG-458).
35
35
  const request = asked.ast !== undefined || asked.exact || !asked.text
36
36
  ? asked
37
- : { ...asked, text: asked.text.replace(/(?<=\p{L})\?(?=\s|$)/gu, "").replace(/(?<=\d):(?=\d)/gu, " ") };
37
+ : {
38
+ ...asked,
39
+ text: asked.text
40
+ .replace(/(?<=\p{L})\?(?=\s|$)/gu, "")
41
+ .replace(/"(?:[^"\\]|\\.)*"|(?<=\d):(?=\d)/gu, (found) => (found === ":" ? " " : found)),
42
+ };
38
43
  const original = await prepareLucene(store, account, request, messenger);
39
44
  const text = request.text ?? "";
40
45
  const plan = planQuestion(text, request.ast !== undefined || !!request.exact || !!request.newest);
@@ -1,3 +1,4 @@
1
+ import { type QueryNode } from "@wirecat/cli-base/query";
1
2
  import { type Embedder, type WarmEmbedders } from "../embeddings/embed.js";
2
3
  import { type TextModel } from "../embeddings/models.js";
3
4
  import type { PictureEmbedder } from "../images/picture/embedder.js";
@@ -90,7 +91,7 @@ export interface FoundNote {
90
91
  /** The note's path inside its folder; `null` for a note written in memo. */
91
92
  path: string | null;
92
93
  title: string | null;
93
- /** When the store's copy last changed. */
94
+ /** The time `date:` reads: a Drive document's edit time when the store has one, else when the store's copy changed. */
94
95
  updated_at: string;
95
96
  /** The first line holding a word of the query. */
96
97
  excerpt: string;
@@ -135,7 +136,7 @@ export interface NotesQuery {
135
136
  limit?: number;
136
137
  offset?: number;
137
138
  tag?: string;
138
- /** A query in the same language that every hit must also match; it does not change the meaning half. */
139
+ /** A query in the same language that every hit must also match, as the query's own fields are. */
139
140
  filter?: string;
140
141
  folderIds?: string[];
141
142
  /** With `folderIds`, the notes written in memo too: one ranked list, so `offset` pages it. */
@@ -164,9 +165,18 @@ export declare const noteSourceOfQuery: (query: string) => {
164
165
  source?: Note["source"];
165
166
  rest: string;
166
167
  };
168
+ /**
169
+ * What a note found by meaning must still match: the query with each word it asks for taken as matched, since the
170
+ * meaning half stands in for those. Words it excludes, and every other field, stay. `undefined` when nothing but
171
+ * wanted words is left, so any note found by meaning fits.
172
+ */
173
+ export declare const fieldsOfQuery: (root: QueryNode) => QueryNode | undefined;
174
+ /** The words and phrases the query asks for, for the meaning half to embed; none it excludes, no field. */
175
+ export declare const meaningWords: (root: QueryNode) => string;
167
176
  /**
168
177
  * `search notes`: words and stems, and meaning when the local model is there, merged by reciprocal rank;
169
- * each hit says which half found it, and the answer lists what the notes found link to.
178
+ * each hit says which half found it, and the answer lists what the notes found link to. Every field of the query and
179
+ * of `filter` holds for every hit, found by words or by meaning; a query of fields alone is not searched by meaning.
170
180
  */
171
181
  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>;
172
182
  /** What `notes:internal` and `notes:<folder id>` ask of the store: one query, so a page of it is one page. */
@@ -19,8 +19,10 @@ import { listContextOf } from "./search-kind.js";
19
19
  * Text in the query language messages use, parsed for a store search: `date:` read in `timezone`, and the store's
20
20
  * stemmer when a word is stemmed.
21
21
  */
22
- export const storeQuery = async (store, text, { exact, timezone } = {}) => {
23
- const ast = validateFields(parseLucene(text, { ...SEARCH_QUERY, defaultField: exact ? "exact" : "text" }));
22
+ export const storeQuery = async (store, text, { exact, timezone } = {}) => resolvedQuery(store, parsedQuery(text, exact), timezone);
23
+ const parsedQuery = (text, exact) => validateFields(parseLucene(text, { ...SEARCH_QUERY, defaultField: exact ? "exact" : "text" })).root;
24
+ const resolvedQuery = async (store, root, timezone) => {
25
+ const ast = { root };
24
26
  const zone = timezoneOf(timezone);
25
27
  const resolve = (node) => {
26
28
  if (node.kind === "boolean")
@@ -145,8 +147,6 @@ const MAX_LINE = 200;
145
147
  const MAX_NOTES_OFFSET = 1000;
146
148
  /** How deep each half is read before the two are merged. */
147
149
  const CANDIDATES = 50;
148
- /** What a filter may narrow the meaning half to; past this the filter is applied to words only. */
149
- const FILTERED = 500;
150
150
  /** Reciprocal rank fusion's usual constant, as conversations use. */
151
151
  export const RRF_K = 60;
152
152
  const firstLine = (text, query) => {
@@ -225,17 +225,88 @@ export const noteSourceOfQuery = (query) => {
225
225
  }
226
226
  return { source, rest };
227
227
  };
228
+ const WORD_FIELDS = new Set(["text", "exact"]);
229
+ /**
230
+ * What a note found by meaning must still match: the query with each word it asks for taken as matched, since the
231
+ * meaning half stands in for those. Words it excludes, and every other field, stay. `undefined` when nothing but
232
+ * wanted words is left, so any note found by meaning fits.
233
+ */
234
+ export const fieldsOfQuery = (root) => {
235
+ let narrows = false;
236
+ const kept = (node) => {
237
+ if (node.kind === "boolean")
238
+ return {
239
+ ...node,
240
+ clauses: node.clauses.map(({ occur, node }) => {
241
+ if (occur !== "must_not")
242
+ return { occur, node: kept(node) };
243
+ narrows = true;
244
+ return { occur, node };
245
+ }),
246
+ };
247
+ if (!WORD_FIELDS.has(node.field)) {
248
+ narrows = true;
249
+ return node;
250
+ }
251
+ // `in:notes` holds for every note and document; an empty clause list would match nothing.
252
+ return { kind: "predicate", field: "in", operator: "term", value: "notes", span: node.span };
253
+ };
254
+ const fields = kept(root);
255
+ return narrows ? fields : undefined;
256
+ };
257
+ /** The words and phrases the query asks for, for the meaning half to embed; none it excludes, no field. */
258
+ export const meaningWords = (root) => {
259
+ const wanted = (node) => node.kind === "boolean"
260
+ ? node.clauses.flatMap(({ occur, node }) => (occur === "must_not" ? [] : wanted(node)))
261
+ : WORD_FIELDS.has(node.field) && (node.operator === "term" || node.operator === "phrase")
262
+ ? [node.value]
263
+ : [];
264
+ return wanted(root).join(" ").trim();
265
+ };
266
+ const allOf = (nodes) => nodes.length <= 1
267
+ ? nodes[0]
268
+ : {
269
+ kind: "boolean",
270
+ clauses: nodes.map((node) => ({ occur: "must", node })),
271
+ span: { start: 0, end: 0 },
272
+ };
273
+ /**
274
+ * The notes of `refs` that match `root`, in the order of `refs`, each with the time `date:` read. One store query over
275
+ * just those notes, so a filter that matches thousands of notes still checks every candidate.
276
+ */
277
+ const matching = async (store, refs, root, scope) => {
278
+ if (refs.length === 0)
279
+ return new Map();
280
+ const resolved = await resolvedQuery(store, root ?? { kind: "predicate", field: "in", operator: "term", value: "notes", span: { start: 0, end: 0 } }, scope.timezone);
281
+ const found = await store.notes.search({
282
+ root: resolved.root,
283
+ refs,
284
+ limit: refs.length,
285
+ ...(scope.folderIds ? { folderIds: scope.folderIds } : {}),
286
+ ...(scope.withInternal ? { withInternal: true } : {}),
287
+ ...(scope.source ? { source: scope.source } : {}),
288
+ ...(resolved.stemmer ? { stemmer: resolved.stemmer } : {}),
289
+ });
290
+ return new Map(found.items.map(({ ref, note, at }) => [ref, { note, at }]));
291
+ };
228
292
  /**
229
293
  * `search notes`: words and stems, and meaning when the local model is there, merged by reciprocal rank;
230
- * each hit says which half found it, and the answer lists what the notes found link to.
294
+ * each hit says which half found it, and the answer lists what the notes found link to. Every field of the query and
295
+ * of `filter` holds for every hit, found by words or by meaning; a query of fields alone is not searched by meaning.
231
296
  */
232
297
  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, } = {}) => {
233
298
  const typed = noteSourceOfQuery(query);
234
299
  const source = given ?? typed.source;
235
- const words = typed.source !== undefined && typed.source === source ? typed.rest : query;
300
+ const rest = typed.source !== undefined && typed.source === source ? typed.rest : query;
236
301
  const label = tag === undefined ? undefined : normalizeTag(tag);
237
302
  const filter = [expression ? `(${expression})` : "", label === undefined ? "" : `tag:${label}`].filter(Boolean).join(" AND ") ||
238
303
  undefined;
304
+ const parsed = query.trim() ? parsedQuery(query, exact) : undefined;
305
+ const words = parsed === undefined ? "" : meaningWords(parsed);
306
+ const narrowing = allOf([
307
+ ...(parsed === undefined ? [] : [fieldsOfQuery(parsed)].filter((node) => node !== undefined)),
308
+ ...(filter === undefined ? [] : [parsedQuery(filter)]),
309
+ ]);
239
310
  const text = [query.trim() ? `(${query})` : "", filter ?? ""].filter(Boolean).join(" AND ");
240
311
  const window = offset + limit;
241
312
  const scope = {
@@ -248,7 +319,7 @@ export const searchNotes = async (store, query, { limit = 20, offset = 0, tag, f
248
319
  let meaning;
249
320
  if (exact)
250
321
  meaningSkipped = "--exact searches words as written";
251
- else if (words.trim()) {
322
+ else if (words) {
252
323
  try {
253
324
  const nearest = await nearestNotes(store, words, {
254
325
  limit: Math.max(window, CANDIDATES),
@@ -259,13 +330,11 @@ export const searchNotes = async (store, query, { limit = 20, offset = 0, tag, f
259
330
  ...(withInternal ? { withInternal: true } : {}),
260
331
  ...(source === undefined ? {} : { source }),
261
332
  });
262
- if (filter === undefined)
263
- meaning = nearest;
264
- else {
265
- const allowed = await searchNotesQuery(store, { text: filter, limit: FILTERED, ...scope });
266
- const refs = new Set(allowed.items.map(({ ref }) => ref));
267
- meaning = nearest.filter(({ ref }) => refs.has(ref));
268
- }
333
+ const allowed = await matching(store, nearest.map(({ ref }) => ref), narrowing, scope);
334
+ meaning = nearest.flatMap(({ ref }) => {
335
+ const found = allowed.get(ref);
336
+ return found ? [{ ref, ...found }] : [];
337
+ });
269
338
  }
270
339
  catch (error) {
271
340
  if (!(error instanceof CliError && error.code === "not_found"))
@@ -273,7 +342,7 @@ export const searchNotes = async (store, query, { limit = 20, offset = 0, tag, f
273
342
  meaningSkipped = error.message;
274
343
  }
275
344
  }
276
- const titleWords = plainQueryWords(words);
345
+ const titleWords = plainQueryWords(rest);
277
346
  const titled = titleWords === undefined
278
347
  ? { items: [], hasMore: false }
279
348
  : await searchNotesQuery(store, {
@@ -292,10 +361,10 @@ export const searchNotes = async (store, query, { limit = 20, offset = 0, tag, f
292
361
  ...scope,
293
362
  });
294
363
  const ranked = new Map();
295
- const add = (list, by) => list.forEach(({ ref, note }, rank) => {
364
+ const add = (list, by) => list.forEach(({ ref, note, at }, rank) => {
296
365
  if (tierRefs.has(ref))
297
366
  return;
298
- const held = ranked.get(ref) ?? { note, score: 0, foundBy: [] };
367
+ const held = ranked.get(ref) ?? { note, at, score: 0, foundBy: [] };
299
368
  held.score += 1 / (RRF_K + rank + 1);
300
369
  held.foundBy.push(by);
301
370
  ranked.set(ref, held);
@@ -304,7 +373,7 @@ export const searchNotes = async (store, query, { limit = 20, offset = 0, tag, f
304
373
  if (meaning !== undefined)
305
374
  add(meaning, "meaning");
306
375
  const ordered = [
307
- ...tier.map(({ ref, note }) => [ref, { note, score: 0, foundBy: ["title"] }]),
376
+ ...tier.map(({ ref, note, at }) => [ref, { note, at, score: 0, foundBy: ["title"] }]),
308
377
  ...[...ranked.entries()].sort(([, a], [, b]) => b.score - a.score),
309
378
  ];
310
379
  const page = slice ? ordered : ordered.slice(offset, window);
@@ -313,7 +382,7 @@ export const searchNotes = async (store, query, { limit = 20, offset = 0, tag, f
313
382
  const counts = new Map();
314
383
  const accounts = await store.accounts.storedAccounts();
315
384
  const profiles = await store.accounts.profiles();
316
- for (const [ref, { note, foundBy }] of page) {
385
+ for (const [ref, { note, at, foundBy }] of page) {
317
386
  const links = (await store.notes.links({ from: ref })).map(({ to, targetText, kind, anchor }) => ({
318
387
  to,
319
388
  target_text: targetText,
@@ -327,6 +396,7 @@ export const searchNotes = async (store, query, { limit = 20, offset = 0, tag, f
327
396
  path: note.path,
328
397
  title: note.title,
329
398
  ...documentSource(note, accounts, profiles, note.source === "file" ? (await store.files.ofDocument(note.ref))[0] : undefined),
399
+ updated_at: at,
330
400
  excerpt: firstLine(note.text, words),
331
401
  links,
332
402
  found_by: foundBy,
@@ -360,6 +430,10 @@ export const searchNotes = async (store, query, { limit = 20, offset = 0, tag, f
360
430
  embedder: pictureEmbedder,
361
431
  })
362
432
  : {};
433
+ if (lane.pictures && narrowing !== undefined) {
434
+ const allowed = await matching(store, lane.pictures.map(({ ref }) => ref), narrowing, scope);
435
+ lane.pictures = lane.pictures.filter(({ ref }) => allowed.has(ref));
436
+ }
363
437
  return {
364
438
  query,
365
439
  ...(label === undefined ? {} : { tag: label }),
@@ -1,4 +1,6 @@
1
1
  import type { ContextEvidenceItem, ContextEvidenceReader, ContextIndex, ContextSource } from "../context/evidence.js";
2
+ import type { AccountKey } from "../store/types.js";
3
+ import type { ChatCoverage } from "./chat-coverage.js";
2
4
  import { type ProjectNeighbour, type ProjectNeighbourReader } from "./project-gathering-around.js";
3
5
  import { type ProjectFetchState, type ProjectGatheringRequest, type ProjectLane } from "./project-gathering-request.js";
4
6
  /** A connector's history fetch into the store; the store reads that follow find what it saved. */
@@ -18,6 +20,8 @@ export interface ProjectGatheringPorts {
18
20
  fetcher?: ProjectSourceFetcher;
19
21
  index?: ContextIndex;
20
22
  neighbours?: ProjectNeighbourReader;
23
+ /** What the store holds of the named chats of one account, e.g. `chatCoverage` over the store. */
24
+ coverage?: (account: AccountKey, chatIds: string[]) => Promise<ChatCoverage[]>;
21
25
  now?: () => number;
22
26
  }
23
27
  export interface ProjectIndexCatchUp {
@@ -88,9 +92,16 @@ export declare const gatherProjectEvidence: (ports: ProjectGatheringPorts, reque
88
92
  ref: string;
89
93
  source: number | null;
90
94
  lane: string | null;
91
- state: "in-scope" | "out-of-scope" | "unavailable";
92
- items: number;
93
- capped: boolean;
95
+ state: "in-scope" | "out-of-scope" | "pending" | "unavailable";
96
+ title: string | null;
97
+ completeness: import("../store/exports.js").ChatCompleteness | null;
98
+ held: {
99
+ messages: number;
100
+ oldestAt: string | null;
101
+ newestAt: string | null;
102
+ } | null;
103
+ items?: number | undefined;
104
+ capped?: boolean | undefined;
94
105
  }[];
95
106
  fetch: {
96
107
  state: ProjectFetchState;
@@ -136,7 +147,7 @@ export declare const gatherProjectEvidence: (ports: ProjectGatheringPorts, reque
136
147
  };
137
148
  stopped: ProjectGatheringStop;
138
149
  complete: boolean;
139
- completeness: "unknown";
150
+ completeness: "complete" | "partial" | "unknown";
140
151
  checkpoint: string | null;
141
152
  }>;
142
153
  export type ProjectGathering = Awaited<ReturnType<typeof gatherProjectEvidence>>;
@@ -264,6 +264,20 @@ export const gatherProjectEvidence = async (ports, request) => {
264
264
  },
265
265
  }, (ref) => byRef.has(ref) || seen.has(seenKey(ref)), signal);
266
266
  const people = peopleOf(items, senderOf);
267
+ const coverageOf = new Map();
268
+ if (ports.coverage) {
269
+ const byAccount = new Map();
270
+ for (const { provider, account, chat, source } of plan.linkedChats) {
271
+ if (source === null)
272
+ continue;
273
+ const group = byAccount.get(JSON.stringify([provider, account])) ?? { account: { provider, account }, chats: [] };
274
+ group.chats.push(chat);
275
+ byAccount.set(JSON.stringify([provider, account]), group);
276
+ }
277
+ for (const [key, { account, chats }] of byAccount)
278
+ for (const row of await ports.coverage(account, chats))
279
+ coverageOf.set(`${key}${row.chatId}`, row);
280
+ }
267
281
  const totals = {
268
282
  reads: (previous?.totals.reads ?? 0) + usage.reads,
269
283
  readBytes: (previous?.totals.readBytes ?? 0) + usage.readBytes,
@@ -284,6 +298,39 @@ export const gatherProjectEvidence = async (ports, request) => {
284
298
  seen: [...seen],
285
299
  totals,
286
300
  };
301
+ const linkedRows = plan.linkedChats.map(({ provider, account, chat, ref, source, lane }) => {
302
+ const laneUnits = units.flatMap((unit, index) => (unit.lane.id === lane ? [progress[index]] : []));
303
+ const state = source === null
304
+ ? "out-of-scope"
305
+ : laneUnits.every((u) => u?.state === "unavailable")
306
+ ? "unavailable"
307
+ : laneUnits.every((u) => u?.state === "pending")
308
+ ? "pending"
309
+ : "in-scope";
310
+ const row = coverageOf.get(`${JSON.stringify([provider, account])}${chat}`);
311
+ return {
312
+ provider,
313
+ account,
314
+ chat,
315
+ ref,
316
+ source,
317
+ lane,
318
+ state,
319
+ title: row?.title ?? null,
320
+ completeness: row?.completeness ?? null,
321
+ held: row?.held ?? null,
322
+ ...(state === "pending" ? {} : { items: lane === null ? 0 : (linkedTaken.get(lane) ?? 0) }),
323
+ ...(state === "pending" ? {} : { capped: lane !== null && capped.has(lane) }),
324
+ };
325
+ });
326
+ const states = linkedRows.flatMap((row) => (row.completeness ? [row.completeness.state] : []));
327
+ const completeness = !states.length
328
+ ? "unknown"
329
+ : states.includes("partial")
330
+ ? "partial"
331
+ : states.includes("unknown") || states.length < linkedRows.filter((row) => row.source !== null).length
332
+ ? "unknown"
333
+ : "complete";
287
334
  return {
288
335
  schemaVersion: 1,
289
336
  runKey: plan.runKey,
@@ -300,21 +347,7 @@ export const gatherProjectEvidence = async (ports, request) => {
300
347
  },
301
348
  scope: { origin: plan.scope, sources: plan.sources },
302
349
  lanes: plan.lanes,
303
- linkedChats: plan.linkedChats.map(({ provider, account, chat, ref, source, lane }) => ({
304
- provider,
305
- account,
306
- chat,
307
- ref,
308
- source,
309
- lane,
310
- state: source === null
311
- ? "out-of-scope"
312
- : units.every((unit, index) => unit.lane.id !== lane || progress[index]?.state === "unavailable")
313
- ? "unavailable"
314
- : "in-scope",
315
- items: lane === null ? 0 : (linkedTaken.get(lane) ?? 0),
316
- capped: lane !== null && capped.has(lane),
317
- })),
350
+ linkedChats: linkedRows,
318
351
  fetch: fetched.map((f, index) => ({ source: index, ...f })),
319
352
  index: indexed,
320
353
  units: units.map((unit, index) => ({
@@ -335,7 +368,7 @@ export const gatherProjectEvidence = async (ports, request) => {
335
368
  totals,
336
369
  stopped,
337
370
  complete: !resumable,
338
- completeness: "unknown",
371
+ completeness,
339
372
  checkpoint: resumable ? encodeCheckpoint(checkpoint) : null,
340
373
  };
341
374
  };
@@ -66,6 +66,8 @@ export interface ChatsStore {
66
66
  renamedChatId(key: AccountKey, chatId: Id): Promise<Id | undefined>;
67
67
  /** A stored chat's kind, with or without messages; `undefined` when the store does not know the chat. */
68
68
  chatKind(key: AccountKey, chatId: Id): Promise<ChatKind | undefined>;
69
+ /** A stored chat's title, with or without messages; `null` when it has none or the store does not know the chat. */
70
+ chatTitle(key: AccountKey, chatId: Id): Promise<string | null>;
69
71
  /** Per chat that spoke since `since` (ms): its newest message and who sent it when stored, never the text. */
70
72
  /** `where` is a `tasks agenda` query compiled over `AGENDA_COLUMNS`. */
71
73
  chatActivity(key: AccountKey, since: number, where?: Where): Promise<{
@@ -94,6 +94,15 @@ export const chatsStoreOver = (context) => {
94
94
  .get(accountKey, chatId);
95
95
  return row ? String(row.kind) : undefined;
96
96
  },
97
+ chatTitle: async (key, chatId) => {
98
+ const accountKey = findAccountPk(key);
99
+ if (accountKey === undefined)
100
+ return null;
101
+ const row = context.database
102
+ .prepare("SELECT title FROM chats WHERE account_id = ? AND external_id = ?")
103
+ .get(accountKey, chatId);
104
+ return row?.title == null ? null : String(row.title);
105
+ },
97
106
  chatActivity: async (key, since, where) => {
98
107
  const accountKey = findAccountPk(key);
99
108
  return accountKey === undefined
@@ -13,6 +13,8 @@ export interface NoteQuery {
13
13
  /** With `folderIds`, the notes written in memo too, in the same ranked list. */
14
14
  withInternal?: boolean;
15
15
  source?: Note["source"];
16
+ /** Only these notes (`document:<id>`, `note:<id>`): checks a list found another way against the query. */
17
+ refs?: string[];
16
18
  /** Newest first instead of best first. */
17
19
  newest?: boolean;
18
20
  /** The store's stemmer, set when a leaf is stemmed. */
@@ -26,6 +28,8 @@ export interface NoteHit {
26
28
  relevance: number | null;
27
29
  /** Whether the note holds the exact forms, when the search was stemmed. */
28
30
  exact?: boolean;
31
+ /** The time `date:` read: a Drive document's edit time when the store has one, else when the store's copy changed. */
32
+ at: string;
29
33
  }
30
34
  /** The fields a note has. A message-only field (`from:`, `chat:`, …) is refused rather than matching nothing. */
31
35
  export declare const NOTE_FIELDS: readonly ["text", "exact", "body", "tag", "date", "in", "has", "title", "site", "lang", "author", "keyword", "source"];
@@ -68,6 +72,7 @@ export interface CorpusRow {
68
72
  /** What one corpus is searched by; `accountIds` narrows a corpus that has accounts. */
69
73
  export type CorpusQuery = Pick<NoteQuery, "root" | "limit" | "offset" | "newest" | "stemmer" | "signal"> & {
70
74
  accountIds?: string[];
75
+ ids?: number[];
71
76
  };
72
77
  /** Notes matching a parsed query, by the same language as messages: files and written notes, merged. */
73
78
  export declare const searchNotes: (context: StoreContext, query: NoteQuery) => {
@@ -4,6 +4,7 @@ import { compileAutomaton, foldRegex, wildcardPattern, } from "../../search/luce
4
4
  import { isStemmed } from "../../search/lucene/resolved.js";
5
5
  import { exhausted, QUERY_LIMITS, searchError } from "../../search/lucene/types.js";
6
6
  import { normalize } from "../normalize.js";
7
+ import { contextDocumentTime } from "./context-document-source.js";
7
8
  import { documentPicturesOver } from "./document-pictures.js";
8
9
  import { linkKind } from "./link-kinds.js";
9
10
  import { prefixOf } from "./lucene.js";
@@ -42,7 +43,7 @@ const CORPORA = [
42
43
  type: "document",
43
44
  noun: "note",
44
45
  fields: NOTE_FIELDS,
45
- view: "(SELECT id, account_id AS folder_id, external_id AS path, title, coalesce(body, '') AS text, metadata, updated_at AS at, deleted_at FROM documents)",
46
+ view: `(SELECT n.id, n.account_id AS folder_id, n.external_id AS path, n.title, coalesce(n.body, '') AS text, n.metadata, ${contextDocumentTime} AS at, n.deleted_at FROM documents n)`,
46
47
  words: "document_words",
47
48
  stems: "document_stems",
48
49
  live: "coalesce(json_extract(n.metadata, '$.catalog.active'), 1)=1",
@@ -67,7 +68,13 @@ export const searchNotes = (context, query) => {
67
68
  const offset = query.offset ?? 0;
68
69
  const chosen = CORPORA.filter(({ type }) => (query.source === undefined || (query.source === "file") === (type === "document")) &&
69
70
  (!query.folderIds?.length || query.withInternal === true || type === "document"));
70
- const rows = chosen.flatMap((corpus) => searchCorpus(context, { ...query, ...(query.folderIds ? { accountIds: query.folderIds } : {}) }, corpus));
71
+ const idsOf = (type) => query.refs?.flatMap((ref) => (ref.startsWith(`${type}:`) ? [Number(ref.slice(type.length + 1))] : []));
72
+ const rows = chosen.flatMap((corpus) => {
73
+ const ids = idsOf(corpus.type);
74
+ if (ids?.length === 0)
75
+ return [];
76
+ return searchCorpus(context, { ...query, ...(query.folderIds ? { accountIds: query.folderIds } : {}), ...(ids ? { ids } : {}) }, corpus);
77
+ });
71
78
  const exactFirst = rows.some((row) => row.exact !== undefined);
72
79
  rows.sort((a, b) => query.newest
73
80
  ? (b.at ?? 0) - (a.at ?? 0) || a.id - b.id
@@ -89,6 +96,7 @@ export const searchNotes = (context, query) => {
89
96
  note,
90
97
  relevance: row.relevance,
91
98
  ...(row.exact === undefined ? {} : { exact: row.exact }),
99
+ at: new Date(row.at ?? 0).toISOString(),
92
100
  };
93
101
  }),
94
102
  hasMore: rows.length > offset + query.limit,
@@ -247,6 +255,7 @@ export const searchCorpus = (context, query, corpus) => {
247
255
  bound("n.deleted_at IS NULL"),
248
256
  ...(corpus.live ? [bound(corpus.live)] : []),
249
257
  ...(query.accountIds?.length && corpus.inAccounts ? [corpus.inAccounts(JSON.stringify(query.accountIds))] : []),
258
+ ...(query.ids ? [bound("n.id IN (SELECT value FROM json_each(?))", JSON.stringify(query.ids))] : []),
250
259
  ], "AND");
251
260
  const where = combine([scope, expression], "AND");
252
261
  const ranking = query.newest ? undefined : required(query.root, stemmed ? "stems" : "fts");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wirecat/cli-core",
3
- "version": "0.494.0",
3
+ "version": "0.496.0",
4
4
  "description": "The messenger-neutral half of a messaging CLI: domain model, message locators, rendering, name resolution, the SQLite seam, the command skeleton and shared read commands, the send guard, run records and the message store",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Viacheslav Ptsarev",
package/parity.json CHANGED
@@ -387,7 +387,7 @@
387
387
  "--filter": {
388
388
  "value": "query",
389
389
  "meaning": "Strict Lucene filter: any message in the conversation must match; the meaning query stays unchanged",
390
- "note": "search notes uses it for a query every note must also match, the meaning half unchanged"
390
+ "note": "search notes uses it for a query every note must also match, found by words or by meaning"
391
391
  },
392
392
  "--find-by-phone": {
393
393
  "value": "<who>",