@wirecat/cli-core 0.550.0 → 0.552.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 (190) hide show
  1. package/CHANGELOG.md +235 -0
  2. package/README.md +1 -1
  3. package/THIRD_PARTY_NOTICES +41 -0
  4. package/dist/background/schedules.d.ts +12 -1
  5. package/dist/background/schedules.js +65 -24
  6. package/dist/background/units.d.ts +16 -9
  7. package/dist/background/units.js +26 -11
  8. package/dist/calendar/ics.d.ts +1 -1
  9. package/dist/calendar/ics.js +5 -2
  10. package/dist/calendar/windows-zones.generated.d.ts +1 -0
  11. package/dist/calendar/windows-zones.generated.js +146 -0
  12. package/dist/cli/compact.js +2 -0
  13. package/dist/cli/context.d.ts +2 -0
  14. package/dist/cli/index.d.ts +3 -0
  15. package/dist/cli/index.js +3 -0
  16. package/dist/cli/meeting-findings-input.js +4 -3
  17. package/dist/cli/meeting-preparation-answer.d.ts +3 -0
  18. package/dist/cli/meeting-preparation-projects.d.ts +3 -0
  19. package/dist/cli/meeting-preparation-read.d.ts +3 -0
  20. package/dist/cli/messenger/backfill-command.js +28 -1
  21. package/dist/cli/messenger/context.js +1 -0
  22. package/dist/cli/messenger/history-gaps.js +4 -4
  23. package/dist/cli/messenger/mcp-command.js +1 -0
  24. package/dist/cli/messenger/messages-delete-command.js +7 -8
  25. package/dist/cli/messenger/projects-command.js +47 -2
  26. package/dist/cli/messenger/search-answers.js +8 -1
  27. package/dist/cli/messenger/serve-catch-up.d.ts +43 -0
  28. package/dist/cli/messenger/serve-catch-up.js +90 -0
  29. package/dist/cli/messenger/serve-command.js +54 -1
  30. package/dist/cli/messenger/server-command.d.ts +4 -3
  31. package/dist/cli/messenger/server-command.js +30 -12
  32. package/dist/cli/payment-query.d.ts +8 -0
  33. package/dist/cli/payment-query.js +27 -0
  34. package/dist/cli/program.js +9 -4
  35. package/dist/cli/project-status-rows.d.ts +8 -0
  36. package/dist/cli/project-status-rows.js +102 -0
  37. package/dist/cli/project-status.d.ts +132 -0
  38. package/dist/cli/project-status.js +248 -0
  39. package/dist/cli/schedule/schedule-command.js +5 -1
  40. package/dist/cli/steps.js +4 -1
  41. package/dist/domain/knowledge.d.ts +1 -1
  42. package/dist/domain/knowledge.js +1 -1
  43. package/dist/domain/status-words.d.ts +3 -0
  44. package/dist/domain/status-words.js +44 -0
  45. package/dist/entities/fact.d.ts +12 -1
  46. package/dist/entities/fact.js +13 -0
  47. package/dist/entities/hit.d.ts +5 -0
  48. package/dist/entities/hit.js +5 -0
  49. package/dist/entities/index.d.ts +45 -2
  50. package/dist/entities/listener.d.ts +6 -0
  51. package/dist/entities/listener.js +1 -0
  52. package/dist/entities/meeting-findings.d.ts +1 -1
  53. package/dist/entities/meeting-preparation.d.ts +3 -0
  54. package/dist/entities/message-transfer.d.ts +16 -0
  55. package/dist/entities/message-transfer.js +6 -0
  56. package/dist/errors/reasons.js +1 -0
  57. package/dist/mcp/app-server.js +1 -0
  58. package/dist/mcp/http/oauth.d.ts +2 -1
  59. package/dist/mcp/http/oauth.js +2 -2
  60. package/dist/mcp/http/serve.d.ts +2 -0
  61. package/dist/mcp/http/serve.js +2 -1
  62. package/dist/mcp/meeting-preparation.d.ts +3 -0
  63. package/dist/mcp/server.js +1 -0
  64. package/dist/mcp/surface.js +1 -1
  65. package/dist/meetings/answers.d.ts +5 -0
  66. package/dist/meetings/answers.js +8 -0
  67. package/dist/meetings/cli/index.js +14 -0
  68. package/dist/meetings/findings-rules.d.ts +14 -3
  69. package/dist/meetings/findings-rules.js +16 -2
  70. package/dist/meetings/index.d.ts +5 -3
  71. package/dist/meetings/index.js +3 -1
  72. package/dist/meetings/ingest.js +2 -1
  73. package/dist/meetings/language.d.ts +5 -0
  74. package/dist/meetings/language.js +57 -0
  75. package/dist/meetings/mcp/search.d.ts +3 -0
  76. package/dist/meetings/meeting-context.d.ts +2 -3
  77. package/dist/meetings/meeting-context.js +3 -4
  78. package/dist/meetings/payment.d.ts +27 -0
  79. package/dist/meetings/payment.js +81 -0
  80. package/dist/meetings/project-evidence-answer.d.ts +3 -0
  81. package/dist/meetings/result-items.d.ts +9 -0
  82. package/dist/meetings/result-items.js +7 -0
  83. package/dist/meetings/store.d.ts +22 -0
  84. package/dist/meetings/testing/memory.js +3 -0
  85. package/dist/meetings/transcription.d.ts +2 -0
  86. package/dist/meetings/transcription.js +5 -1
  87. package/dist/search/lucene/types.d.ts +7 -0
  88. package/dist/search/lucene/types.js +11 -1
  89. package/dist/services/archive.d.ts +13 -0
  90. package/dist/services/archive.js +56 -5
  91. package/dist/services/deps.d.ts +3 -0
  92. package/dist/services/history-limits.d.ts +2 -0
  93. package/dist/services/index.d.ts +4 -1
  94. package/dist/services/index.js +4 -1
  95. package/dist/services/job-query.d.ts +5 -2
  96. package/dist/services/job-query.js +5 -2
  97. package/dist/services/link-scorers/alias-hits.d.ts +7 -0
  98. package/dist/services/link-scorers/alias-hits.js +54 -0
  99. package/dist/services/link-scorers/chat-senders.d.ts +6 -0
  100. package/dist/services/link-scorers/chat-senders.js +56 -0
  101. package/dist/services/link-scorers/chat-title.d.ts +6 -0
  102. package/dist/services/link-scorers/chat-title.js +44 -0
  103. package/dist/services/link-scorers/email-domain.d.ts +6 -0
  104. package/dist/services/link-scorers/email-domain.js +64 -0
  105. package/dist/services/link-scorers/index.d.ts +9 -0
  106. package/dist/services/link-scorers/index.js +8 -0
  107. package/dist/services/link-scorers/mail-thread.d.ts +3 -0
  108. package/dist/services/link-scorers/mail-thread.js +74 -0
  109. package/dist/services/link-scorers/member-chats.d.ts +3 -0
  110. package/dist/services/link-scorers/member-chats.js +40 -0
  111. package/dist/services/link-scorers/member-dialog.d.ts +3 -0
  112. package/dist/services/link-scorers/member-dialog.js +34 -0
  113. package/dist/services/link-scorers/scan.d.ts +41 -0
  114. package/dist/services/link-scorers/scan.js +244 -0
  115. package/dist/services/link-scorers/shared.d.ts +26 -0
  116. package/dist/services/link-scorers/shared.js +48 -0
  117. package/dist/services/link-scorers/types.d.ts +77 -0
  118. package/dist/services/link-scorers/types.js +1 -0
  119. package/dist/services/messages-archive-search.js +3 -2
  120. package/dist/services/messages-combined.js +2 -2
  121. package/dist/services/messages-discovery.js +2 -2
  122. package/dist/services/notes-search.js +4 -5
  123. package/dist/services/project-status/index.d.ts +71 -0
  124. package/dist/services/project-status/index.js +196 -0
  125. package/dist/services/project-status/items.d.ts +10 -0
  126. package/dist/services/project-status/items.js +23 -0
  127. package/dist/services/project-status/signals/amounts.d.ts +20 -0
  128. package/dist/services/project-status/signals/amounts.js +91 -0
  129. package/dist/services/project-status/signals/children.d.ts +13 -0
  130. package/dist/services/project-status/signals/children.js +28 -0
  131. package/dist/services/project-status/signals/coverage.d.ts +13 -0
  132. package/dist/services/project-status/signals/coverage.js +30 -0
  133. package/dist/services/project-status/signals/index.d.ts +3 -0
  134. package/dist/services/project-status/signals/index.js +21 -0
  135. package/dist/services/project-status/signals/last-message.d.ts +4 -0
  136. package/dist/services/project-status/signals/last-message.js +22 -0
  137. package/dist/services/project-status/signals/payments.d.ts +9 -0
  138. package/dist/services/project-status/signals/payments.js +24 -0
  139. package/dist/services/project-status/signals/promises.d.ts +17 -0
  140. package/dist/services/project-status/signals/promises.js +109 -0
  141. package/dist/services/project-status/signals/questions.d.ts +8 -0
  142. package/dist/services/project-status/signals/questions.js +38 -0
  143. package/dist/services/project-status/signals/reply-next.d.ts +19 -0
  144. package/dist/services/project-status/signals/reply-next.js +75 -0
  145. package/dist/services/project-status/signals/topics.d.ts +10 -0
  146. package/dist/services/project-status/signals/topics.js +50 -0
  147. package/dist/services/project-status/types.d.ts +84 -0
  148. package/dist/services/project-status/types.js +1 -0
  149. package/dist/services/proposals/index.d.ts +1 -0
  150. package/dist/services/proposals/index.js +1 -0
  151. package/dist/services/proposals/kinds/findings.d.ts +3 -0
  152. package/dist/services/proposals/kinds/findings.js +8 -1
  153. package/dist/services/search-all-meetings.d.ts +3 -0
  154. package/dist/services/search-all-meetings.js +6 -1
  155. package/dist/speech/local.d.ts +2 -0
  156. package/dist/speech/local.js +8 -1
  157. package/dist/speech/models.d.ts +2 -0
  158. package/dist/speech/models.js +2 -0
  159. package/dist/store/decisions.d.ts +5 -1
  160. package/dist/store/decisions.js +5 -1
  161. package/dist/store/driver.d.ts +11 -0
  162. package/dist/store/driver.js +13 -0
  163. package/dist/store/drivers/node-sqlite.d.ts +1 -1
  164. package/dist/store/drivers/node-sqlite.js +3 -0
  165. package/dist/store/evidence-findings.js +1 -0
  166. package/dist/store/index.d.ts +2 -1
  167. package/dist/store/index.js +1 -0
  168. package/dist/store/project-transfer/request.d.ts +1 -1
  169. package/dist/store/project-transfer/schema.d.ts +2 -2
  170. package/dist/store/project-transfer/snapshot-model.d.ts +2 -2
  171. package/dist/store/sqlite/evidence-findings.js +5 -1
  172. package/dist/store/sqlite/facts.d.ts +13 -0
  173. package/dist/store/sqlite/facts.js +64 -6
  174. package/dist/store/sqlite/findings-core.js +14 -1
  175. package/dist/store/sqlite/knowledge.d.ts +16 -2
  176. package/dist/store/sqlite/knowledge.js +1 -0
  177. package/dist/store/sqlite/lucene.d.ts +1 -1
  178. package/dist/store/sqlite/lucene.js +33 -7
  179. package/dist/store/sqlite/meeting-hit-search.js +10 -3
  180. package/dist/store/sqlite/meeting-languages.d.ts +4 -0
  181. package/dist/store/sqlite/meeting-languages.js +36 -0
  182. package/dist/store/sqlite/meetings.d.ts +2 -1
  183. package/dist/store/sqlite/meetings.js +9 -3
  184. package/dist/store/sqlite/note-search.js +2 -2
  185. package/dist/store/sqlite/project-transfer/snapshot.d.ts +1 -1
  186. package/dist/store/sqlite/relation-proposals.d.ts +29 -2
  187. package/dist/store/sqlite/relation-proposals.js +75 -4
  188. package/dist/store/store.d.ts +1 -0
  189. package/package.json +3 -3
  190. package/parity.json +11 -0
@@ -0,0 +1,36 @@
1
+ import { transcriptLanguage } from "../../meetings/index.js";
2
+ import { positiveReadInteger } from "./meeting-reads.js";
3
+ import { read, writeBatch } from "./transaction.js";
4
+ export const meetingLanguagesOver = ({ database }) => ({
5
+ async fillTranscriptLanguages({ accountId, apply, now }) {
6
+ positiveReadInteger(accountId, "accountId");
7
+ const found = read(database, () => {
8
+ const rows = database.prepare("SELECT text FROM meeting_transcript_rows WHERE meeting_transcript_id = ?");
9
+ return database
10
+ .prepare(`SELECT t.id FROM meeting_transcripts t JOIN meetings m ON m.id = t.meeting_id
11
+ WHERE m.account_id = ? AND t.language IS NULL AND t.deleted_at IS NULL ORDER BY t.id`)
12
+ .all(accountId)
13
+ .map(({ id }) => ({
14
+ id: Number(id),
15
+ language: transcriptLanguage(rows.all(Number(id)).map((row) => String(row.text))),
16
+ }));
17
+ });
18
+ if (apply) {
19
+ const update = database.prepare("UPDATE meeting_transcripts SET language = ?, updated_at = ? WHERE id = ? AND language IS NULL");
20
+ writeBatch(database, () => {
21
+ for (const { id, language } of found)
22
+ if (language !== null)
23
+ update.run(language, now, id);
24
+ });
25
+ }
26
+ const counts = new Map();
27
+ for (const { language } of found)
28
+ counts.set(language, (counts.get(language) ?? 0) + 1);
29
+ return {
30
+ accountId,
31
+ applied: apply,
32
+ transcripts: found.length,
33
+ languages: [...counts].map(([language, transcripts]) => ({ language, transcripts })),
34
+ };
35
+ },
36
+ });
@@ -1,5 +1,6 @@
1
1
  import type { Attachment, MeetingChatLinesStore, MeetingContextStore, MeetingParticipantSearchStore, MeetingPreparationContextStore, MeetingPreparationKnowledgeStore, MeetingPreparationNotesStore, MeetingRelatedHistoryStore, MeetingReviewStore, MeetingSearchIndexStore, MeetingSearchStore, MeetingSeriesHistoryStore, MeetingStatsStore, MeetingStore, MeetingTranscriptReceiptStore, MeetingTranscriptStore, NewRecord } from "../../meetings/index.js";
2
2
  import type { CacheDatabase } from "../driver.js";
3
+ import { type MeetingLanguageStore } from "./meeting-languages.js";
3
4
  import { type MeetingContextReadCapabilities, type MeetingReadCapabilities } from "./meeting-reads.js";
4
5
  import type { StoreContext } from "./open.js";
5
6
  type Row = Record<string, unknown>;
@@ -11,7 +12,7 @@ export declare const saveAttachment: (database: CacheDatabase, type: string, par
11
12
  export declare const drainMeetingIndex: (database: CacheDatabase, batch?: number) => number;
12
13
  /** Each word of the query, as a prefix, all required. */
13
14
  export declare const wordsQuery: (query: string) => string | null;
14
- export type StoredMeetings = MeetingStore & MeetingPreparationContextStore & MeetingPreparationKnowledgeStore & MeetingPreparationNotesStore & MeetingRelatedHistoryStore & MeetingSeriesHistoryStore & MeetingTranscriptStore & MeetingReadCapabilities & MeetingContextReadCapabilities & MeetingParticipantSearchStore & MeetingSearchStore & MeetingTranscriptReceiptStore & MeetingContextStore & MeetingSearchIndexStore & MeetingStatsStore & MeetingReviewStore & MeetingChatLinesStore;
15
+ export type StoredMeetings = MeetingStore & MeetingPreparationContextStore & MeetingPreparationKnowledgeStore & MeetingPreparationNotesStore & MeetingRelatedHistoryStore & MeetingSeriesHistoryStore & MeetingTranscriptStore & MeetingReadCapabilities & MeetingContextReadCapabilities & MeetingParticipantSearchStore & MeetingSearchStore & MeetingTranscriptReceiptStore & MeetingContextStore & MeetingSearchIndexStore & MeetingStatsStore & MeetingReviewStore & MeetingChatLinesStore & MeetingLanguageStore;
15
16
  /** The `MeetingStore` of `src/meetings/` over the shared store. */
16
17
  export declare const meetingStoreOver: (context: StoreContext) => StoredMeetings;
17
18
  export {};
@@ -11,6 +11,7 @@ import { meetingContextLinksOver } from "./meeting-context-links.js";
11
11
  import { meetingHitIndexOver } from "./meeting-hit-index.js";
12
12
  import { meetingHitSearchOver } from "./meeting-hit-search.js";
13
13
  import { INDEX_CODES, indexedText, meetingRowid, summaryOf } from "./meeting-index-values.js";
14
+ import { meetingLanguagesOver } from "./meeting-languages.js";
14
15
  import { meetingParticipantSearchOver } from "./meeting-participant-search.js";
15
16
  import { meetingPreparationContextOver } from "./meeting-preparation-context.js";
16
17
  import { meetingPreparationKnowledgeOver } from "./meeting-preparation-knowledge.js";
@@ -516,17 +517,18 @@ const search = (database, query, filter) => {
516
517
  .prepare(`WITH hit AS (SELECT rowid AS r FROM meeting_words WHERE meeting_words MATCH ?),
517
518
  picked AS (SELECT id, row_number() OVER (ORDER BY coalesce(started_at, 0) DESC, id) AS n FROM (${picked.sql}))
518
519
  SELECT p.n, 0 AS kind, t.id AS a, r.position AS b, r.id, t.meeting_id, r.start_ms, coalesce(length(CAST(r.text AS BLOB)),0) AS bytes,
519
- r.speaker_name AS speaker, m.account_id
520
+ r.speaker_name AS speaker, m.account_id, t.language,
521
+ (SELECT count(*) FROM meeting_transcript_rows n WHERE n.meeting_transcript_id = t.id) AS lines_total
520
522
  FROM hit h JOIN meeting_transcript_rows r ON h.r % 4 = 1 AND r.id = h.r / 4
521
523
  JOIN meeting_transcripts t ON t.id = r.meeting_transcript_id
522
524
  AND t.superseded_at IS NULL AND t.deleted_at IS NULL
523
525
  JOIN picked p ON p.id = t.meeting_id JOIN meetings m ON m.id = t.meeting_id
524
526
  UNION ALL
525
- SELECT p.n, 1, c.id, 0, c.id, c.meeting_id, NULL, coalesce(length(CAST(c.text AS BLOB)),0), NULL, NULL
527
+ SELECT p.n, 1, c.id, 0, c.id, c.meeting_id, NULL, coalesce(length(CAST(c.text AS BLOB)),0), NULL, NULL, NULL, NULL
526
528
  FROM hit h JOIN meeting_chat_messages c ON h.r % 4 = 2 AND c.id = h.r / 4
527
529
  JOIN picked p ON p.id = c.meeting_id
528
530
  UNION ALL
529
- SELECT p.n, 2, s.id, 0, s.id, s.meeting_id, NULL, coalesce(length(CAST(s.title AS BLOB)),0)+coalesce(length(CAST(s.overview AS BLOB)),0)+coalesce(length(CAST(s.sections AS BLOB)),0)+coalesce(length(CAST(s.next_steps AS BLOB)),0)+coalesce(length(CAST(s.content AS BLOB)),0), NULL, NULL
531
+ SELECT p.n, 2, s.id, 0, s.id, s.meeting_id, NULL, coalesce(length(CAST(s.title AS BLOB)),0)+coalesce(length(CAST(s.overview AS BLOB)),0)+coalesce(length(CAST(s.sections AS BLOB)),0)+coalesce(length(CAST(s.next_steps AS BLOB)),0)+coalesce(length(CAST(s.content AS BLOB)),0), NULL, NULL, NULL, NULL
530
532
  FROM hit h JOIN meeting_summaries s ON h.r % 4 = 3 AND s.id = h.r / 4
531
533
  JOIN picked p ON p.id = s.meeting_id
532
534
  ORDER BY 1, 2, 3, 4 LIMIT 10001`)
@@ -551,6 +553,9 @@ const search = (database, query, filter) => {
551
553
  cuePosition: Number(row.b),
552
554
  }),
553
555
  speakerName: row.speaker === null ? null : String(row.speaker),
556
+ language: row.language === null ? null : String(row.language),
557
+ line: Number(row.b) + 1,
558
+ linesTotal: Number(row.lines_total),
554
559
  }
555
560
  : {};
556
561
  return {
@@ -596,6 +601,7 @@ export const meetingStoreOver = (context) => {
596
601
  ...meetingParticipantSearchOver(context),
597
602
  ...meetingHitIndexOver(context),
598
603
  ...meetingContextLinksOver(context),
604
+ ...meetingLanguagesOver(context),
599
605
  async appendTranscriptsWithReceipt(input) {
600
606
  try {
601
607
  return append(context, input);
@@ -2,7 +2,7 @@ import { CliError } from "@wirecat/cli-base";
2
2
  import { tagOf } from "../../domain/tags.js";
3
3
  import { compileAutomaton, foldRegex, wildcardPattern, } from "../../search/lucene/automaton.js";
4
4
  import { isStemmed } from "../../search/lucene/resolved.js";
5
- import { exhausted, QUERY_LIMITS, searchError } from "../../search/lucene/types.js";
5
+ import { exhausted, outOfTime, QUERY_LIMITS, searchError } from "../../search/lucene/types.js";
6
6
  import { normalize } from "../normalize.js";
7
7
  import { contextDocumentTime } from "./context-document-source.js";
8
8
  import { documentPicturesOver } from "./document-pictures.js";
@@ -126,7 +126,7 @@ export const searchCorpus = (context, query, corpus) => {
126
126
  if (query.signal?.aborted)
127
127
  throw new CliError("validation_error", "search was aborted", { reason: "query.aborted", complete: false });
128
128
  if (context.now() - started > QUERY_LIMITS.milliseconds)
129
- exhausted("time");
129
+ outOfTime();
130
130
  };
131
131
  const tests = new Map();
132
132
  const fragments = new Map();
@@ -5,7 +5,7 @@ export declare const projectAnnotationSnapshot: (database: CacheDatabase, projec
5
5
  format: "wirecat.project-annotations";
6
6
  facts: {
7
7
  ref: string;
8
- type: "decision" | "fact" | "promise" | "question" | "statement";
8
+ type: "decision" | "fact" | "payment" | "promise" | "question" | "statement";
9
9
  statement: string;
10
10
  status: "accepted" | "proposed" | "rejected" | "reversed" | "superseded";
11
11
  subject: string | null;
@@ -5,6 +5,7 @@ import { type Link } from "./notes.js";
5
5
  import type { StoreContext } from "./open.js";
6
6
  import { type ProposedAction } from "./proposed-actions.js";
7
7
  import { type Thing } from "./things.js";
8
+ type Row = Record<string, unknown>;
8
9
  export interface KnowledgeRelation {
9
10
  id: string;
10
11
  from: string;
@@ -32,7 +33,23 @@ export interface RelateInput {
32
33
  export declare const relationOf: (link: Link) => KnowledgeRelation;
33
34
  /** `relationship` proposals in `proposed_actions`: an agent's or a rule's, and the rejected ones a scan skips. */
34
35
  export declare const relationProposalsOver: (context: StoreContext) => {
35
- /** The same pending pair returns the proposal already held. */
36
+ /**
37
+ * Where a pair stands before a scan proposes it: a link joins it, a `relationship` proposal for it was rejected,
38
+ * or one is pending. A rejection is matched by the pair alone, whoever proposed it and with whatever scorer.
39
+ */
40
+ state: (paired: () => {
41
+ from: Thing;
42
+ existing: Row | undefined;
43
+ }, { to, kind }: {
44
+ to: string;
45
+ kind: string;
46
+ }) => {
47
+ linked: boolean;
48
+ confirmed: boolean;
49
+ rejected: boolean;
50
+ pending: ProposedAction | null;
51
+ } | undefined;
52
+ /** The same pending pair returns the proposal already held; a scored one updates its score in place. */
36
53
  propose: (from: Thing, input: {
37
54
  from: string;
38
55
  to: string;
@@ -41,7 +58,7 @@ export declare const relationProposalsOver: (context: StoreContext) => {
41
58
  purpose?: string;
42
59
  evidence?: string[];
43
60
  reason?: string;
44
- }, by: Exclude<Author, "owner">) => ProposedAction;
61
+ } & Scored, by: Exclude<Author, "owner">) => ProposedAction;
45
62
  rejected: (link: Link, by: Decider, reason?: string) => void;
46
63
  update: (link: Link, input: RelationUpdate) => Link;
47
64
  proposeUpdate: (link: Link, input: RelationUpdate & {
@@ -62,6 +79,15 @@ export declare const relationProposalsOver: (context: StoreContext) => {
62
79
  /** Decides an unconfirmed link written before proposals were stored; only the owner may, as its author is unknown. */
63
80
  decideLink: (id: string, decision: LinkDecision) => Link | null;
64
81
  };
82
+ /** What a scan adds to a relationship proposal: the combined score and each scorer's part in it. */
83
+ export interface Scored {
84
+ score?: number;
85
+ signals?: {
86
+ scorer: string;
87
+ weight: number;
88
+ why: string;
89
+ }[];
90
+ }
65
91
  /** A relationship's role and purpose to set; `null` clears one. */
66
92
  export interface RelationUpdate {
67
93
  role?: string | null;
@@ -72,3 +98,4 @@ export interface LinkDecision {
72
98
  by: Decider;
73
99
  reason?: string;
74
100
  }
101
+ export {};
@@ -1,5 +1,5 @@
1
1
  import { CliError } from "@wirecat/cli-base";
2
- import { assertMayDecide, deciderAuthor } from "../decisions.js";
2
+ import { assertMayDecide, botKey, deciderAuthor } from "../decisions.js";
3
3
  import { actorOfOrigin, botNamed, ownerPerson } from "./actors.js";
4
4
  import { insertLink, linkOf, purposeOf } from "./notes.js";
5
5
  import { proposalOperationsOver, proposerOfAction } from "./proposed-actions.js";
@@ -47,19 +47,74 @@ export const relationProposalsOver = (context) => {
47
47
  .run(Number("role" in changes), changes.role ?? null, Number("purpose" in changes), changes.purpose ?? null, now(), Number(link.id));
48
48
  return linkOf(database, database.prepare("SELECT * FROM links WHERE id = ?").get(Number(link.id)));
49
49
  };
50
+ /**
51
+ * A scan's newer score on a pending proposal, in place under the same id. A rule's own row takes the new score,
52
+ * signals, evidence and purpose; another proposer's row keeps its author, evidence and purpose and gains the signals.
53
+ */
54
+ const rescored = (action, input, scored, by) => {
55
+ const proposer = proposerOfAction(action);
56
+ const own = proposer.type === "bot" && proposer.kind === "rule" && botKey(proposer.name) === botKey(by.bot);
57
+ const before = action.payload;
58
+ const payload = own
59
+ ? {
60
+ ...before,
61
+ ...scored,
62
+ evidence: input.evidence ?? [],
63
+ ...(input.purpose === undefined ? {} : { purpose: purposeOf(input.purpose) }),
64
+ }
65
+ : { ...before, ...scored };
66
+ const reason = own && input.reason !== undefined ? input.reason : action.reason;
67
+ if (JSON.stringify(payload) === JSON.stringify(before) && reason === action.reason)
68
+ return action;
69
+ database
70
+ .prepare("UPDATE proposed_actions SET payload = ?, reason = ?, updated_at = ? WHERE id = ? AND status = 'proposed'")
71
+ .run(JSON.stringify(payload), reason, now(), Number(action.id));
72
+ return operations.get(action.id);
73
+ };
50
74
  return {
51
- /** The same pending pair returns the proposal already held. */
75
+ /**
76
+ * Where a pair stands before a scan proposes it: a link joins it, a `relationship` proposal for it was rejected,
77
+ * or one is pending. A rejection is matched by the pair alone, whoever proposed it and with whatever scorer.
78
+ */
79
+ state: (paired, { to, kind }) => {
80
+ let found;
81
+ try {
82
+ found = paired();
83
+ }
84
+ catch (error) {
85
+ if (error instanceof CliError && (error.code === "not_found" || error.code === "validation_error"))
86
+ return;
87
+ throw error;
88
+ }
89
+ const { from, existing } = found;
90
+ const rows = database
91
+ .prepare("SELECT id, status FROM proposed_actions WHERE kind='relationship' AND status IN ('proposed','rejected') " +
92
+ "AND target_type=? AND target_id=? AND json_extract(payload,'$.to')=? AND json_extract(payload,'$.kind')=? " +
93
+ "ORDER BY id")
94
+ .all(from.type, from.id, to, kind);
95
+ const pending = rows.find((row) => row.status === "proposed");
96
+ return {
97
+ linked: existing !== undefined,
98
+ confirmed: existing !== undefined && Number(existing.confirmed) === 1,
99
+ rejected: rows.some((row) => row.status === "rejected"),
100
+ pending: pending ? operations.get(String(pending.id)) : null,
101
+ };
102
+ },
103
+ /** The same pending pair returns the proposal already held; a scored one updates its score in place. */
52
104
  propose: (from, input, by) => {
53
105
  const evidence = input.evidence ?? [];
54
106
  if (!Array.isArray(evidence) || evidence.length > 5 || evidence.some((ref) => typeof ref !== "string"))
55
107
  throw new CliError("validation_error", "a relationship proposal cites up to 5 references");
108
+ const scored = scoredOf(input);
56
109
  return write(database, () => {
57
110
  const held = database
58
111
  .prepare("SELECT id FROM proposed_actions WHERE kind='relationship' AND status='proposed' AND target_type=? AND target_id=? " +
59
112
  "AND json_extract(payload,'$.to')=? AND json_extract(payload,'$.kind')=? ORDER BY id LIMIT 1")
60
113
  .get(from.type, from.id, input.to, input.kind);
61
- if (held)
62
- return operations.get(String(held.id));
114
+ if (held) {
115
+ const action = operations.get(String(held.id));
116
+ return scored ? rescored(action, input, scored, by) : action;
117
+ }
63
118
  return operations.propose({
64
119
  kind: "relationship",
65
120
  by,
@@ -71,6 +126,7 @@ export const relationProposalsOver = (context) => {
71
126
  ...(input.role === undefined ? {} : { role: input.role }),
72
127
  ...(input.purpose === undefined ? {} : { purpose: purposeOf(input.purpose) }),
73
128
  evidence,
129
+ ...scored,
74
130
  },
75
131
  ...(input.reason === undefined ? {} : { reason: input.reason }),
76
132
  });
@@ -174,6 +230,21 @@ export const relationProposalsOver = (context) => {
174
230
  }),
175
231
  };
176
232
  };
233
+ const scoredOf = (input) => {
234
+ if (input.score === undefined && input.signals === undefined)
235
+ return undefined;
236
+ const { score, signals } = input;
237
+ if (typeof score !== "number" || !(score >= 0 && score <= 1) || !Array.isArray(signals) || signals.length > 20)
238
+ throw new CliError("validation_error", "a scored proposal takes a score of 0–1 and up to 20 signals");
239
+ for (const signal of signals)
240
+ if (typeof signal?.scorer !== "string" ||
241
+ typeof signal.why !== "string" ||
242
+ signal.why.length > 500 ||
243
+ typeof signal.weight !== "number" ||
244
+ !(signal.weight >= 0 && signal.weight <= 1))
245
+ throw new CliError("validation_error", "a signal names its scorer, a weight of 0–1 and why, up to 500 characters");
246
+ return { score, signals: signals.map(({ scorer, weight, why }) => ({ scorer, weight, why })) };
247
+ };
177
248
  /** What a relationship update sets, checked; `null` clears a field and an absent one is left alone. */
178
249
  const changesOf = (input) => {
179
250
  const changes = {};
@@ -112,6 +112,7 @@ export interface MessageStore {
112
112
  forAccount(key: AccountKey): AccountStore;
113
113
  close(): Promise<void>;
114
114
  }
115
+ export type { PaymentFilter } from "./sqlite/facts.js";
115
116
  export type { ChatHistoryShape, StoredHistorySkip } from "./sqlite/history-skips.js";
116
117
  export { BACKFILL_ON_OPEN, type StoreOptions } from "./sqlite/open-store.js";
117
118
  export { fileFacts, readStoreTables, type TableKind } from "./sqlite/store-tables.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wirecat/cli-core",
3
- "version": "0.550.0",
3
+ "version": "0.552.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",
@@ -251,7 +251,7 @@
251
251
  "@types/node": "^22.20.5",
252
252
  "@types/pngjs": "6.0.5",
253
253
  "@vitest/coverage-v8": "^5.0.5",
254
- "@wirecat/cli-base": "0.74.0",
254
+ "@wirecat/cli-base": "0.75.0",
255
255
  "@wirecat/cli-core-legacy-tags": "npm:@wirecat/cli-core@0.414.0",
256
256
  "commander": "15.0.0",
257
257
  "cspell": "10.4.0",
@@ -271,7 +271,7 @@
271
271
  },
272
272
  "peerDependencies": {
273
273
  "@napi-rs/canvas": ">=0.1.69 <2",
274
- "@wirecat/cli-base": ">=0.74.0 <0.75.0",
274
+ "@wirecat/cli-base": ">=0.75.0 <0.76.0",
275
275
  "commander": ">=15"
276
276
  },
277
277
  "bin": {
package/parity.json CHANGED
@@ -3715,6 +3715,17 @@
3715
3715
  "--sync-time": "all"
3716
3716
  }
3717
3717
  },
3718
+ "projects status": {
3719
+ "in": ["max", "tg"],
3720
+ "planned": {
3721
+ "wa": "wa backlog: mount projectsCommand once wa is on cli-core 0.504.0"
3722
+ },
3723
+ "options": {
3724
+ "--before-time": "all",
3725
+ "--context-query": "all",
3726
+ "--since-time": "all"
3727
+ }
3728
+ },
3718
3729
  "reactions": {
3719
3730
  "in": "all"
3720
3731
  },