@wirecat/cli-core 0.559.0 → 0.561.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 (89) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/dist/background/schedules.d.ts +2 -0
  3. package/dist/calendar/busy.d.ts +11 -0
  4. package/dist/calendar/busy.js +14 -1
  5. package/dist/calendar/cli/index.d.ts +9 -3
  6. package/dist/calendar/cli/index.js +14 -7
  7. package/dist/calendar/cli/local-times.d.ts +5 -0
  8. package/dist/calendar/cli/local-times.js +39 -0
  9. package/dist/calendar/mcp/index.js +1 -1
  10. package/dist/calendar/reads.d.ts +11 -2
  11. package/dist/calendar/reads.js +34 -18
  12. package/dist/calendar/writes.js +3 -44
  13. package/dist/cli/effects.d.ts +13 -2
  14. package/dist/cli/effects.js +23 -2
  15. package/dist/cli/index.d.ts +2 -2
  16. package/dist/cli/index.js +2 -2
  17. package/dist/cli/meeting-findings-input.d.ts +3 -0
  18. package/dist/cli/meeting-findings-input.js +4 -0
  19. package/dist/cli/messenger/conversation-answers.d.ts +3 -0
  20. package/dist/cli/messenger/message-answers.d.ts +3 -0
  21. package/dist/cli/messenger/message-writes.d.ts +6 -0
  22. package/dist/cli/schedule/schedule-command.d.ts +6 -1
  23. package/dist/cli/schedule/schedule-command.js +24 -2
  24. package/dist/entities/contact-context.d.ts +6 -0
  25. package/dist/entities/conversation-results.d.ts +6 -0
  26. package/dist/entities/evidence.d.ts +24 -0
  27. package/dist/entities/index.d.ts +190 -0
  28. package/dist/entities/index.js +3 -0
  29. package/dist/entities/listener.d.ts +12 -0
  30. package/dist/entities/mail-findings.d.ts +116 -0
  31. package/dist/entities/mail-findings.js +68 -0
  32. package/dist/entities/message-context.d.ts +9 -0
  33. package/dist/entities/message-write.d.ts +12 -0
  34. package/dist/entities/message.d.ts +6 -0
  35. package/dist/entities/message.js +6 -0
  36. package/dist/entities/store-records.d.ts +3 -0
  37. package/dist/errors/reasons.js +3 -0
  38. package/dist/mail/cli/marks.d.ts +1 -0
  39. package/dist/mail/cli/marks.js +11 -4
  40. package/dist/mail/cli/review.js +10 -2
  41. package/dist/mail/findings.d.ts +55 -0
  42. package/dist/mail/findings.js +140 -0
  43. package/dist/mail/index.d.ts +2 -1
  44. package/dist/mail/index.js +2 -1
  45. package/dist/mail/marks.d.ts +10 -3
  46. package/dist/mail/marks.js +34 -5
  47. package/dist/meetings/findings-rules.d.ts +24 -0
  48. package/dist/meetings/findings-rules.js +136 -116
  49. package/dist/meetings/index.d.ts +2 -1
  50. package/dist/meetings/index.js +2 -1
  51. package/dist/sends/index.d.ts +1 -0
  52. package/dist/sends/index.js +1 -0
  53. package/dist/sends/keyed-guard.d.ts +12 -0
  54. package/dist/sends/keyed-guard.js +49 -0
  55. package/dist/services/proposals/kinds/mail-findings.d.ts +8 -0
  56. package/dist/services/proposals/kinds/mail-findings.js +87 -0
  57. package/dist/services/proposals/registry.js +3 -1
  58. package/dist/services/proposals/types.d.ts +2 -0
  59. package/dist/store/decisions.d.ts +1 -1
  60. package/dist/store/decisions.js +1 -1
  61. package/dist/store/index.d.ts +1 -0
  62. package/dist/store/index.js +1 -0
  63. package/dist/store/mail-findings.d.ts +51 -0
  64. package/dist/store/mail-findings.js +1 -0
  65. package/dist/store/path.d.ts +2 -0
  66. package/dist/store/path.js +21 -1
  67. package/dist/store/sqlite/email-activity.d.ts +8 -0
  68. package/dist/store/sqlite/email-activity.js +31 -5
  69. package/dist/store/sqlite/findings-core.d.ts +1 -0
  70. package/dist/store/sqlite/findings-core.js +1 -1
  71. package/dist/store/sqlite/mail-findings.d.ts +3 -0
  72. package/dist/store/sqlite/mail-findings.js +333 -0
  73. package/dist/store/sqlite/manifest.js +1 -0
  74. package/dist/store/sqlite/memories.d.ts +4 -1
  75. package/dist/store/sqlite/memories.js +48 -43
  76. package/dist/store/sqlite/migrations.generated.js +6 -0
  77. package/dist/store/sqlite/open-store.d.ts +1 -1
  78. package/dist/store/sqlite/open-store.js +4 -1
  79. package/dist/store/sqlite/open.js +2 -1
  80. package/dist/store/sqlite/proposed-actions.js +2 -1
  81. package/dist/store/sqlite/tasks.d.ts +2 -0
  82. package/dist/store/sqlite/tasks.js +26 -24
  83. package/dist/store/sqlite/test-accounts.d.ts +8 -0
  84. package/dist/store/sqlite/test-accounts.js +31 -0
  85. package/dist/store/sqlite/work-tasks.d.ts +1 -0
  86. package/dist/store/sqlite/work-tasks.js +6 -0
  87. package/dist/store/store.d.ts +3 -0
  88. package/dist/store/store.js +5 -0
  89. package/package.json +1 -1
@@ -4,7 +4,7 @@ import { CliError } from "@wirecat/cli-base";
4
4
  import { DEFAULT_DECISION_POLICY } from "../decisions.js";
5
5
  import { migrate } from "../migrations.js";
6
6
  import { storeCapable } from "../open.js";
7
- import { storePath } from "../path.js";
7
+ import { refuseOwnerStoreInTests, storePath } from "../path.js";
8
8
  import { sharedConfigPath } from "../shared-config.js";
9
9
  import { backfillNormalized, pendingNormalization } from "./backfill.js";
10
10
  import { fillFiles } from "./files.js";
@@ -18,6 +18,7 @@ import { assertReadCompatible } from "./read-compatibility.js";
18
18
  import { stemsState } from "./stems.js";
19
19
  import { fillStems, followDefaultStemmers } from "./stems-fill.js";
20
20
  import { moveSuggestedLinks } from "./suggested-links.js";
21
+ import { purgeTestAccounts } from "./test-accounts.js";
21
22
  import { fillVectorIndexes } from "./vector-index.js";
22
23
  export const BACKFILL_ON_OPEN = 5_000;
23
24
  const NOT_WRITABLE = new Set(["EACCES", "EPERM", "EROFS"]);
@@ -39,6 +40,7 @@ export const openStoreContext = async ({ path, env, now = Date.now, command, ver
39
40
  throw new CliError("validation_error", "readOnly must be a boolean");
40
41
  await storeCapable();
41
42
  const file = path ?? storePath(env);
43
+ refuseOwnerStoreInTests(file);
42
44
  if (readOnly) {
43
45
  if (!statSync(file, { throwIfNoEntry: false })?.isFile())
44
46
  throw new CliError("not_found", "The store does not exist as a file; read-only opening does not create it");
@@ -59,6 +61,7 @@ export const openStoreContext = async ({ path, env, now = Date.now, command, ver
59
61
  fillJoinKeys(database);
60
62
  moveSuggestedLinks(database, now);
61
63
  movePaymentFacts({ ...opened, now, decisions });
64
+ purgeTestAccounts({ ...opened, now, decisions }, warn);
62
65
  const pending = pendingNormalization(database);
63
66
  if (pending > 0 && pending <= BACKFILL_ON_OPEN)
64
67
  backfillNormalized(database);
@@ -1,5 +1,5 @@
1
- // biome-ignore-all lint/plugin/no-sqlite-driver: cli-core owns the store, so it opens the driver itself
2
1
  import { PRAGMAS } from "../driver.js";
2
+ import { refuseOwnerStoreInTests } from "../path.js";
3
3
  import { quietSqliteWarning } from "../sqlite-warning.js";
4
4
  /**
5
5
  * One SQLite connection, seen two ways: the `CacheDatabase` seam the hand-written SQL and the
@@ -8,6 +8,7 @@ import { quietSqliteWarning } from "../sqlite-warning.js";
8
8
  * `../open.ts` for why a static import breaks the other runtime.
9
9
  */
10
10
  export const openSqlite = async (path, { readOnly = false } = {}) => {
11
+ refuseOwnerStoreInTests(path);
11
12
  const opened = "Bun" in globalThis ? await openUnderBun(path, readOnly) : await openUnderNode(path, readOnly);
12
13
  try {
13
14
  opened.database.exec(readOnly ? "PRAGMA busy_timeout=5000; PRAGMA foreign_keys=ON; PRAGMA query_only=ON" : PRAGMAS);
@@ -2,6 +2,7 @@ import { CliError } from "@wirecat/cli-base";
2
2
  import { MEETING_FINDINGS_KIND } from "../../meetings/index.js";
3
3
  import { assertMayDecide, deciderAuthor } from "../decisions.js";
4
4
  import { EVIDENCE_FINDINGS_KIND } from "../evidence-findings.js";
5
+ import { MAIL_FINDINGS_KIND } from "../mail-findings.js";
5
6
  import { proposedBySql } from "./actors.js";
6
7
  import { authorActor } from "./memories.js";
7
8
  import { requiredThing } from "./notes.js";
@@ -15,7 +16,7 @@ const json = (value) => (value == null ? null : JSON.parse(String(value)));
15
16
  * A findings batch closed with nothing accepted is stored `approved`, as older builds read it; it reads as
16
17
  * `rejected`, the decision the owner made.
17
18
  */
18
- const STATUS = `CASE WHEN p.status = 'approved' AND p.kind IN ('${MEETING_FINDINGS_KIND}', '${EVIDENCE_FINDINGS_KIND}') ` +
19
+ const STATUS = `CASE WHEN p.status = 'approved' AND p.kind IN ('${MEETING_FINDINGS_KIND}', '${EVIDENCE_FINDINGS_KIND}', '${MAIL_FINDINGS_KIND}') ` +
19
20
  "AND json_array_length(p.result, '$.accepted') = 0 THEN 'rejected' ELSE p.status END";
20
21
  const NAMED = `SELECT p.*, ${STATUS} AS shown_status, b.name AS proposer_name, b.kind AS proposer_kind FROM proposed_actions p ` +
21
22
  "LEFT JOIN bots b ON p.proposed_by_type = 'bot' AND b.id = p.proposed_by_id";
@@ -36,4 +36,6 @@ export interface StoreTaskStore extends Omit<Required<TaskStore>, "reviewDuplica
36
36
  closeSuggestion: CloseSuggestion | null;
37
37
  }[]>;
38
38
  }
39
+ /** The inbox project of a task account (`telegram:100`), made the first time `create` asks for it. */
40
+ export declare const inboxProject: ({ database, now }: Pick<StoreContext, "database" | "now">, account: string, create: boolean) => number | undefined;
39
41
  export declare const taskStoreOver: (context: StoreContext) => StoreTaskStore;
@@ -18,32 +18,34 @@ export { inboxKey, linkInbox, taskAccountOf } from "./task-keys.js";
18
18
  export { taskIdOf, taskRowOf } from "./things.js";
19
19
  const COLUMNS = "t.id AS row_id, t.package_id, t.source_locator, t.source_kind, t.source_group, t.type, t.status, t.close_reason, t.source, t.created_at, t.due_at, t.closed_at, " +
20
20
  "t.closed_by_type, t.closed_by_id, t.key, t.category, t.claimed_by, t.claimed_until, p.name AS account";
21
+ /** The inbox project of a task account (`telegram:100`), made the first time `create` asks for it. */
22
+ export const inboxProject = ({ database, now }, account, create) => {
23
+ const key = inboxKey(account);
24
+ const found = database.prepare("SELECT id FROM projects WHERE key = ?").get(key);
25
+ if (!create)
26
+ return found ? Number(found.id) : undefined;
27
+ const [provider, external] = [account.slice(0, account.indexOf(":")), account.slice(account.indexOf(":") + 1)];
28
+ const owning = database
29
+ .prepare("SELECT id FROM accounts WHERE provider = ? AND external_id = ?")
30
+ .get(provider, external);
31
+ const accountId = owning ? Number(owning.id) : null;
32
+ // A task can arrive before its messenger saved the account; the project learns it on a later task.
33
+ if (found) {
34
+ if (accountId !== null)
35
+ database
36
+ .prepare("UPDATE projects SET account_id = ? WHERE id = ? AND account_id IS NULL")
37
+ .run(accountId, Number(found.id));
38
+ return Number(found.id);
39
+ }
40
+ const at = now();
41
+ return Number(database
42
+ .prepare("INSERT INTO projects (key, name, name_folded, description, type, account_id, scope, tasks_count, status, created_at, updated_at) " +
43
+ "VALUES (?, ?, ?, ?, 'personal', ?, 'personal', 0, 'active', ?, ?) RETURNING id")
44
+ .get(key, account, folded(account), `What waits on the owner in ${account}`, accountId, at, at)?.id);
45
+ };
21
46
  export const taskStoreOver = (context) => {
22
47
  const { database, now } = context;
23
- const inbox = (account, create) => {
24
- const key = inboxKey(account);
25
- const found = database.prepare("SELECT id FROM projects WHERE key = ?").get(key);
26
- if (!create)
27
- return found ? Number(found.id) : undefined;
28
- const [provider, external] = [account.slice(0, account.indexOf(":")), account.slice(account.indexOf(":") + 1)];
29
- const owning = database
30
- .prepare("SELECT id FROM accounts WHERE provider = ? AND external_id = ?")
31
- .get(provider, external);
32
- const accountId = owning ? Number(owning.id) : null;
33
- // A task can arrive before its messenger saved the account; the project learns it on a later task.
34
- if (found) {
35
- if (accountId !== null)
36
- database
37
- .prepare("UPDATE projects SET account_id = ? WHERE id = ? AND account_id IS NULL")
38
- .run(accountId, Number(found.id));
39
- return Number(found.id);
40
- }
41
- const at = now();
42
- return Number(database
43
- .prepare("INSERT INTO projects (key, name, name_folded, description, type, account_id, scope, tasks_count, status, created_at, updated_at) " +
44
- "VALUES (?, ?, ?, ?, 'personal', ?, 'personal', 0, 'active', ?, ?) RETURNING id")
45
- .get(key, account, folded(account), `What waits on the owner in ${account}`, accountId, at, at)?.id);
46
- };
48
+ const inbox = (account, create) => inboxProject(context, account, create);
47
49
  // A task made here has no package id and may be a type the package does not know; the package never sees it.
48
50
  const select = (where) => database.prepare(`SELECT ${COLUMNS} FROM tasks t JOIN projects p ON p.id = t.project_id WHERE t.package_id IS NOT NULL AND ${where}`);
49
51
  const toTask = (row) => {
@@ -0,0 +1,8 @@
1
+ import type { StoreContext } from "./open.js";
2
+ /** Set by store version 48 and deleted once the purge ran. */
3
+ export declare const TEST_ACCOUNTS_TO_PURGE = "test_accounts_to_purge";
4
+ /**
5
+ * Removes every account of the test provider, as `cache clear` removes one. A failure keeps the key for the next open
6
+ * and only warns: a leftover test account must never stop the owner's store from opening.
7
+ */
8
+ export declare const purgeTestAccounts: (context: StoreContext, warn: (line: string) => void) => number;
@@ -0,0 +1,31 @@
1
+ import { purgeAccount } from "./accounts.js";
2
+ import { write } from "./transaction.js";
3
+ /** Set by store version 48 and deleted once the purge ran. */
4
+ export const TEST_ACCOUNTS_TO_PURGE = "test_accounts_to_purge";
5
+ /** The fake messenger of `src/testing/shared-program.ts`; no tool writes it. */
6
+ const TEST_PROVIDER = "chat";
7
+ /**
8
+ * Removes every account of the test provider, as `cache clear` removes one. A failure keeps the key for the next open
9
+ * and only warns: a leftover test account must never stop the owner's store from opening.
10
+ */
11
+ export const purgeTestAccounts = (context, warn) => {
12
+ const { database } = context;
13
+ if (!database.prepare("SELECT 1 AS found FROM store_settings WHERE key = ?").get(TEST_ACCOUNTS_TO_PURGE))
14
+ return 0;
15
+ try {
16
+ return write(database, () => {
17
+ const pks = database
18
+ .prepare("SELECT id FROM accounts WHERE provider = ? ORDER BY id")
19
+ .all(TEST_PROVIDER)
20
+ .map((row) => Number(row.id));
21
+ for (const pk of pks)
22
+ purgeAccount(context, pk);
23
+ database.prepare("DELETE FROM store_settings WHERE key = ?").run(TEST_ACCOUNTS_TO_PURGE);
24
+ return pks.length;
25
+ });
26
+ }
27
+ catch (error) {
28
+ warn(`could not remove the test accounts from the store; the next open tries again: ${String(error)}`);
29
+ return 0;
30
+ }
31
+ };
@@ -232,6 +232,7 @@ export declare const checkRevision: (row: Row, expected: number | undefined) =>
232
232
  type WorkTaskOperations = Omit<WorkTasks, "create" | "assign"> & {
233
233
  create(input: NewWorkTask, by: Author): TaskDetails;
234
234
  assign(id: string, assignment: TaskAssignment, by: Author, check?: RevisionCheck): TaskDetails;
235
+ editNow(id: string, fields: TaskFields, by: Author): TaskDetails;
235
236
  };
236
237
  export declare const workTaskOperationsOver: (context: StoreContext) => WorkTaskOperations;
237
238
  export declare const workTasksOver: (context: StoreContext) => WorkTasks;
@@ -396,6 +396,12 @@ export const workTaskOperationsOver = (context) => {
396
396
  });
397
397
  return details(task);
398
398
  },
399
+ /** `edit` inside the caller's write, so a decision that edits a task rolls back with it. */
400
+ editNow: (id, fields, by) => {
401
+ const task = required(id);
402
+ write(database, () => edit(task, fields, authorActor(context, by).actor));
403
+ return details(task);
404
+ },
399
405
  move: async (id, move, by, check = {}) => {
400
406
  const task = required(id);
401
407
  return write(database, () => {
@@ -9,6 +9,7 @@ import { type ConversationsStore } from "./conversations.js";
9
9
  import { type CountersStore } from "./counters.js";
10
10
  import { type DecisionPolicy } from "./decisions.js";
11
11
  import type { EvidenceFindingsStore } from "./evidence-findings.js";
12
+ import type { MailFindingsStore } from "./mail-findings.js";
12
13
  import type { MeetingFindingsStore } from "./meeting-findings.js";
13
14
  import { type MentionsStore } from "./mentions.js";
14
15
  import { type MessagesStore } from "./messages.js";
@@ -94,6 +95,8 @@ export interface MessageStore {
94
95
  readonly meetingFindings: MeetingFindingsStore;
95
96
  /** Cited facts and follow-ups about one project from stored messages, mail and documents, for review. */
96
97
  readonly evidenceFindings: EvidenceFindingsStore;
98
+ /** What an agent read in new mail, cited and staged per mailbox; only the owner decides it. */
99
+ readonly mailFindings: MailFindingsStore;
97
100
  /** What any ref names, a page of them at a time: read once per page, never once per row. */
98
101
  readonly refs: RefsStore;
99
102
  readonly facts: FactsStore;
@@ -28,6 +28,7 @@ import { financeStoreOver } from "./sqlite/finance.js";
28
28
  import { involvementStoreOver } from "./sqlite/involvements.js";
29
29
  import { knowledgeStoreOver } from "./sqlite/knowledge.js";
30
30
  import { logPruner } from "./sqlite/log-pruning.js";
31
+ import { mailFindingsStoreOver } from "./sqlite/mail-findings.js";
31
32
  import { meetingFindingsStoreOver } from "./sqlite/meeting-findings.js";
32
33
  import { meetingVectorsOver } from "./sqlite/meeting-vectors.js";
33
34
  import { meetingStoreOver } from "./sqlite/meetings.js";
@@ -108,6 +109,7 @@ const storeOver = (context) => {
108
109
  const calendar = once(() => ({ ...eventStoreOver(context), ...eventSearchOver(context) }));
109
110
  const meetingFindings = once(() => meetingFindingsStoreOver(context));
110
111
  const evidenceFindings = once(() => evidenceFindingsStoreOver(context));
112
+ const mailFindings = once(() => mailFindingsStoreOver(context));
111
113
  const refs = {
112
114
  summaries: async (texts, options) => refSummariesOf(context.database, texts, options),
113
115
  };
@@ -211,6 +213,9 @@ const storeOver = (context) => {
211
213
  get evidenceFindings() {
212
214
  return evidenceFindings();
213
215
  },
216
+ get mailFindings() {
217
+ return mailFindings();
218
+ },
214
219
  get facts() {
215
220
  return facts();
216
221
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wirecat/cli-core",
3
- "version": "0.559.0",
3
+ "version": "0.561.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",