@wirecat/cli-core 0.491.0 → 0.493.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.
Files changed (37) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/dist/cli/messenger/conversation-answers.d.ts +7 -0
  3. package/dist/cli/messenger/message-answers.d.ts +7 -0
  4. package/dist/cli/messenger/message-writes.d.ts +14 -0
  5. package/dist/cli/messenger/messages-command.js +13 -1
  6. package/dist/cli/messenger/messages-search-command.js +4 -1
  7. package/dist/cli/messenger/search-answers.d.ts +6 -2
  8. package/dist/cli/messenger/search-answers.js +60 -7
  9. package/dist/cli/messenger/tasks-command.js +15 -3
  10. package/dist/cli/steps.js +1 -0
  11. package/dist/entities/agenda.d.ts +42 -0
  12. package/dist/entities/agenda.js +10 -0
  13. package/dist/entities/conversation-results.d.ts +14 -0
  14. package/dist/entities/evidence.d.ts +56 -0
  15. package/dist/entities/index.d.ts +169 -0
  16. package/dist/entities/index.js +1 -0
  17. package/dist/entities/listener.d.ts +28 -0
  18. package/dist/entities/message-context.d.ts +21 -0
  19. package/dist/entities/message-write.d.ts +28 -0
  20. package/dist/entities/message.d.ts +11 -0
  21. package/dist/entities/message.js +11 -0
  22. package/dist/entities/store-records.d.ts +7 -0
  23. package/dist/services/agenda.d.ts +5 -0
  24. package/dist/services/agenda.js +25 -4
  25. package/dist/services/chat-coverage.d.ts +19 -0
  26. package/dist/services/chat-coverage.js +27 -0
  27. package/dist/services/messages-discovery.d.ts +6 -0
  28. package/dist/services/messages-discovery.js +50 -3
  29. package/dist/services/messages-search.d.ts +2 -0
  30. package/dist/services/messages.d.ts +2 -0
  31. package/dist/services/search-all.d.ts +3 -0
  32. package/dist/services/search-all.js +4 -0
  33. package/dist/store/messages.d.ts +9 -0
  34. package/dist/store/messages.js +2 -0
  35. package/dist/store/sqlite/follow-ups.d.ts +17 -0
  36. package/dist/store/sqlite/follow-ups.js +59 -0
  37. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,64 @@ Notable changes to `@wirecat/cli-core` (`@wirecat/cli-messaging` up to 0.261.0,
5
5
  minor version may break callers, and says how under "Changed — may break callers". `pnpm docs:check` checks the
6
6
  shape of this file.
7
7
 
8
+ ## Unreleased
9
+
10
+ ## 0.493.0 — 31.10.2026
11
+
12
+ ### Added
13
+
14
+ - **`chatCoverage(store, account, chatIds)` says what the store holds of each chat.** Per chat, in the order asked:
15
+ `completeness` (`complete`, `partial` or `unknown`, up to date, gaps, start reached, last fetch) and `held` (messages,
16
+ oldest, newest), never text. It reads only the chats named, so a store of thousands of chats costs what the ids cost.
17
+ `coverageGaps` also takes a list of these rows and answers `missing` (`field: "history"`) and `next`: for the first
18
+ incomplete chat, `messages sync <chat> --since-time <start of the window>` when the answer has a window, then
19
+ `messages sync <chat>`. The step `messages.sync` takes `since_time`.
20
+ - **`messages list --offline` says when the store holds only part of the chat.** A chat that is `partial` or was never
21
+ fetched gives `meta.complete: false`, exit code 0, `missing` and `next`, as an offline read of 262 of 777 messages
22
+ must not pass for the whole chat. Online reads are unchanged.
23
+ - **`tasks agenda` says how much of each listed chat the store holds.** Each row of `owed`, `waiting` and `unsynced` has
24
+ `completeness` and `held`; a chat held only in part is marked `[partial]` or `[never fetched]` in the text, with one
25
+ line above the lists. The answer then has `meta.complete: false`, `missing` and `next` (bounded sync over the
26
+ `--within` window, then the whole chat).
27
+ - **`search all` names the chat to sync** when the store holds the chats its messages were searched in only in part:
28
+ `missing` (`field: "coverage"`) and `next`, from the same coverage `search messages` reports. `SearchAllFound` gains
29
+ `coverage`.
30
+
31
+ ### Changed — may break callers
32
+
33
+ - **`tasks agenda --json` rows gain `completeness` and `held`**, and the answer may carry `meta.complete: false` with
34
+ `missing` and `next`; a caller that stops on anything but `meta.complete: true` now sees a store that holds chats in
35
+ part as incomplete. `tasks agenda` is declared `partial`, so the exit code stays 0. `AgendaChat` gains `coverage`
36
+ and `Agenda` gains `since`.
37
+ - **`search messages`, `search mail` and `search all` answer `meta.complete: false` when a chat searched is partial or
38
+ never fetched**, where they kept `true` and put the gap only in `missing` and `next`. Exit code stays 0 (the commands
39
+ are declared `partial`). A caller that read `meta.complete` as "no source failed" must read `missing` for the reason.
40
+ - **`messages list --offline --json` may carry `meta.complete: false`, `missing` and `next`** for a chat held in part.
41
+ `messages list` is declared `partial`: the exit code stays 0.
42
+
43
+ ## 0.492.0 — 31.10.2026
44
+
45
+ ### Added
46
+
47
+ - **`search messages --discover` shows follow-ups: later messages that may cancel or change a hit.** Each of the
48
+ first five hits gets up to two later messages, from any chat the search reads and passing its filters, that
49
+ share a word of three letters or more whose stem at most three messages of the store hold; latest first, a
50
+ message already shown above is not repeated. `items[].follow_ups` in `--json` and MCP (`ref`, `chat_title`,
51
+ `timestamp`, `sender_name`, `text`, all untrusted), `later, shares a rare word` lines in the terminal, and the
52
+ SDK's `query.discovery.followUps` counts them. The counts come from the stem index through a temporary `fts5vocab`
53
+ table, so the store does not change; while the stems are being built there are no follow-ups. On a benchmark
54
+ split written by an agent that never saw the method, cancelled decisions in the first three went from 7 to 11
55
+ of 15 and no answer was lost
56
+ ([evidence pipeline](https://github.com/WireCatLabs/cli-testing/tree/main/performance/search/evidence-pipeline)).
57
+
58
+ ### Fixed
59
+
60
+ - **`search messages --discover` no longer fails on a question with a time or a ratio.** "Do we meet until 22:00?"
61
+ or "a 1:1 with Anna" read the colon as a field filter and ended the whole search with `query.invalid-syntax`.
62
+ In a question, a colon between two digits now splits the numbers, as a space would. Explicit query syntax is
63
+ unchanged. A question with a bare number still runs as strict search until cli-core takes cli-base 0.66.1, where a
64
+ number counts as a word of the question.
65
+
8
66
  ## 0.491.0 — 31.10.2026
9
67
 
10
68
  ### Fixed
@@ -75,6 +75,13 @@ export declare const conversationShown: (account: AccountKey, { summary, message
75
75
  sender_name: string | null;
76
76
  text: string;
77
77
  }[] | undefined;
78
+ follow_ups?: {
79
+ ref: string;
80
+ chat_title: string | null;
81
+ timestamp: string;
82
+ sender_name: string | null;
83
+ text: string;
84
+ }[] | undefined;
78
85
  outgoing: boolean | null;
79
86
  attachments: {
80
87
  message?: string | undefined;
@@ -52,6 +52,13 @@ export declare const threadAnswer: ({ items, ...thread }: ThreadContext, reader:
52
52
  sender_name: string | null;
53
53
  text: string;
54
54
  }[] | undefined;
55
+ follow_ups?: {
56
+ ref: string;
57
+ chat_title: string | null;
58
+ timestamp: string;
59
+ sender_name: string | null;
60
+ text: string;
61
+ }[] | undefined;
55
62
  outgoing: boolean | null;
56
63
  attachments: {
57
64
  message?: string | undefined;
@@ -56,6 +56,13 @@ export declare const writtenAnswer: ({ operationId, sendId, message, messages, s
56
56
  sender_name: string | null;
57
57
  text: string;
58
58
  }[] | undefined;
59
+ follow_ups?: {
60
+ ref: string;
61
+ chat_title: string | null;
62
+ timestamp: string;
63
+ sender_name: string | null;
64
+ text: string;
65
+ }[] | undefined;
59
66
  outgoing: boolean | null;
60
67
  attachments: {
61
68
  message?: string | undefined;
@@ -268,6 +275,13 @@ export declare const writtenAnswer: ({ operationId, sendId, message, messages, s
268
275
  sender_name: string | null;
269
276
  text: string;
270
277
  }[] | undefined;
278
+ follow_ups?: {
279
+ ref: string;
280
+ chat_title: string | null;
281
+ timestamp: string;
282
+ sender_name: string | null;
283
+ text: string;
284
+ }[] | undefined;
271
285
  outgoing: boolean | null;
272
286
  attachments: {
273
287
  message?: string | undefined;
@@ -5,7 +5,9 @@ import { isLocator, parseLocator } from "../../domain/locator.js";
5
5
  import { SAVE_HELP } from "../../meetings/index.js";
6
6
  import { renderMessages } from "../../render/messages.js";
7
7
  import { renderThreadLinks } from "../../render/thread-context.js";
8
+ import { chatCoverage } from "../../services/chat-coverage.js";
8
9
  import { readMail } from "../../services/mail-search.js";
10
+ import { storedChatId } from "../../services/message-resolution.js";
9
11
  import { modelWith, spoken } from "../../speech/hearing.js";
10
12
  import { REMOTE_READ } from "../effects.js";
11
13
  import { itemOf, listOf, renderAnswer } from "../envelope.js";
@@ -30,6 +32,7 @@ import { pinCommand, starCommands, unpinCommand } from "./messages-pin-command.j
30
32
  import { pressCommand } from "./messages-press-command.js";
31
33
  import { scheduledCommand } from "./messages-scheduled-command.js";
32
34
  import { sendCommand } from "./messages-send-command.js";
35
+ import { coverageGaps } from "./search-answers.js";
33
36
  import { threadOptions, threadRequest } from "./thread-options.js";
34
37
  import { transcribeSubcommand } from "./transcribe-command.js";
35
38
  /** `messages`: reading, and sending through the guard. A CLI may add its own subcommands. */
@@ -40,6 +43,7 @@ export const messagesCommand = (messenger) => {
40
43
  const list = annotate(messages.command("list"), {
41
44
  examples: [`${messenger.app.command} personal messages list "Book club" --limit 20`],
42
45
  effects: REMOTE_READ,
46
+ partial: true,
43
47
  })
44
48
  .description("a chat's messages, oldest to newest")
45
49
  .addHelpText("after", SAVE_HELP)
@@ -68,6 +72,12 @@ export const messagesCommand = (messenger) => {
68
72
  const next = page.hasMore
69
73
  ? cursorOf(window, { first: page.items[0]?.id, last: page.items.at(-1)?.id })
70
74
  : undefined;
75
+ // Offline, the store is the whole answer: say how much of the chat it holds. Online the messenger is read.
76
+ const gaps = context.settings.offline
77
+ ? coverageGaps(await context.withStore(async (store, account) => chatCoverage(store, account, [
78
+ await storedChatId(messenger, chat, store, account, { includeLeft: true }),
79
+ ])))
80
+ : { next: [], missing: [] };
71
81
  renderAnswer(this, context, listOf("messages list", {
72
82
  items: messageItems(heardItems(page.items, hearing), reader),
73
83
  limit,
@@ -75,7 +85,9 @@ export const messagesCommand = (messenger) => {
75
85
  paging: "cursor",
76
86
  ...(next === undefined ? {} : { cursor: next }),
77
87
  args: { chat, ...(query.length > 0 ? { query: query.join(" ") } : {}) },
78
- missing: hearingMissing(hearing, transcribe === true),
88
+ next: gaps.next,
89
+ missing: [...hearingMissing(hearing, transcribe === true), ...gaps.missing],
90
+ ...(gaps.missing.length > 0 ? { meta: { complete: false } } : {}),
79
91
  }), () => renderMessages(spokenItems(page.items, hearing), viewOf(context, messenger)));
80
92
  });
81
93
  annotate(list, { output: { entity: "message" }, query: history });
@@ -174,7 +174,10 @@ const searchAction = (messenger) => async function (given) {
174
174
  const place = hit.matchedIn && hit.matchedIn !== "text" && "excerpt" in hit
175
175
  ? `\n found in the ${PLACES[hit.matchedIn]}: ${String(hit.excerpt)}`
176
176
  : "";
177
- return `${spans ? `${at.provider} · ${title}` : title} ${hit.locator}\n${renderMessages(hit.thread?.items ?? hit.context ?? [hit], options)}${place}${links ? `\n${links}` : ""}`;
177
+ const later = (hit.followUps ?? [])
178
+ .map((one) => `\n later, shares a rare word · ${one.chatTitle ?? one.chatId} ${one.locator}\n${renderMessages([one], options)}`)
179
+ .join("");
180
+ return `${spans ? `${at.provider} · ${title}` : title} ${hit.locator}\n${renderMessages(hit.thread?.items ?? hit.context ?? [hit], options)}${place}${later}${links ? `\n${links}` : ""}`;
178
181
  })
179
182
  .join("\n\n");
180
183
  const elsewhere = [...new Set(hits.map(({ at }) => at.provider))].filter((one) => one !== messenger.provider);
@@ -1,6 +1,7 @@
1
1
  import type { Missing, NextStep } from "@wirecat/cli-base/standards";
2
2
  import type { InferOutput } from "valibot";
3
3
  import type { ENTITIES } from "../../entities/index.js";
4
+ import type { ChatCoverage } from "../../services/chat-coverage.js";
4
5
  import type { FoundMessage, SearchFound } from "../../services/messages.js";
5
6
  import type { MeaningSkipped } from "../../services/messages-discovery.js";
6
7
  import type { FoundNote, NotesFoundOf } from "../../services/notes-search.js";
@@ -16,8 +17,11 @@ export type HitItem = InferOutput<(typeof ENTITIES)["hit"]>;
16
17
  /** The page a `--page` asks for, as the offset the search services take. */
17
18
  export declare const offsetOf: (page: number | undefined, limit: number) => number | undefined;
18
19
  export declare const messageHitItem: (hit: FoundMessage, query?: string) => MessageHitItem;
19
- /** What the archive lacked for this answer, and the step that would fill it. */
20
- export declare const coverageGaps: (found: Pick<SearchFound, "coverage" | "refreshed">, shown: number) => {
20
+ /**
21
+ * What the archive lacked for this answer, and the step that would fill it. From a search's coverage, or from the
22
+ * chats an answer is about (`since` is the start of the window it was asked for).
23
+ */
24
+ export declare const coverageGaps: (found: Pick<SearchFound, "coverage" | "refreshed"> | ChatCoverage[], shown?: number, since?: string) => {
21
25
  next: NextStep[];
22
26
  missing: Missing[];
23
27
  };
@@ -12,7 +12,16 @@ const defined = (value) => Object.fromEntries(Object.entries(value).filter(([, o
12
12
  /** The page a `--page` asks for, as the offset the search services take. */
13
13
  export const offsetOf = (page, limit) => page === undefined || page <= 1 ? undefined : (page - 1) * limit;
14
14
  /** Who wrote it and what, the chat's title, the mail's headers: others write them; the owner's own messages too. */
15
- const MESSAGE_HIT_UNTRUSTED = ["chat_title", "sender_name", "excerpt", "context.sender_name", "context.text"];
15
+ const MESSAGE_HIT_UNTRUSTED = [
16
+ "chat_title",
17
+ "sender_name",
18
+ "excerpt",
19
+ "context.sender_name",
20
+ "context.text",
21
+ "follow_ups.chat_title",
22
+ "follow_ups.sender_name",
23
+ "follow_ups.text",
24
+ ];
16
25
  const MAIL_HIT_UNTRUSTED = [
17
26
  "email.subject",
18
27
  "email.from.name",
@@ -67,20 +76,61 @@ export const messageHitItem = (hit, query = "") => {
67
76
  sender_name: one.senderName,
68
77
  text: one.text,
69
78
  })),
79
+ follow_ups: hit.followUps?.map((one) => ({
80
+ ref: one.locator,
81
+ chat_title: one.chatTitle ?? null,
82
+ timestamp: one.timestamp,
83
+ sender_name: one.senderName,
84
+ text: one.text,
85
+ })),
70
86
  untrusted: hit.email ? [...MESSAGE_HIT_UNTRUSTED, ...MAIL_HIT_UNTRUSTED] : MESSAGE_HIT_UNTRUSTED,
71
87
  });
72
88
  };
73
89
  const accountName = ({ provider, account }) => `${provider}:${account}`;
74
90
  /** A source left out is an issue by its code; the sentence saying why goes to a person as a note, never here. */
75
- const metaOf = (accounts, skipped = []) => ({
91
+ const metaOf = (accounts, skipped = [], heldInPart = false) => ({
76
92
  accounts: accounts.map(accountName),
77
- complete: skipped.length === 0,
93
+ complete: skipped.length === 0 && !heldInPart,
78
94
  ...(skipped.length
79
95
  ? { issues: skipped.map(({ source, resource, code }) => ({ code, source: source ?? resource ?? "" })) }
80
96
  : {}),
81
97
  });
82
- /** What the archive lacked for this answer, and the step that would fill it. */
83
- export const coverageGaps = (found, shown) => {
98
+ const day = (at) => at?.slice(0, 10) ?? "no date";
99
+ /**
100
+ * The steps for chats the store holds only in part. Bounded first, when the answer's window is known, then the whole
101
+ * chat; both name the first chat only, because a download is the owner's call. Counts and dates only, never text.
102
+ */
103
+ const chatGaps = (rows, since) => {
104
+ const open = rows.filter(({ completeness }) => completeness.state !== "complete");
105
+ const [first] = open;
106
+ if (!first)
107
+ return { next: [], missing: [] };
108
+ const held = first.completeness.state === "unknown" ? "never fetched" : `held from ${day(first.held.oldestAt)} only`;
109
+ const steps = (args, why) => ({
110
+ ...nextStep("messages.sync", { args: { chat: first.chatId, ...args } }),
111
+ why,
112
+ });
113
+ return {
114
+ next: [
115
+ ...(since === undefined ? [] : [steps({ since_time: since }, `${held}; this reads back to ${day(since)}`)]),
116
+ steps({}, "reads the chat back to its first message; a big group can take minutes"),
117
+ ],
118
+ missing: [
119
+ {
120
+ field: "history",
121
+ why: `${open.length} of ${rows.length} chats hold only part of their history; the first is ${held}`,
122
+ step: "messages.sync",
123
+ },
124
+ ],
125
+ };
126
+ };
127
+ /**
128
+ * What the archive lacked for this answer, and the step that would fill it. From a search's coverage, or from the
129
+ * chats an answer is about (`since` is the start of the window it was asked for).
130
+ */
131
+ export const coverageGaps = (found, shown = 0, since) => {
132
+ if (Array.isArray(found))
133
+ return chatGaps(found, since);
84
134
  const coverage = found.coverage;
85
135
  const stale = found.refreshed?.complete === false;
86
136
  if (!coverage?.next && !stale)
@@ -187,7 +237,7 @@ export const messagesAnswer = (command, found, { limit, page, again }, query = "
187
237
  page: page ?? 1,
188
238
  ...(again ? { args: again } : {}),
189
239
  ...gapsOf(found),
190
- meta: metaOf(accounts, found.skipped),
240
+ meta: metaOf(accounts, found.skipped, coverageGaps(found).missing.length > 0),
191
241
  });
192
242
  };
193
243
  /**
@@ -256,6 +306,7 @@ export const allAnswer = (command, found, { limit, page, again }, exact = false)
256
306
  const scan = found.meetings?.complete === false ? found.meetings : undefined;
257
307
  const notes = meaningGap(found.notes?.meaningSkipped, exact);
258
308
  const messages = messageMeaningGap(found.messages?.meaningSkipped, "found_by");
309
+ const archive = coverageGaps({ coverage: found.coverage }, found.items.length);
259
310
  return listOf(command, {
260
311
  items: found.items.map(allHitItem),
261
312
  limit,
@@ -274,6 +325,7 @@ export const allAnswer = (command, found, { limit, page, again }, exact = false)
274
325
  : []),
275
326
  ...notes.next,
276
327
  ...messages.next.filter((step) => !notes.next.some((asked) => JSON.stringify(asked) === JSON.stringify(step))),
328
+ ...archive.next,
277
329
  ],
278
330
  missing: [
279
331
  ...(scan
@@ -287,7 +339,8 @@ export const allAnswer = (command, found, { limit, page, again }, exact = false)
287
339
  : []),
288
340
  ...notes.missing,
289
341
  ...messages.missing,
342
+ ...archive.missing,
290
343
  ],
291
- meta: metaOf(found.accounts, found.skipped),
344
+ meta: metaOf(found.accounts, found.skipped, archive.missing.length > 0),
292
345
  });
293
346
  };
@@ -15,6 +15,7 @@ import { itemOf, listOf, renderAnswer, snakeKeys } from "../envelope.js";
15
15
  import { listQuery, pagedList, pageOf, queryTerms, typedQuery, withQuery } from "../list-query.js";
16
16
  import { positiveCount } from "../paging.js";
17
17
  import { messengerContext, refuseLocalWrite } from "./context.js";
18
+ import { coverageGaps } from "./search-answers.js";
18
19
  import { refuseJsonl, taskStatsAnswer } from "./stats-answers.js";
19
20
  const line = ({ id, key, state, kind, group, createdAt, message, note }) => [
20
21
  key ?? id.slice(0, 8),
@@ -50,10 +51,16 @@ const applyText = (pass, yes, tool) => yes
50
51
  ...pass.tasks.map((task) => ` ${task.kind.padEnd(8)} ${(task.category ?? "—").padEnd(13)} ${task.source}`),
51
52
  ...(pass.tasks.length > 0 ? [` ${tool} tasks scan ${pass.chat} --yes opens them`] : []),
52
53
  ].join("\n");
54
+ const agendaChats = (agenda) => [...agenda.unsynced, ...agenda.owed, ...agenda.waiting];
55
+ const agendaGaps = (agenda) => coverageGaps(agendaChats(agenda).map(({ coverage }) => coverage), 0, agenda.since);
56
+ const STATE_MARK = { complete: "", partial: " [partial]", unknown: " [never fetched]" };
53
57
  const agendaText = (agenda) => {
54
58
  const chat = (one) => ` ${when(one.lastMessageAt)} ${one.title ?? one.chatId} ${one.lastMessage ?? one.chatId}` +
55
- (one.openTasks.length > 0 ? ` (${one.openTasks.length} open)` : "");
59
+ (one.openTasks.length > 0 ? ` (${one.openTasks.length} open)` : "") +
60
+ STATE_MARK[one.coverage.completeness.state];
61
+ const [history] = agendaGaps(agenda).missing;
56
62
  return [
63
+ ...(history ? [`${history.why}: sync before trusting what is listed`] : []),
57
64
  ...(agenda.stale
58
65
  ? [`the store saved nothing since ${when(agenda.storedAt)}: sync before trusting quiet chats`]
59
66
  : []),
@@ -356,7 +363,11 @@ export const tasksCommand = (messenger) => {
356
363
  const unmerged = await context.withServices((services) => services.tasks.unmerge(dup, writerOf(this)));
357
364
  renderAnswer(this, context, itemOf(snakeKeys(unmerged)), () => `${card(unmerged.task)}\n`);
358
365
  });
359
- withQuery(annotate(tasks.command("agenda"), { effects: LOCAL_READ, examples: [`${tool} personal tasks agenda`] })
366
+ withQuery(annotate(tasks.command("agenda"), {
367
+ effects: LOCAL_READ,
368
+ partial: true,
369
+ examples: [`${tool} personal tasks agenda`],
370
+ })
360
371
  .summary("what needs a look today: tasks due, chats you owe or wait on, what you are owed")
361
372
  .description("what needs a look today, from the local store and without message text: tasks overdue, due or to check, " +
362
373
  "chats where you owe an answer, chats where you wait for one, open tasks someone else does"), AGENDA_QUERY)
@@ -377,7 +388,8 @@ export const tasksCommand = (messenger) => {
377
388
  ...(query ? { query } : {}),
378
389
  holder: holderOf(this),
379
390
  }));
380
- renderAnswer(this, context, itemOf(agendaItem(agenda)), () => `${agendaText(agenda)}\n`);
391
+ const { next, missing } = agendaGaps(agenda);
392
+ renderAnswer(this, context, itemOf(agendaItem(agenda), { next, missing, ...(missing.length > 0 ? { meta: { complete: false } } : {}) }), () => `${agendaText(agenda)}\n`);
381
393
  });
382
394
  annotate(tasks.command("categories"), {
383
395
  output: { entity: "task_categories" },
package/dist/cli/steps.js CHANGED
@@ -14,6 +14,7 @@ export const STEPS = defineSteps([
14
14
  args: [
15
15
  { name: "chat", required: false },
16
16
  { name: "all", required: false },
17
+ { name: "since_time", required: false },
17
18
  { name: "background", required: false },
18
19
  ],
19
20
  why: "the local copy is missing messages; syncing brings them in",
@@ -42,6 +42,20 @@ export declare const agenda: v.StrictObjectSchema<{
42
42
  readonly last_from_owner: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
43
43
  /** Open tasks whose source is in this chat. */
44
44
  readonly open_tasks: v.ArraySchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>, undefined>;
45
+ /** `unknown` means nothing ever fetched the chat's history; `partial` that the store holds only part of it. */
46
+ readonly completeness: v.StrictObjectSchema<{
47
+ readonly state: v.PicklistSchema<["complete", "partial", "unknown"], undefined>;
48
+ readonly up_to_date: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
49
+ readonly gaps: v.BooleanSchema<undefined>;
50
+ readonly reaches_start: v.BooleanSchema<undefined>;
51
+ readonly fetched_at: v.NullableSchema<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`">]>, undefined>;
52
+ }, undefined>;
53
+ /** What the store holds of the chat: the count and the span, never the text. */
54
+ readonly held: v.StrictObjectSchema<{
55
+ readonly messages: v.NumberSchema<undefined>;
56
+ readonly oldest_at: v.NullableSchema<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`">]>, undefined>;
57
+ readonly newest_at: v.NullableSchema<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`">]>, undefined>;
58
+ }, undefined>;
45
59
  }, undefined>, undefined>;
46
60
  /** The owner wrote last, and nobody answered. */
47
61
  readonly waiting: v.ArraySchema<v.StrictObjectSchema<{
@@ -55,6 +69,20 @@ export declare const agenda: v.StrictObjectSchema<{
55
69
  readonly last_from_owner: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
56
70
  /** Open tasks whose source is in this chat. */
57
71
  readonly open_tasks: v.ArraySchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>, undefined>;
72
+ /** `unknown` means nothing ever fetched the chat's history; `partial` that the store holds only part of it. */
73
+ readonly completeness: v.StrictObjectSchema<{
74
+ readonly state: v.PicklistSchema<["complete", "partial", "unknown"], undefined>;
75
+ readonly up_to_date: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
76
+ readonly gaps: v.BooleanSchema<undefined>;
77
+ readonly reaches_start: v.BooleanSchema<undefined>;
78
+ readonly fetched_at: v.NullableSchema<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`">]>, undefined>;
79
+ }, undefined>;
80
+ /** What the store holds of the chat: the count and the span, never the text. */
81
+ readonly held: v.StrictObjectSchema<{
82
+ readonly messages: v.NumberSchema<undefined>;
83
+ readonly oldest_at: v.NullableSchema<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`">]>, undefined>;
84
+ readonly newest_at: v.NullableSchema<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`">]>, undefined>;
85
+ }, undefined>;
58
86
  }, undefined>, undefined>;
59
87
  /** The chat list says these moved and their newest message is not stored: read them first. */
60
88
  readonly unsynced: v.ArraySchema<v.StrictObjectSchema<{
@@ -68,6 +96,20 @@ export declare const agenda: v.StrictObjectSchema<{
68
96
  readonly last_from_owner: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
69
97
  /** Open tasks whose source is in this chat. */
70
98
  readonly open_tasks: v.ArraySchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>]>, undefined>;
99
+ /** `unknown` means nothing ever fetched the chat's history; `partial` that the store holds only part of it. */
100
+ readonly completeness: v.StrictObjectSchema<{
101
+ readonly state: v.PicklistSchema<["complete", "partial", "unknown"], undefined>;
102
+ readonly up_to_date: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
103
+ readonly gaps: v.BooleanSchema<undefined>;
104
+ readonly reaches_start: v.BooleanSchema<undefined>;
105
+ readonly fetched_at: v.NullableSchema<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`">]>, undefined>;
106
+ }, undefined>;
107
+ /** What the store holds of the chat: the count and the span, never the text. */
108
+ readonly held: v.StrictObjectSchema<{
109
+ readonly messages: v.NumberSchema<undefined>;
110
+ readonly oldest_at: v.NullableSchema<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`">]>, undefined>;
111
+ readonly newest_at: v.NullableSchema<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`">]>, undefined>;
112
+ }, undefined>;
71
113
  }, undefined>, undefined>;
72
114
  /** Open tasks someone else does: what they owe the owner, oldest first. */
73
115
  readonly assigned: v.ArraySchema<v.StrictObjectSchema<{
@@ -13,6 +13,16 @@ const agendaChat = v.strictObject({
13
13
  last_from_owner: v.nullable(v.boolean()),
14
14
  /** Open tasks whose source is in this chat. */
15
15
  open_tasks: v.array(id),
16
+ /** `unknown` means nothing ever fetched the chat's history; `partial` that the store holds only part of it. */
17
+ completeness: v.strictObject({
18
+ state: v.picklist(["complete", "partial", "unknown"]),
19
+ up_to_date: v.nullable(v.boolean()),
20
+ gaps: v.boolean(),
21
+ reaches_start: v.boolean(),
22
+ fetched_at: v.nullable(time),
23
+ }),
24
+ /** What the store holds of the chat: the count and the span, never the text. */
25
+ held: v.strictObject({ messages: v.number(), oldest_at: v.nullable(time), newest_at: v.nullable(time) }),
16
26
  });
17
27
  /** What needs a look today, from the store alone and without one message's text. */
18
28
  export const agenda = v.strictObject({
@@ -51,6 +51,13 @@ export declare const conversationDetails: v.UnionSchema<[v.StrictObjectSchema<{
51
51
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
52
52
  readonly text: v.StringSchema<undefined>;
53
53
  }, undefined>, undefined>, undefined>;
54
+ readonly follow_ups: v.OptionalSchema<v.ArraySchema<v.StrictObjectSchema<{
55
+ readonly ref: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
56
+ readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
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
+ readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
59
+ readonly text: v.StringSchema<undefined>;
60
+ }, undefined>, undefined>, undefined>;
54
61
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
55
62
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
56
63
  readonly message: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
@@ -261,6 +268,13 @@ export declare const conversationDetails: v.UnionSchema<[v.StrictObjectSchema<{
261
268
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
262
269
  readonly text: v.StringSchema<undefined>;
263
270
  }, undefined>, undefined>, undefined>;
271
+ readonly follow_ups: v.OptionalSchema<v.ArraySchema<v.StrictObjectSchema<{
272
+ readonly ref: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
273
+ readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
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
+ readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
276
+ readonly text: v.StringSchema<undefined>;
277
+ }, undefined>, undefined>, undefined>;
264
278
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
265
279
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
266
280
  readonly message: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
@@ -42,6 +42,13 @@ export declare const contribution: v.StrictObjectSchema<{
42
42
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
43
43
  readonly text: v.StringSchema<undefined>;
44
44
  }, undefined>, undefined>, undefined>;
45
+ readonly follow_ups: v.OptionalSchema<v.ArraySchema<v.StrictObjectSchema<{
46
+ readonly ref: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
47
+ readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
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
+ readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
50
+ readonly text: v.StringSchema<undefined>;
51
+ }, undefined>, undefined>, undefined>;
45
52
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
46
53
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
47
54
  readonly message: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
@@ -252,6 +259,13 @@ export declare const contribution: v.StrictObjectSchema<{
252
259
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
253
260
  readonly text: v.StringSchema<undefined>;
254
261
  }, undefined>, undefined>, undefined>;
262
+ readonly follow_ups: v.OptionalSchema<v.ArraySchema<v.StrictObjectSchema<{
263
+ readonly ref: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
264
+ readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
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
+ readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
267
+ readonly text: v.StringSchema<undefined>;
268
+ }, undefined>, undefined>, undefined>;
255
269
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
256
270
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
257
271
  readonly message: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
@@ -500,6 +514,13 @@ export declare const contribution: v.StrictObjectSchema<{
500
514
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
501
515
  readonly text: v.StringSchema<undefined>;
502
516
  }, undefined>, undefined>, undefined>;
517
+ readonly follow_ups: v.OptionalSchema<v.ArraySchema<v.StrictObjectSchema<{
518
+ readonly ref: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
519
+ readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
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
+ readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
522
+ readonly text: v.StringSchema<undefined>;
523
+ }, undefined>, undefined>, undefined>;
503
524
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
504
525
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
505
526
  readonly message: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
@@ -710,6 +731,13 @@ export declare const contribution: v.StrictObjectSchema<{
710
731
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
711
732
  readonly text: v.StringSchema<undefined>;
712
733
  }, undefined>, undefined>, undefined>;
734
+ readonly follow_ups: v.OptionalSchema<v.ArraySchema<v.StrictObjectSchema<{
735
+ readonly ref: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
736
+ readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
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
+ readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
739
+ readonly text: v.StringSchema<undefined>;
740
+ }, undefined>, undefined>, undefined>;
713
741
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
714
742
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
715
743
  readonly message: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
@@ -994,6 +1022,13 @@ export declare const evidence: v.UnionSchema<[v.StrictObjectSchema<{
994
1022
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
995
1023
  readonly text: v.StringSchema<undefined>;
996
1024
  }, undefined>, undefined>, undefined>;
1025
+ readonly follow_ups: v.OptionalSchema<v.ArraySchema<v.StrictObjectSchema<{
1026
+ readonly ref: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
1027
+ readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
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
+ readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
1030
+ readonly text: v.StringSchema<undefined>;
1031
+ }, undefined>, undefined>, undefined>;
997
1032
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
998
1033
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
999
1034
  readonly message: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
@@ -1204,6 +1239,13 @@ export declare const evidence: v.UnionSchema<[v.StrictObjectSchema<{
1204
1239
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
1205
1240
  readonly text: v.StringSchema<undefined>;
1206
1241
  }, undefined>, undefined>, undefined>;
1242
+ readonly follow_ups: v.OptionalSchema<v.ArraySchema<v.StrictObjectSchema<{
1243
+ readonly ref: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
1244
+ readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
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
+ readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
1247
+ readonly text: v.StringSchema<undefined>;
1248
+ }, undefined>, undefined>, undefined>;
1207
1249
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
1208
1250
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
1209
1251
  readonly message: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
@@ -1452,6 +1494,13 @@ export declare const evidence: v.UnionSchema<[v.StrictObjectSchema<{
1452
1494
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
1453
1495
  readonly text: v.StringSchema<undefined>;
1454
1496
  }, undefined>, undefined>, undefined>;
1497
+ readonly follow_ups: v.OptionalSchema<v.ArraySchema<v.StrictObjectSchema<{
1498
+ readonly ref: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
1499
+ readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
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
+ readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
1502
+ readonly text: v.StringSchema<undefined>;
1503
+ }, undefined>, undefined>, undefined>;
1455
1504
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
1456
1505
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
1457
1506
  readonly message: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;
@@ -1662,6 +1711,13 @@ export declare const evidence: v.UnionSchema<[v.StrictObjectSchema<{
1662
1711
  readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
1663
1712
  readonly text: v.StringSchema<undefined>;
1664
1713
  }, undefined>, undefined>, undefined>;
1714
+ readonly follow_ups: v.OptionalSchema<v.ArraySchema<v.StrictObjectSchema<{
1715
+ readonly ref: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>;
1716
+ readonly chat_title: v.NullableSchema<v.StringSchema<undefined>, undefined>;
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
+ readonly sender_name: v.NullableSchema<v.StringSchema<undefined>, undefined>;
1719
+ readonly text: v.StringSchema<undefined>;
1720
+ }, undefined>, undefined>, undefined>;
1665
1721
  readonly outgoing: v.NullableSchema<v.BooleanSchema<undefined>, undefined>;
1666
1722
  readonly attachments: v.ArraySchema<v.StrictObjectSchema<{
1667
1723
  readonly message: v.OptionalSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.RegexAction<string, "a reference such as `note:42`">]>, undefined>;