@wirecat/cli-core 0.547.0 → 0.549.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 (79) hide show
  1. package/CHANGELOG.md +228 -0
  2. package/dist/calendar/cli/answers.d.ts +7 -2
  3. package/dist/calendar/cli/answers.js +12 -4
  4. package/dist/calendar/cli/index.d.ts +10 -1
  5. package/dist/calendar/cli/index.js +18 -11
  6. package/dist/calendar/cli/search-all.d.ts +17 -0
  7. package/dist/calendar/cli/search-all.js +72 -0
  8. package/dist/calendar/feeds.d.ts +19 -0
  9. package/dist/calendar/feeds.js +52 -9
  10. package/dist/calendar/source.d.ts +10 -2
  11. package/dist/calendar/sync.d.ts +2 -0
  12. package/dist/calendar/sync.js +10 -6
  13. package/dist/calendar/testing/memory.js +39 -0
  14. package/dist/cli/answer-entity.d.ts +4 -0
  15. package/dist/cli/answer-entity.js +5 -0
  16. package/dist/cli/bot/context.d.ts +2 -0
  17. package/dist/cli/compact.d.ts +41 -0
  18. package/dist/cli/compact.js +313 -0
  19. package/dist/cli/context.d.ts +11 -2
  20. package/dist/cli/context.js +15 -6
  21. package/dist/cli/envelope.d.ts +5 -1
  22. package/dist/cli/envelope.js +15 -4
  23. package/dist/cli/index.d.ts +2 -0
  24. package/dist/cli/index.js +2 -0
  25. package/dist/cli/messenger/search-command.d.ts +10 -0
  26. package/dist/cli/messenger/search-command.js +19 -13
  27. package/dist/cli/messenger/store-maintenance-command.d.ts +1 -1
  28. package/dist/cli/program.d.ts +3 -0
  29. package/dist/cli/program.js +7 -1
  30. package/dist/domain/providers.d.ts +3 -0
  31. package/dist/domain/providers.js +20 -4
  32. package/dist/domain/ref-arguments.js +3 -7
  33. package/dist/domain/ref-summaries.d.ts +56 -0
  34. package/dist/domain/ref-summaries.js +63 -0
  35. package/dist/entities/index.d.ts +11 -0
  36. package/dist/entities/relationship.d.ts +2 -0
  37. package/dist/entities/relationship.js +2 -0
  38. package/dist/entities/task-results.d.ts +10 -0
  39. package/dist/entities/task-results.js +1 -0
  40. package/dist/errors/reasons.js +1 -0
  41. package/dist/meetings/event-store.d.ts +10 -0
  42. package/dist/meetings/testing/event-store-contract.js +36 -0
  43. package/dist/output-detail.js +9 -5
  44. package/dist/output.d.ts +5 -1
  45. package/dist/output.js +29 -4
  46. package/dist/services/proposals/kinds/relationship-update.d.ts +4 -0
  47. package/dist/services/proposals/kinds/relationship-update.js +53 -0
  48. package/dist/services/proposals/registry.js +2 -0
  49. package/dist/store/decisions.d.ts +3 -1
  50. package/dist/store/decisions.js +7 -1
  51. package/dist/store/index.d.ts +2 -1
  52. package/dist/store/sqlite/accounts.d.ts +8 -0
  53. package/dist/store/sqlite/accounts.js +2 -2
  54. package/dist/store/sqlite/calendar-forget.d.ts +17 -0
  55. package/dist/store/sqlite/calendar-forget.js +68 -0
  56. package/dist/store/sqlite/calendar.js +8 -0
  57. package/dist/store/sqlite/evidence-sources.d.ts +2 -2
  58. package/dist/store/sqlite/evidence-sources.js +41 -77
  59. package/dist/store/sqlite/knowledge.d.ts +17 -23
  60. package/dist/store/sqlite/knowledge.js +13 -13
  61. package/dist/store/sqlite/manifest.js +2 -0
  62. package/dist/store/sqlite/migrations.generated.js +12 -0
  63. package/dist/store/sqlite/notes.d.ts +5 -0
  64. package/dist/store/sqlite/notes.js +6 -2
  65. package/dist/store/sqlite/open-store.js +2 -0
  66. package/dist/store/sqlite/proposed-actions.d.ts +1 -1
  67. package/dist/store/sqlite/proposed-actions.js +10 -2
  68. package/dist/store/sqlite/ref-summaries.d.ts +17 -0
  69. package/dist/store/sqlite/ref-summaries.js +378 -0
  70. package/dist/store/sqlite/relation-proposals.d.ts +40 -0
  71. package/dist/store/sqlite/relation-proposals.js +88 -1
  72. package/dist/store/sqlite/schema.d.ts +16 -0
  73. package/dist/store/sqlite/schema.js +1 -0
  74. package/dist/store/sqlite/suggested-links.d.ts +10 -0
  75. package/dist/store/sqlite/suggested-links.js +68 -0
  76. package/dist/store/store.d.ts +3 -0
  77. package/dist/store/store.js +5 -0
  78. package/package.json +3 -3
  79. package/parity.json +4 -0
@@ -1,6 +1,7 @@
1
1
  import { CliError } from "@wirecat/cli-base";
2
2
  import { calendarText, entryInWindow, entryMetadata, joinKeyOf, joinUrlWithoutQuery, } from "../../meetings/index.js";
3
3
  import { listEvents } from "./calendar-events.js";
4
+ import { forgetCalendarRows } from "./calendar-forget.js";
4
5
  import { bool, eventOf, fromJson, int, str, toJson } from "./events.js";
5
6
  import { driveDocumentOf, driveFileTarget, logFileLink } from "./files.js";
6
7
  import { identityOf } from "./identities.js";
@@ -524,6 +525,13 @@ export const eventStoreOver = (context) => {
524
525
  .prepare("UPDATE calendars SET synced = ?, updated_at = ? WHERE id = ?")
525
526
  .run(flag(synced), now, calendarId);
526
527
  }),
528
+ forgetCalendar: async (calendarId, now) => write(database, () => {
529
+ const accountId = Number(calendarRow(calendarId).account_id);
530
+ return forgetCalendarRows(database, { calendarId, accountId, cursorKey: cursorKey(calendarId), now }, {
531
+ event: refreshEvent,
532
+ series: refreshSeries,
533
+ });
534
+ }),
527
535
  saveEntries: async (calendarId, entries, now) => write(database, () => saveAll(calendarId, entries, now)),
528
536
  removeEntries: async (calendarId, externalIds, now) => write(database, () => removeAll(calendarId, externalIds, now)),
529
537
  markUnseen: async (calendarId, seenBefore, window, now) => write(database, () => {
@@ -13,6 +13,8 @@ export interface EvidenceSource {
13
13
  /** Deleted at its source since it was cited, or no longer in the store. */
14
14
  missing: boolean;
15
15
  }
16
+ /** Each distinct reference read back, in order; one the store no longer resolves keeps its reference, `missing`. */
17
+ export declare const sourcesOfReferences: (database: CacheDatabase, references: readonly string[]) => EvidenceSource[];
16
18
  /**
17
19
  * Every `evidence` link from a memory or fact, in the order they were written, including one whose target was
18
20
  * deleted since: `evidenceOf` drops those, so a reader would never learn the record lost a source.
@@ -21,5 +23,3 @@ export declare const evidenceSourcesOf: (database: CacheDatabase, from: {
21
23
  type: string;
22
24
  id: number;
23
25
  }) => EvidenceSource[];
24
- /** Each distinct reference read back, in order; one the store no longer resolves keeps its reference, `missing`. */
25
- export declare const sourcesOfReferences: (database: CacheDatabase, references: readonly string[]) => EvidenceSource[];
@@ -1,83 +1,47 @@
1
1
  import { linkKind } from "./link-kinds.js";
2
- import { referenceOfThing, stateOfThing, storedThing, thingOf } from "./things.js";
3
- const EXCERPT_CHARS = 280;
4
- const excerptOf = (text) => {
5
- if (text == null)
6
- return null;
7
- const flat = String(text).replace(/\s+/g, " ").trim();
8
- if (!flat)
9
- return null;
10
- return flat.length > EXCERPT_CHARS ? `${flat.slice(0, EXCERPT_CHARS - 1)}…` : flat;
11
- };
12
- const iso = (value) => (value == null ? null : new Date(Number(value)).toISOString());
13
- const textOf = (value) => (value == null || String(value).trim() === "" ? null : String(value));
14
- const READS = {
15
- message: "SELECT c.title, coalesce(m.sender_name, i.name) AS sender, m.sent_at AS at, m.text AS body, a.provider AS tool " +
16
- "FROM messages m JOIN chats c ON c.id = m.chat_id JOIN accounts a ON a.id = c.account_id " +
17
- "LEFT JOIN identities i ON i.id = m.sender_identity_id WHERE m.id = ?",
18
- email: "SELECT e.subject AS title, coalesce(e.from_name, e.from_address) AS sender, e.sent_at AS at, " +
19
- "coalesce(e.body_text, e.snippet) AS body, 'email' AS tool FROM emails e WHERE e.id = ?",
20
- note: "SELECT title, NULL AS sender, updated_at AS at, body, 'notes' AS tool FROM notes WHERE id = ?",
21
- document: "SELECT d.title, NULL AS sender, coalesce(d.external_updated_at, d.updated_at) AS at, d.body, a.provider AS tool " +
22
- "FROM documents d JOIN accounts a ON a.id = d.account_id WHERE d.id = ?",
23
- meeting: "SELECT m.title, NULL AS sender, m.started_at AS at, m.description AS body, a.provider AS tool " +
24
- "FROM meetings m JOIN accounts a ON a.id = m.account_id WHERE m.id = ?",
25
- meeting_transcript: "SELECT m.title, NULL AS sender, m.started_at AS at, NULL AS body, a.provider AS tool FROM meeting_transcripts t " +
26
- "JOIN meetings m ON m.id = t.meeting_id JOIN accounts a ON a.id = m.account_id WHERE t.id = ?",
27
- meeting_transcript_row: "SELECT m.title, r.speaker_name AS sender, m.started_at + coalesce(r.start_ms, 0) AS at, r.text AS body, " +
28
- "a.provider AS tool FROM meeting_transcript_rows r JOIN meeting_transcripts t ON t.id = r.meeting_transcript_id " +
29
- "JOIN meetings m ON m.id = t.meeting_id JOIN accounts a ON a.id = m.account_id WHERE r.id = ?",
30
- };
31
- const KIND = {
32
- meeting_transcript: "meeting",
33
- meeting_transcript_row: "meeting",
34
- };
35
- const sourceOf = (database, thing) => {
36
- const state = stateOfThing(database, thing);
37
- const read = READS[thing.type];
38
- const row = read === undefined || state === "unavailable" ? undefined : database.prepare(read).get(thing.id);
39
- const gone = state !== "available";
40
- return {
41
- ref: referenceOfThing(database, thing) ?? null,
42
- kind: KIND[thing.type] ?? thing.type,
43
- title: textOf(row?.title),
44
- sender: textOf(row?.sender),
45
- at: iso(row?.at),
46
- excerpt: gone ? null : excerptOf(row?.body),
47
- tool: textOf(row?.tool),
48
- missing: gone,
49
- };
50
- };
2
+ import { refReadsOf } from "./ref-summaries.js";
3
+ import { referenceOfThing, storedThing } from "./things.js";
4
+ /** A message is known by its chat here, not by the sender and time its summary title says. */
5
+ const sourceOf = (read, ref) => ({
6
+ ref,
7
+ kind: read.kind,
8
+ title: read.kind === "message" ? read.in : read.title,
9
+ sender: read.sender,
10
+ at: read.at,
11
+ excerpt: read.excerpt,
12
+ tool: read.provider,
13
+ missing: read.state !== "available",
14
+ });
15
+ /** Each distinct reference read back, in order; one the store no longer resolves keeps its reference, `missing`. */
16
+ export const sourcesOfReferences = (database, references) => [...refReadsOf(database, references, { excerpt: true })].map(([ref, read]) => sourceOf(read, ref));
17
+ const GONE_KIND = { meeting_transcript: "meeting", meeting_transcript_row: "meeting" };
51
18
  /**
52
19
  * Every `evidence` link from a memory or fact, in the order they were written, including one whose target was
53
20
  * deleted since: `evidenceOf` drops those, so a reader would never learn the record lost a source.
54
21
  */
55
- export const evidenceSourcesOf = (database, from) => database
56
- .prepare(`SELECT to_type, to_id FROM links WHERE from_type = ? AND from_id = ? AND kind = ${linkKind("evidence")} ` +
57
- "AND to_id IS NOT NULL ORDER BY id")
58
- .all(from.type, from.id)
59
- .map((link) => sourceOf(database, storedThing(link.to_type, link.to_id)));
60
- const thingNamed = (database, reference) => {
61
- try {
62
- return thingOf(database, reference);
63
- }
64
- catch {
65
- return undefined;
66
- }
22
+ export const evidenceSourcesOf = (database, from) => {
23
+ const cited = database
24
+ .prepare(`SELECT to_type, to_id FROM links WHERE from_type = ? AND from_id = ? AND kind = ${linkKind("evidence")} ` +
25
+ "AND to_id IS NOT NULL ORDER BY id")
26
+ .all(from.type, from.id)
27
+ .map((link) => {
28
+ const thing = storedThing(link.to_type, link.to_id);
29
+ return { type: thing.type, ref: referenceOfThing(database, thing) };
30
+ });
31
+ const reads = refReadsOf(database, cited.flatMap(({ ref }) => ref ?? []), { excerpt: true });
32
+ return cited.map(({ type, ref }) => {
33
+ const read = ref === undefined ? undefined : reads.get(ref);
34
+ if (ref !== undefined && read !== undefined)
35
+ return sourceOf(read, ref);
36
+ return {
37
+ ref: null,
38
+ kind: GONE_KIND[type] ?? type,
39
+ title: null,
40
+ sender: null,
41
+ at: null,
42
+ excerpt: null,
43
+ tool: null,
44
+ missing: true,
45
+ };
46
+ });
67
47
  };
68
- /** Each distinct reference read back, in order; one the store no longer resolves keeps its reference, `missing`. */
69
- export const sourcesOfReferences = (database, references) => [...new Set(references)].map((reference) => {
70
- const thing = thingNamed(database, reference);
71
- if (thing)
72
- return { ...sourceOf(database, thing), ref: reference };
73
- return {
74
- ref: reference,
75
- kind: reference.startsWith("msg:") ? "message" : reference.split(":")[0] || "unknown",
76
- title: null,
77
- sender: null,
78
- at: null,
79
- excerpt: null,
80
- tool: null,
81
- missing: true,
82
- };
83
- });
@@ -1,17 +1,17 @@
1
1
  import { type QueryAst } from "@wirecat/cli-base/query";
2
2
  import { type Decider } from "../decisions.js";
3
3
  import type { AccountKey } from "../types.js";
4
- import { RELATION_KINDS } from "./link-kinds.js";
5
4
  import { type MeetingProjectsStore } from "./meeting-projects.js";
6
5
  import type { Author } from "./memories.js";
7
6
  import { type Link } from "./notes.js";
8
7
  import type { StoreContext } from "./open.js";
9
8
  import type { ProposedAction } from "./proposed-actions.js";
10
- import { type LinkDecision } from "./relation-proposals.js";
9
+ import { type KnowledgeRelation, type LinkDecision, type RelateInput, type RelationUpdate } from "./relation-proposals.js";
11
10
  import type { TagCreationOptions } from "./tag-creation.js";
12
11
  import { type Label, type TagKind, type TagRow } from "./tags.js";
13
12
  import { type ThingState } from "./things.js";
14
13
  import { type VocabularyStore } from "./vocabulary.js";
14
+ export type { KnowledgeRelation, RelateInput } from "./relation-proposals.js";
15
15
  export type { Label } from "./tags.js";
16
16
  export type KnowledgeTarget = {
17
17
  type: "message";
@@ -90,18 +90,6 @@ export interface Project {
90
90
  scope: (typeof SCOPES)[number];
91
91
  createdAt: string;
92
92
  }
93
- export interface KnowledgeRelation {
94
- id: string;
95
- from: string;
96
- to: string;
97
- kind: (typeof RELATION_KINDS)[number];
98
- role: string | null;
99
- evidence: string | null;
100
- confirmed: boolean;
101
- provenance: string | null;
102
- confidenceCategory: "owner-confirmed" | "weak";
103
- createdAt: string;
104
- }
105
93
  export interface Reminder {
106
94
  id: string;
107
95
  task: string;
@@ -199,9 +187,24 @@ export interface KnowledgeStore extends VocabularyStore, MeetingProjectsStore {
199
187
  to: string;
200
188
  kind: KnowledgeRelation["kind"];
201
189
  role?: string;
190
+ purpose?: string;
202
191
  evidence?: string[];
203
192
  reason?: string;
204
193
  }, by: Exclude<Author, "owner">): Promise<ProposedAction>;
194
+ /** The owner sets a relationship's role and purpose; `null` clears one. */
195
+ updateRelation(key: KnowledgeScope, id: string, input: RelationUpdate & {
196
+ by: "owner";
197
+ }): Promise<KnowledgeRelation>;
198
+ /** An agent's change is a `relationship_update` proposal for the owner; the same pending change is answered again. */
199
+ updateRelation(key: KnowledgeScope, id: string, input: RelationUpdate & {
200
+ by: Exclude<Author, "owner">;
201
+ reason?: string;
202
+ }): Promise<ProposedAction>;
203
+ /** Decides a `relationship_update` proposal; a confirmed one changes the relationship in the same transaction. */
204
+ decideRelationUpdate(id: string, decision: LinkDecision): Promise<{
205
+ proposal: ProposedAction;
206
+ relation: KnowledgeRelation | null;
207
+ }>;
205
208
  /** Decides a stored `relationship` or `key_document` proposal; the link and the decision land together. */
206
209
  decideLinkProposal(id: string, decision: LinkDecision): Promise<{
207
210
  proposal: ProposedAction;
@@ -229,13 +232,4 @@ export interface KnowledgeStore extends VocabularyStore, MeetingProjectsStore {
229
232
  }): Promise<Reminder[]>;
230
233
  acknowledgeReminder(key: AccountKey, id: string, receipt: string): Promise<Reminder>;
231
234
  }
232
- export interface RelateInput {
233
- from: string;
234
- to: string;
235
- kind: KnowledgeRelation["kind"];
236
- role?: string;
237
- evidence?: string;
238
- confirmed?: boolean;
239
- provenance?: string;
240
- }
241
235
  export declare const knowledgeStoreOver: (context: StoreContext) => KnowledgeStore;
@@ -15,7 +15,7 @@ import { meetingProjectsStoreOver } from "./meeting-projects.js";
15
15
  import { holdingText } from "./note-index.js";
16
16
  import { insertLink, linkOf } from "./notes.js";
17
17
  import { queryFilter } from "./query-filter.js";
18
- import { relationProposalsOver } from "./relation-proposals.js";
18
+ import { relationOf, relationProposalsOver, } from "./relation-proposals.js";
19
19
  import { addTags as addThingTags, canonicalTagNamed, createTag, labelsOf, liveTagging, removeTags as removeThingTags, setMainTopic, } from "./tags.js";
20
20
  import { INBOX_KEY_LENGTH, TASK_KEY_PREFIXES } from "./task-keys.js";
21
21
  import { inboxKey, taskAccountOf, taskIdOf, taskRowOf } from "./tasks.js";
@@ -221,18 +221,6 @@ export const knowledgeStoreOver = (context) => {
221
221
  scope: row.scope,
222
222
  createdAt: iso(row.created_at),
223
223
  });
224
- const relationOf = (link) => ({
225
- id: link.id,
226
- from: link.from,
227
- to: String(link.to),
228
- kind: link.kind,
229
- role: link.role,
230
- evidence: link.evidence,
231
- confirmed: link.confirmed,
232
- provenance: link.provenance,
233
- confidenceCategory: link.confirmed ? "owner-confirmed" : "weak",
234
- createdAt: link.createdAt,
235
- });
236
224
  const proposals = relationProposalsOver(context);
237
225
  const RELATIONS = `SELECT * FROM links WHERE kind IN (${RELATION_KINDS.map(linkKind).join(",")}) AND to_id IS NOT NULL`;
238
226
  const relation = (id) => {
@@ -561,6 +549,7 @@ export const knowledgeStoreOver = (context) => {
561
549
  origin: confirmed ? "owner" : "suggested",
562
550
  confirmed,
563
551
  ...(input.role === undefined ? {} : { role: input.role }),
552
+ ...(input.purpose === undefined ? {} : { purpose: input.purpose }),
564
553
  ...(input.evidence === undefined ? {} : { evidence: input.evidence }),
565
554
  ...(input.provenance === undefined ? {} : { provenance: input.provenance }),
566
555
  })));
@@ -573,6 +562,17 @@ export const knowledgeStoreOver = (context) => {
573
562
  });
574
563
  return proposals.propose(from, input, by);
575
564
  },
565
+ updateRelation: (async (key, id, input) => {
566
+ checked(key);
567
+ const link = linkOf(database, relation(id));
568
+ if (input.by === "owner")
569
+ return relationOf(proposals.update(link, input));
570
+ return proposals.proposeUpdate(link, input, input.by);
571
+ }),
572
+ decideRelationUpdate: async (id, decision) => {
573
+ const { proposal, link } = proposals.decideUpdate(id, decision);
574
+ return { proposal, relation: link && relationOf(link) };
575
+ },
576
576
  decideLinkProposal: async (id, decision) => proposals.decideProposal(id, decision),
577
577
  decideSuggestedLink: async (id, decision) => proposals.decideLink(id, decision),
578
578
  relations: async (key, reference) => {
@@ -57,6 +57,8 @@ export const MANIFEST = [
57
57
  { name: "20261031230945_version-41-task-fields", version: 41, minCompatible: 34 },
58
58
  { name: "20261101002523_version-42-task-revision", version: 42, minCompatible: 34 },
59
59
  { name: "20261101011859_version-43-memory-project", version: 43, minCompatible: 34 },
60
+ { name: "20261102050533_version-44-relationship-purpose", version: 44, minCompatible: 34 },
61
+ { name: "20261102050534_version-44-suggested-links-move", version: 44, minCompatible: 34 },
60
62
  ];
61
63
  export const generatedMigrations = (manifest = MANIFEST, generated = GENERATED) => {
62
64
  const migrations = [];
@@ -877,5 +877,17 @@ export const GENERATED = [
877
877
  "ALTER TABLE `memories` ADD `project_id` integer REFERENCES projects(id);",
878
878
  "CREATE INDEX `memories_by_project_id` ON `memories` (`project_id`);"
879
879
  ]
880
+ },
881
+ {
882
+ "name": "20261102050533_version-44-relationship-purpose",
883
+ "statements": [
884
+ "ALTER TABLE `links` ADD `purpose` text;"
885
+ ]
886
+ },
887
+ {
888
+ "name": "20261102050534_version-44-suggested-links-move",
889
+ "statements": [
890
+ "-- A suggestion's references (chat:, msg:) need the store's own encoding, which SQL cannot write, so the next writable\n-- open moves the unconfirmed agent and rule links into `proposed_actions` and deletes this key (moveSuggestedLinks).\nINSERT OR REPLACE INTO store_settings (key, value, at) VALUES ('suggested_links_to_move', '1', unixepoch() * 1000);"
891
+ ]
880
892
  }
881
893
  ];
@@ -56,6 +56,8 @@ export interface Link {
56
56
  origin: (typeof LINK_SOURCES)[number];
57
57
  targetText: string | null;
58
58
  role: string | null;
59
+ /** What a chat is for in a project, from the purposes memo's config lists; `null` means `work`. */
60
+ purpose: string | null;
59
61
  evidence: string | null;
60
62
  provenance: string | null;
61
63
  confirmed: boolean;
@@ -79,6 +81,7 @@ export interface LinkInput {
79
81
  anchor?: string;
80
82
  origin?: (typeof LINK_ORIGINS)[number];
81
83
  role?: string;
84
+ purpose?: string;
82
85
  evidence?: string;
83
86
  provenance?: string;
84
87
  confirmed?: boolean;
@@ -194,6 +197,8 @@ export declare const requiredThing: (database: CacheDatabase, reference: string,
194
197
  * and only a name some note is waiting for costs more.
195
198
  */
196
199
  export declare const resolvePersonLinks: (database: CacheDatabase, names: (string | null | undefined)[]) => void;
200
+ /** The store keeps no list of purposes: memo's config holds it, and checks a purpose before it gets here. */
201
+ export declare const purposeOf: (purpose: string) => string;
197
202
  /** Writes one link; an identical resolved link is answered instead of doubled. */
198
203
  export declare const insertLink: (context: StoreContext, input: LinkInput) => Link;
199
204
  export declare const notesStoreOver: (context: StoreContext) => Omit<NotesStore, keyof NoteSearch>;
@@ -87,6 +87,7 @@ export const linkOf = (database, row) => {
87
87
  origin: row.source,
88
88
  targetText: row.target_text == null ? null : String(row.target_text),
89
89
  role: row.role == null ? null : String(row.role),
90
+ purpose: row.purpose == null ? null : String(row.purpose),
90
91
  evidence: row.evidence == null ? null : String(row.evidence),
91
92
  provenance: metadata.provenance ?? null,
92
93
  confirmed: Number(row.confirmed) === 1,
@@ -164,6 +165,8 @@ const ownerLink = (input) => {
164
165
  });
165
166
  return input;
166
167
  };
168
+ /** The store keeps no list of purposes: memo's config holds it, and checks a purpose before it gets here. */
169
+ export const purposeOf = (purpose) => textOf(purpose, "a purpose", 100);
167
170
  /** Writes one link; an identical resolved link is answered instead of doubled. */
168
171
  export const insertLink = (context, input) => {
169
172
  const { database, now } = context;
@@ -187,8 +190,9 @@ export const insertLink = (context, input) => {
187
190
  const provenance = input.provenance === undefined ? undefined : textOf(input.provenance, "provenance", 2000);
188
191
  const row = database
189
192
  .prepare("INSERT INTO links (from_type, from_id, to_type, to_id, kind, anchor, source, target_text, target_folded, role, evidence, " +
190
- "metadata, confirmed, created_at, author, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING id")
191
- .get(from.type, from.id, to?.type ?? null, to?.id ?? null, kind, input.anchor ?? null, origin, targetText, targetText === null ? null : targetKey(targetText), input.role === undefined ? null : textOf(input.role, "a role", 200), input.evidence === undefined ? null : textOf(input.evidence, "evidence", 2000), provenance === undefined ? null : JSON.stringify({ provenance }), input.confirmed === false ? 0 : 1, at, origin === "owner" ? "owner" : null, at);
193
+ "metadata, confirmed, created_at, author, updated_at, purpose) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) " +
194
+ "RETURNING id")
195
+ .get(from.type, from.id, to?.type ?? null, to?.id ?? null, kind, input.anchor ?? null, origin, targetText, targetText === null ? null : targetKey(targetText), input.role === undefined ? null : textOf(input.role, "a role", 200), input.evidence === undefined ? null : textOf(input.evidence, "evidence", 2000), provenance === undefined ? null : JSON.stringify({ provenance }), input.confirmed === false ? 0 : 1, at, origin === "owner" ? "owner" : null, at, input.purpose === undefined ? null : purposeOf(input.purpose));
192
196
  if (to === null && targetText !== null)
193
197
  resolveKey(database, targetKey(targetText));
194
198
  return linkOf(database, database.prepare("SELECT * FROM links WHERE id = ?").get(Number(row?.id)));
@@ -16,6 +16,7 @@ import { loadProviders } from "./providers.js";
16
16
  import { assertReadCompatible } from "./read-compatibility.js";
17
17
  import { stemsState } from "./stems.js";
18
18
  import { fillStems, followDefaultStemmers } from "./stems-fill.js";
19
+ import { moveSuggestedLinks } from "./suggested-links.js";
19
20
  import { fillVectorIndexes } from "./vector-index.js";
20
21
  export const BACKFILL_ON_OPEN = 5_000;
21
22
  const NOT_WRITABLE = new Set(["EACCES", "EPERM", "EROFS"]);
@@ -55,6 +56,7 @@ export const openStoreContext = async ({ path, env, now = Date.now, command, ver
55
56
  fillFiles(database, now);
56
57
  fillFolded(database);
57
58
  fillJoinKeys(database);
59
+ moveSuggestedLinks(database, now);
58
60
  const pending = pendingNormalization(database);
59
61
  if (pending > 0 && pending <= BACKFILL_ON_OPEN)
60
62
  backfillNormalized(database);
@@ -81,7 +81,7 @@ type ProposalOperations = Omit<{
81
81
  proposer(id: string): Proposer;
82
82
  };
83
83
  /** Who proposed an action, for `mayDecide`. */
84
- export declare const proposerOfAction: ({ proposedBy }: ProposedAction) => Proposer;
84
+ export declare const proposerOfAction: ({ proposedBy, payload }: ProposedAction) => Proposer;
85
85
  export declare const proposalOperationsOver: (context: StoreContext) => ProposalOperations;
86
86
  export declare const proposedActionsStoreOver: (context: StoreContext) => ProposedActionsStore;
87
87
  export {};
@@ -20,11 +20,19 @@ const STATUS = `CASE WHEN p.status = 'approved' AND p.kind IN ('${MEETING_FINDIN
20
20
  const NAMED = `SELECT p.*, ${STATUS} AS shown_status, b.name AS proposer_name, b.kind AS proposer_kind FROM proposed_actions p ` +
21
21
  "LEFT JOIN bots b ON p.proposed_by_type = 'bot' AND b.id = p.proposed_by_id";
22
22
  /** Who proposed an action, for `mayDecide`. */
23
- export const proposerOfAction = ({ proposedBy }) => proposedBy.type === "person"
23
+ export const proposerOfAction = ({ proposedBy, payload }) => proposedBy.type === "person"
24
24
  ? { type: "person", id: proposedBy.id }
25
25
  : proposedBy.name === null
26
26
  ? { type: "unknown" }
27
- : { type: "bot", id: proposedBy.id, name: proposedBy.name, kind: proposedBy.kind ?? "agent" };
27
+ : {
28
+ type: "bot",
29
+ id: proposedBy.id,
30
+ name: proposedBy.name,
31
+ kind: proposedBy.kind ?? "agent",
32
+ ...(movedSuggestion(payload) ? { verified: false } : {}),
33
+ };
34
+ /** A suggestion moved from `links` names its bot only as the link's provenance text said it (store version 44). */
35
+ const movedSuggestion = (payload) => typeof payload === "object" && payload !== null && "suggestedLink" in payload;
28
36
  export const proposalOperationsOver = (context) => {
29
37
  const { database, now } = context;
30
38
  const actionOf = (row) => {
@@ -0,0 +1,17 @@
1
+ import { type RefSummary } from "../../domain/ref-summaries.js";
2
+ import type { CacheDatabase } from "../driver.js";
3
+ /** A summary plus what an evidence reader shows beside it; `summaries` drops the extra fields. */
4
+ export interface RefRead extends RefSummary {
5
+ sender: string | null;
6
+ excerpt: string | null;
7
+ }
8
+ /**
9
+ * Every ref read back in one statement per kind present, keyed by the text passed and in its order. A bad ref is
10
+ * `unknown` and a ref the store lacks is `unavailable`: one bad row must not fail a page of them.
11
+ */
12
+ export declare const refReadsOf: (database: CacheDatabase, texts: readonly string[], { excerpt }?: {
13
+ excerpt?: boolean;
14
+ }) => Map<string, RefRead>;
15
+ export declare const refSummariesOf: (database: CacheDatabase, texts: readonly string[], options?: {
16
+ excerpt?: boolean;
17
+ }) => Map<string, RefSummary>;