@wirecat/cli-core 0.362.0 → 0.363.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 +13 -0
  2. package/dist/cli/index.d.ts +1 -1
  3. package/dist/cli/index.js +1 -1
  4. package/dist/cli/meeting-findings-input.d.ts +2 -1
  5. package/dist/cli/meeting-findings-input.js +10 -7
  6. package/dist/context/evidence.d.ts +6 -0
  7. package/dist/context/evidence.js +4 -0
  8. package/dist/errors/reasons.js +2 -0
  9. package/dist/meetings/findings-rules.d.ts +60 -0
  10. package/dist/meetings/findings-rules.js +117 -0
  11. package/dist/meetings/findings.d.ts +10 -48
  12. package/dist/meetings/findings.js +10 -116
  13. package/dist/meetings/index.d.ts +1 -0
  14. package/dist/meetings/index.js +1 -0
  15. package/dist/services/context-evidence.d.ts +2 -1
  16. package/dist/services/context-evidence.js +1 -0
  17. package/dist/services/index.d.ts +3 -2
  18. package/dist/services/index.js +2 -1
  19. package/dist/services/project-gathering.d.ts +1 -0
  20. package/dist/services/project-gathering.js +3 -0
  21. package/dist/services/proposal-tasks.js +5 -0
  22. package/dist/store/evidence-findings.d.ts +63 -0
  23. package/dist/store/evidence-findings.js +15 -0
  24. package/dist/store/index.d.ts +1 -0
  25. package/dist/store/index.js +1 -0
  26. package/dist/store/sqlite/context-evidence.d.ts +9 -1
  27. package/dist/store/sqlite/context-evidence.js +43 -1
  28. package/dist/store/sqlite/evidence-findings.d.ts +3 -0
  29. package/dist/store/sqlite/evidence-findings.js +172 -0
  30. package/dist/store/sqlite/findings-core.d.ts +17 -0
  31. package/dist/store/sqlite/findings-core.js +108 -0
  32. package/dist/store/sqlite/meeting-findings.js +47 -121
  33. package/dist/store/sqlite/proposed-actions.d.ts +4 -1
  34. package/dist/store/sqlite/proposed-actions.js +12 -4
  35. package/dist/store/store.d.ts +5 -1
  36. package/dist/store/store.js +10 -1
  37. package/package.json +1 -1
@@ -1,12 +1,9 @@
1
1
  import { CliError } from "@wirecat/cli-base";
2
2
  import { findingHash, MEETING_FINDINGS_KIND, meetingCueFingerprint, meetingFindingFingerprint, parseMeetingReference, validateMeetingFindings, } from "../../meetings/index.js";
3
- import { factOperationsOver } from "./facts.js";
4
- import { authorActor, linkEvidence } from "./memories.js";
5
- import { requiredThing } from "./notes.js";
3
+ import { findingsCoreOver } from "./findings-core.js";
4
+ import { authorActor } from "./memories.js";
6
5
  import { proposalOperationsOver } from "./proposed-actions.js";
7
- import { taskRowOf } from "./things.js";
8
6
  import { write } from "./transaction.js";
9
- import { workTaskOperationsOver } from "./work-tasks.js";
10
7
  const stale = () => {
11
8
  throw new CliError("validation_error", "meeting findings changed since they were read; stage a new batch", {
12
9
  reason: "meeting-findings.stale",
@@ -18,8 +15,6 @@ const invalid = (message) => {
18
15
  export const meetingFindingsStoreOver = (context) => {
19
16
  const { database } = context;
20
17
  const proposals = proposalOperationsOver(context);
21
- const facts = factOperationsOver(context);
22
- const tasks = workTaskOperationsOver(context);
23
18
  const accountId = (key) => {
24
19
  if (!key ||
25
20
  typeof key.provider !== "string" ||
@@ -68,109 +63,56 @@ export const meetingFindingsStoreOver = (context) => {
68
63
  receipt: proposal.result,
69
64
  };
70
65
  };
66
+ const core = findingsCoreOver(context, MEETING_FINDINGS_KIND);
71
67
  const snapshot = (account, input) => {
72
68
  let readBytes = 0;
73
- const seen = new Map();
74
- for (const finding of input.findings) {
75
- const project = requiredThing(database, finding.project, "the project");
76
- if (project.type !== "project")
77
- return invalid("a finding needs an owner project");
78
- const projectRow = database
79
- .prepare("SELECT id,key,name,description,type,scope,updated_at FROM projects WHERE id=? AND account_id IS NULL AND deleted_at IS NULL")
80
- .get(project.id);
81
- if (!projectRow)
82
- throw new CliError("not_found", "the owner project is unavailable");
83
- seen.set(finding.project, projectRow);
84
- if (finding.kind === "task" && finding.assignee) {
85
- const person = requiredThing(database, finding.assignee, "the assignee");
86
- if (person.type !== "person")
87
- return invalid("the assignee must be a stored person");
88
- const personRow = database.prepare("SELECT id,name,updated_at FROM persons WHERE id=?").get(person.id);
89
- if (!personRow)
90
- throw new CliError("not_found", "the assignee is unavailable");
91
- seen.set(finding.assignee, personRow);
92
- }
93
- for (const evidence of finding.evidence) {
94
- const parsed = parseMeetingReference(evidence.reference);
95
- if (parsed.accountId !== account)
96
- throw new CliError("not_found", "the cited cue belongs to another account");
97
- const membership = database
98
- .prepare("SELECT id,updated_at,confirmed,source FROM links WHERE from_type='meeting' AND from_id=? AND to_type='project' AND to_id=? AND kind='member-of' AND anchor IS NULL AND confirmed=1")
99
- .get(parsed.meetingId, project.id);
100
- if (!membership)
101
- return invalid("each cited meeting needs confirmed membership in the finding's project");
102
- seen.set(`${finding.project}/${parsed.meetingId}`, membership);
103
- if (seen.has(evidence.reference)) {
104
- if (seen.get(evidence.reference) !== evidence.fingerprint)
105
- return stale();
106
- continue;
107
- }
108
- const query = "FROM meeting_transcript_rows r JOIN meeting_transcripts t ON t.id=r.meeting_transcript_id JOIN meetings m ON m.id=t.meeting_id WHERE m.id=? AND m.account_id=? AND m.deleted_at IS NULL AND t.id=? AND t.deleted_at IS NULL AND t.superseded_at IS NULL AND r.position=?";
109
- const params = [parsed.meetingId, account, parsed.transcriptId, parsed.cuePosition];
110
- const budget = database
111
- .prepare(`SELECT length(CAST(r.text AS BLOB))+coalesce(length(CAST(r.speaker_name AS BLOB)),0)+coalesce(length(CAST(t.content_hash AS BLOB)),0) AS bytes ${query}`)
112
- .get(...params);
113
- if (!budget)
114
- return stale();
115
- readBytes += Number(budget.bytes);
116
- if (!Number.isSafeInteger(readBytes) || readBytes > 65536)
117
- return invalid("finding evidence exceeds the 65536-byte stored text budget");
118
- const row = database
119
- .prepare(`SELECT t.content_hash,r.start_ms,r.end_ms,r.speaker_participant_id,r.speaker_name,r.text ${query}`)
120
- .get(...params);
121
- if (!row)
69
+ return core.snapshot(input, (evidence, finding, projectId, seen) => {
70
+ const parsed = parseMeetingReference(evidence.reference);
71
+ if (parsed.accountId !== account)
72
+ throw new CliError("not_found", "the cited cue belongs to another account");
73
+ const membership = database
74
+ .prepare("SELECT id,updated_at,confirmed,source FROM links WHERE from_type='meeting' AND from_id=? AND to_type='project' AND to_id=? AND kind='member-of' AND anchor IS NULL AND confirmed=1")
75
+ .get(parsed.meetingId, projectId);
76
+ if (!membership)
77
+ return invalid("each cited meeting needs confirmed membership in the finding's project");
78
+ seen.set(`${finding.project}/${parsed.meetingId}`, membership);
79
+ if (seen.has(evidence.reference)) {
80
+ if (seen.get(evidence.reference) !== evidence.fingerprint)
122
81
  return stale();
123
- const fingerprint = meetingCueFingerprint({
124
- reference: evidence.reference,
125
- transcriptId: parsed.transcriptId,
126
- contentHash: row.content_hash == null ? null : String(row.content_hash),
127
- position: parsed.cuePosition,
128
- startMs: Number(row.start_ms),
129
- endMs: Number(row.end_ms),
130
- speakerParticipantId: row.speaker_participant_id == null ? null : Number(row.speaker_participant_id),
131
- speakerName: row.speaker_name == null ? null : String(row.speaker_name),
132
- text: String(row.text),
133
- });
134
- if (fingerprint !== evidence.fingerprint)
135
- return stale();
136
- seen.set(evidence.reference, fingerprint);
82
+ return;
137
83
  }
138
- }
139
- return findingHash([...seen].sort(([a], [b]) => a.localeCompare(b)));
140
- };
141
- const previous = (account, fingerprint) => {
142
- const row = database
143
- .prepare("SELECT substr(j.value,1,4096) AS item FROM proposed_actions p,json_each(p.result,'$.items') j WHERE p.account_id=? AND p.kind=? AND p.status='approved' AND json_extract(j.value,'$.fingerprint')=? ORDER BY p.id LIMIT 1")
144
- .get(account, MEETING_FINDINGS_KIND, fingerprint);
145
- return row ? JSON.parse(String(row.item)) : undefined;
146
- };
147
- const create = (finding, agent) => {
148
- const evidence = finding.evidence.map((item) => item.reference);
149
- if (finding.kind === "fact") {
150
- const fact = facts.add({
151
- type: finding.type,
152
- statement: finding.statement,
153
- project: finding.project,
154
- evidence,
155
- by: agent,
84
+ const query = "FROM meeting_transcript_rows r JOIN meeting_transcripts t ON t.id=r.meeting_transcript_id JOIN meetings m ON m.id=t.meeting_id WHERE m.id=? AND m.account_id=? AND m.deleted_at IS NULL AND t.id=? AND t.deleted_at IS NULL AND t.superseded_at IS NULL AND r.position=?";
85
+ const params = [parsed.meetingId, account, parsed.transcriptId, parsed.cuePosition];
86
+ const budget = database
87
+ .prepare(`SELECT length(CAST(r.text AS BLOB))+coalesce(length(CAST(r.speaker_name AS BLOB)),0)+coalesce(length(CAST(t.content_hash AS BLOB)),0) AS bytes ${query}`)
88
+ .get(...params);
89
+ if (!budget)
90
+ return stale();
91
+ readBytes += Number(budget.bytes);
92
+ if (!Number.isSafeInteger(readBytes) || readBytes > 65536)
93
+ return invalid("finding evidence exceeds the 65536-byte stored text budget");
94
+ const row = database
95
+ .prepare(`SELECT t.content_hash,r.start_ms,r.end_ms,r.speaker_participant_id,r.speaker_name,r.text ${query}`)
96
+ .get(...params);
97
+ if (!row)
98
+ return stale();
99
+ const fingerprint = meetingCueFingerprint({
100
+ reference: evidence.reference,
101
+ transcriptId: parsed.transcriptId,
102
+ contentHash: row.content_hash == null ? null : String(row.content_hash),
103
+ position: parsed.cuePosition,
104
+ startMs: Number(row.start_ms),
105
+ endMs: Number(row.end_ms),
106
+ speakerParticipantId: row.speaker_participant_id == null ? null : Number(row.speaker_participant_id),
107
+ speakerName: row.speaker_name == null ? null : String(row.speaker_name),
108
+ text: String(row.text),
156
109
  });
157
- return facts.accept(fact.ref).ref;
158
- }
159
- const task = tasks.create({
160
- project: finding.project,
161
- title: finding.title,
162
- type: finding.type,
163
- description: `${finding.commitment}: ${finding.reason}`,
164
- ...(finding.dueAt ? { dueAt: new Date(finding.dueAt) } : {}),
165
- }, agent);
166
- const id = taskRowOf(database, task.id);
167
- if (id === undefined)
168
- throw new CliError("configuration_error", "created finding task is unavailable");
169
- linkEvidence(context, { type: finding.type, id }, evidence, "agent");
170
- if (finding.assignee)
171
- tasks.assign(task.id, { to: finding.assignee, role: "assignee" }, agent);
172
- return task.ref;
110
+ if (fingerprint !== evidence.fingerprint)
111
+ return stale();
112
+ seen.set(evidence.reference, fingerprint);
113
+ });
173
114
  };
115
+ const previous = core.previous;
174
116
  return {
175
117
  stage: async (key, raw, agent) => {
176
118
  const input = validateMeetingFindings(raw);
@@ -248,23 +190,7 @@ export const meetingFindingsStoreOver = (context) => {
248
190
  bot: proposal.proposedBy.name,
249
191
  ...(input.agentModel === null ? {} : { model: input.agentModel }),
250
192
  };
251
- const items = batch.input.findings.map((finding) => {
252
- const fingerprint = meetingFindingFingerprint(finding);
253
- const prior = previous(account, fingerprint);
254
- if (prior) {
255
- if (accepted.includes(finding.id) && prior.status === "rejected")
256
- return invalid("this unchanged finding was already rejected; changed evidence needs a new candidate");
257
- return { ...prior, id: finding.id, reused: true };
258
- }
259
- const status = accepted.includes(finding.id) ? "accepted" : "rejected";
260
- return {
261
- id: finding.id,
262
- fingerprint,
263
- status,
264
- reference: status === "accepted" ? create(finding, agent) : null,
265
- reused: false,
266
- };
267
- });
193
+ const items = core.outcomes(account, batch.input, accepted, agent);
268
194
  return view(proposals.approve(id, {
269
195
  previewFingerprint: batch.fingerprint,
270
196
  accepted,
@@ -63,8 +63,11 @@ export interface ProposedActionsStore {
63
63
  failed(id: string, error: string): Promise<ProposedAction>;
64
64
  judge(id: string, verdict: ProposedAction["verdict"]): Promise<ProposedAction>;
65
65
  }
66
- type ProposalOperations = {
66
+ type ProposalOperations = Omit<{
67
67
  [K in keyof ProposedActionsStore]: ProposedActionsStore[K] extends (...args: infer A) => Promise<infer R> ? (...args: A) => R : never;
68
+ }, "approve"> & {
69
+ /** `by` records an agent deciding for the owner; the public store always records the owner. */
70
+ approve(id: string, result?: unknown, by?: Author): ProposedAction;
68
71
  };
69
72
  export declare const proposalOperationsOver: (context: StoreContext) => ProposalOperations;
70
73
  export declare const proposedActionsStoreOver: (context: StoreContext) => ProposedActionsStore;
@@ -1,5 +1,6 @@
1
1
  import { CliError } from "@wirecat/cli-base";
2
2
  import { MEETING_FINDINGS_KIND } from "../../meetings/index.js";
3
+ import { EVIDENCE_FINDINGS_KIND } from "../evidence-findings.js";
3
4
  import { authorActor } from "./memories.js";
4
5
  import { requiredThing } from "./notes.js";
5
6
  import { referenceOfThing, storedThing } from "./things.js";
@@ -60,8 +61,8 @@ export const proposalOperationsOver = (context) => {
60
61
  .run(to, now(), ...values, Number(id));
61
62
  return actionOf(rowOf(id));
62
63
  });
63
- const decide = (id, to, result) => {
64
- const { actor } = authorActor(context, "owner");
64
+ const decide = (id, to, result, by = "owner") => {
65
+ const { actor } = authorActor(context, by);
65
66
  return move(id, ["proposed"], to, ", decided_by_type = ?, decided_by_id = ?, decided_at = ?, result = ?", [
66
67
  actor.type,
67
68
  actor.id,
@@ -109,7 +110,7 @@ export const proposalOperationsOver = (context) => {
109
110
  .all(status ?? null, status ?? null, Math.min(Math.max(limit, 1), 500))
110
111
  .map(actionOf);
111
112
  },
112
- approve: (id, result) => decide(id, "approved", result),
113
+ approve: (id, result, by) => decide(id, "approved", result, by),
113
114
  reject: (id) => decide(id, "rejected"),
114
115
  executed: (id, result) => move(id, ["approved"], "executed", ", executed_at = ?, result = ?", [
115
116
  now(),
@@ -133,15 +134,22 @@ export const proposalOperationsOver = (context) => {
133
134
  export const proposedActionsStoreOver = (context) => {
134
135
  const operations = proposalOperationsOver(context);
135
136
  const generic = (id) => {
136
- if (operations.get(id).kind === MEETING_FINDINGS_KIND)
137
+ const kind = operations.get(id).kind;
138
+ if (kind === MEETING_FINDINGS_KIND)
137
139
  throw new CliError("validation_error", "review meeting findings through the dedicated findings command", {
138
140
  reason: "meeting-findings.dedicated-review",
139
141
  });
142
+ if (kind === EVIDENCE_FINDINGS_KIND)
143
+ throw new CliError("validation_error", "review evidence findings through the dedicated findings command", {
144
+ reason: "evidence-findings.dedicated-review",
145
+ });
140
146
  };
141
147
  return {
142
148
  propose: async (input) => {
143
149
  if (input.kind === MEETING_FINDINGS_KIND)
144
150
  throw new CliError("validation_error", "stage meeting findings through the dedicated findings command");
151
+ if (input.kind === EVIDENCE_FINDINGS_KIND)
152
+ throw new CliError("validation_error", "stage evidence findings through the dedicated findings command");
145
153
  return operations.propose(input);
146
154
  },
147
155
  get: async (id) => operations.get(id),
@@ -1,10 +1,11 @@
1
- import type { ContextEvidenceReader, ContextIndex } from "../context/evidence.js";
1
+ import type { ContextEvidenceReader, ContextIndex, ContextRecordReader } from "../context/evidence.js";
2
2
  import { type AccountStore } from "./account-store.js";
3
3
  import { type AccountsStore } from "./accounts.js";
4
4
  import { type AttachmentsStore } from "./attachments.js";
5
5
  import { type ChatsStore } from "./chats.js";
6
6
  import { type ConversationsStore } from "./conversations.js";
7
7
  import { type CountersStore } from "./counters.js";
8
+ import type { EvidenceFindingsStore } from "./evidence-findings.js";
8
9
  import type { MeetingFindingsStore } from "./meeting-findings.js";
9
10
  import { type MentionsStore } from "./mentions.js";
10
11
  import { type MessagesStore } from "./messages.js";
@@ -35,6 +36,7 @@ import { type VectorsStore } from "./vectors.js";
35
36
  export interface MessageStore {
36
37
  readonly contextEvidence?: ContextEvidenceReader;
37
38
  readonly contextIndex?: ContextIndex;
39
+ readonly contextRecords?: ContextRecordReader;
38
40
  /** Accounts, account groups and which account each app profile is. */
39
41
  readonly accounts: AccountsStore;
40
42
  /** One account's chats: the list, what left, what is complete, their metadata. */
@@ -77,6 +79,8 @@ export interface MessageStore {
77
79
  readonly catalog: CatalogStore;
78
80
  /** Choices that hold until replaced, each with its evidence. */
79
81
  readonly meetingFindings: MeetingFindingsStore;
82
+ /** Cited facts and follow-ups about one project from stored messages, mail and documents, for review. */
83
+ readonly evidenceFindings: EvidenceFindingsStore;
80
84
  readonly facts: FactsStore;
81
85
  /** What agents concluded: summaries, digests, facts, preferences, each with evidence and a scope. */
82
86
  readonly memories: MemoriesStore;
@@ -16,8 +16,9 @@ import { agentActionsStoreOver } from "./sqlite/agent-actions.js";
16
16
  import * as attachmentTexts from "./sqlite/attachment-texts.js";
17
17
  import { botUpdateStoreOver } from "./sqlite/bot-updates.js";
18
18
  import { catalogStoreOver } from "./sqlite/catalog.js";
19
- import { contextEvidenceOver, contextIndexOver } from "./sqlite/context-evidence.js";
19
+ import { contextEvidenceOver, contextIndexOver, contextRecordsOver } from "./sqlite/context-evidence.js";
20
20
  import { mailStoreOver } from "./sqlite/emails.js";
21
+ import { evidenceFindingsStoreOver } from "./sqlite/evidence-findings.js";
21
22
  import { factsStoreOver } from "./sqlite/facts.js";
22
23
  import { involvementStoreOver } from "./sqlite/involvements.js";
23
24
  import { knowledgeStoreOver } from "./sqlite/knowledge.js";
@@ -82,6 +83,7 @@ const storeOver = (context) => {
82
83
  };
83
84
  const contextEvidence = once(() => contextEvidenceOver(context));
84
85
  const contextIndex = once(() => contextIndexOver(context, stemmerFor));
86
+ const contextRecords = once(() => contextRecordsOver(context));
85
87
  const searches = once(() => searchesStoreOver(context));
86
88
  const queries = once(() => queriesStoreOver(context, stemmerFor));
87
89
  const searchIndex = once(() => searchIndexStoreOver(context));
@@ -93,6 +95,7 @@ const storeOver = (context) => {
93
95
  const notes = once(() => ({ ...notesStoreOver(context), ...noteSearchOver(context) }));
94
96
  const catalog = once(() => catalogStoreOver(context));
95
97
  const meetingFindings = once(() => meetingFindingsStoreOver(context));
98
+ const evidenceFindings = once(() => evidenceFindingsStoreOver(context));
96
99
  const facts = once(() => factsStoreOver(context));
97
100
  const memories = once(() => memoriesStoreOver(context));
98
101
  const proposedActions = once(() => proposedActionsStoreOver(context));
@@ -105,6 +108,9 @@ const storeOver = (context) => {
105
108
  get contextIndex() {
106
109
  return contextIndex();
107
110
  },
111
+ get contextRecords() {
112
+ return contextRecords();
113
+ },
108
114
  meetingVectors: meetingVectorsOver(context),
109
115
  get accounts() {
110
116
  return areas.accounts().whole;
@@ -175,6 +181,9 @@ const storeOver = (context) => {
175
181
  get meetingFindings() {
176
182
  return meetingFindings();
177
183
  },
184
+ get evidenceFindings() {
185
+ return evidenceFindings();
186
+ },
178
187
  get facts() {
179
188
  return facts();
180
189
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wirecat/cli-core",
3
- "version": "0.362.0",
3
+ "version": "0.363.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",