@wirecat/cli-core 0.357.0 → 0.358.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.
- package/CHANGELOG.md +9 -1
- package/dist/cli/meeting-communications.d.ts +1 -1
- package/dist/context/evidence-response.js +13 -3
- package/dist/context/evidence.d.ts +2 -2
- package/dist/context/evidence.js +1 -1
- package/dist/store/sqlite/context-evidence.js +4 -1
- package/dist/store/sqlite/context-meeting-source.d.ts +6 -0
- package/dist/store/sqlite/context-meeting-source.js +97 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,15 @@ Notable changes to `@wirecat/cli-core` (`@wirecat/cli-messaging` up to 0.261.0,
|
|
|
5
5
|
minor version may break callers, and says how under "Changed — may break callers". `pnpm docs:check` checks the
|
|
6
6
|
shape of this file.
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## 0.358.0 — 19.10.2026
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Meeting transcripts in bounded context reads:** `ContextSource.kind` accepts `meeting`; the native
|
|
13
|
+
reader returns current transcript cue references with `timeKind: "meeting-start"`. Account/time scope,
|
|
14
|
+
cursor validation, read-only snapshots and pre-hydration byte limits apply. Deleted/superseded and
|
|
15
|
+
queued cues are excluded; missing indexes and unknown archive coverage remain explicit. Hosts must
|
|
16
|
+
opt in to meeting sources; the reader does not fetch recordings, fill indexes or infer project links.
|
|
9
17
|
|
|
10
18
|
## 0.357.0 — 19.10.2026
|
|
11
19
|
|
|
@@ -37,7 +37,7 @@ export declare const mapPreparationCommunications: ({ items, ...section }: Meeti
|
|
|
37
37
|
title: string | null;
|
|
38
38
|
url: string | null;
|
|
39
39
|
text: string;
|
|
40
|
-
timeKind: "sent" | "source-modified" | "stored-update";
|
|
40
|
+
timeKind: "sent" | "source-modified" | "stored-update" | "meeting-start";
|
|
41
41
|
revision: number | null;
|
|
42
42
|
selectionId: string;
|
|
43
43
|
textTruncated: boolean;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
2
|
import { formatLocator, parseLocator } from "../domain/locator.js";
|
|
3
|
+
import { formatMeetingReference, parseMeetingReference } from "../meetings/index.js";
|
|
3
4
|
import { contextEvidenceRequest, contextSource, } from "./evidence.js";
|
|
4
5
|
export const contextSourceKey = (source) => JSON.stringify([source.kind, source.accountId, source.provider, source.account]);
|
|
5
6
|
export const validateContextEvidencePage = (page, request) => {
|
|
@@ -49,9 +50,9 @@ export const validateContextEvidencePage = (page, request) => {
|
|
|
49
50
|
fail();
|
|
50
51
|
if (item.revision !== null && (!Number.isSafeInteger(item.revision) || item.revision < 1))
|
|
51
52
|
fail();
|
|
52
|
-
if (
|
|
53
|
-
|
|
54
|
-
(source.kind === "
|
|
53
|
+
if (source.kind === "document"
|
|
54
|
+
? !["source-modified", "stored-update"].includes(item.timeKind)
|
|
55
|
+
: item.timeKind !== (source.kind === "meeting" ? "meeting-start" : "sent"))
|
|
55
56
|
fail();
|
|
56
57
|
const text = [item.text, item.title, item.url, item.provider, item.account, item.timeKind];
|
|
57
58
|
if (text.some((value) => value !== null && typeof value !== "string"))
|
|
@@ -61,6 +62,15 @@ export const validateContextEvidencePage = (page, request) => {
|
|
|
61
62
|
if (!Number.isSafeInteger(id) || id < 1 || item.ref !== `document:${id}`)
|
|
62
63
|
fail();
|
|
63
64
|
}
|
|
65
|
+
else if (source.kind === "meeting") {
|
|
66
|
+
const reference = parseMeetingReference(item.ref);
|
|
67
|
+
if (reference.accountId !== source.accountId ||
|
|
68
|
+
reference.transcriptId === undefined ||
|
|
69
|
+
reference.cuePosition === undefined ||
|
|
70
|
+
formatMeetingReference(reference) !== item.ref)
|
|
71
|
+
fail();
|
|
72
|
+
text.push(item.ref);
|
|
73
|
+
}
|
|
64
74
|
else {
|
|
65
75
|
const locator = parseLocator(item.ref);
|
|
66
76
|
if (locator.provider !== source.provider ||
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const CONTEXT_SOURCE_KINDS: readonly ["message", "mail", "document"];
|
|
1
|
+
export declare const CONTEXT_SOURCE_KINDS: readonly ["message", "mail", "document", "meeting"];
|
|
2
2
|
export interface ContextSource {
|
|
3
3
|
kind: (typeof CONTEXT_SOURCE_KINDS)[number];
|
|
4
4
|
accountId: number;
|
|
@@ -27,7 +27,7 @@ export interface ContextEvidenceItem {
|
|
|
27
27
|
url: string | null;
|
|
28
28
|
text: string;
|
|
29
29
|
at: number;
|
|
30
|
-
timeKind: "sent" | "source-modified" | "stored-update";
|
|
30
|
+
timeKind: "sent" | "source-modified" | "stored-update" | "meeting-start";
|
|
31
31
|
updatedAt: number;
|
|
32
32
|
revision: number | null;
|
|
33
33
|
}
|
package/dist/context/evidence.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { CliError } from "@wirecat/cli-base";
|
|
3
|
-
export const CONTEXT_SOURCE_KINDS = ["message", "mail", "document"];
|
|
3
|
+
export const CONTEXT_SOURCE_KINDS = ["message", "mail", "document", "meeting"];
|
|
4
4
|
export const contextCursorScope = (request) => createHash("sha256")
|
|
5
5
|
.update(JSON.stringify([
|
|
6
6
|
"context-evidence-v1",
|
|
@@ -3,6 +3,7 @@ import { contextCursorScope, contextEvidenceRequest, } from "../../context/evide
|
|
|
3
3
|
import { formatLocator } from "../../domain/locator.js";
|
|
4
4
|
import { NORMALIZER_VERSION, normalize } from "../normalize.js";
|
|
5
5
|
import { contextDocumentTime, contextDocumentTimeKind, contextDocumentUrl } from "./context-document-source.js";
|
|
6
|
+
import { readMeetingContextEvidence } from "./context-meeting-source.js";
|
|
6
7
|
import { drainEmailIndex } from "./email-index.js";
|
|
7
8
|
import { drainNoteIndex } from "./note-index.js";
|
|
8
9
|
import { read } from "./transaction.js";
|
|
@@ -52,10 +53,12 @@ export const contextEvidenceOver = ({ database }) => ({
|
|
|
52
53
|
async read(original) {
|
|
53
54
|
const request = contextEvidenceRequest(original);
|
|
54
55
|
const { source, words, after, before, limit, maxReadBytes, cursor, signal } = request;
|
|
56
|
+
if (source.kind === "meeting")
|
|
57
|
+
return readMeetingContextEvidence(database, request);
|
|
58
|
+
const corpus = corpora[source.kind];
|
|
55
59
|
const scope = contextCursorScope({ source, query: request.query, after, before });
|
|
56
60
|
signal?.throwIfAborted();
|
|
57
61
|
return read(database, () => {
|
|
58
|
-
const corpus = corpora[source.kind];
|
|
59
62
|
const coverage = {
|
|
60
63
|
available: false,
|
|
61
64
|
index: "unknown",
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type ContextEvidencePage, type contextEvidenceRequest } from "../../context/evidence.js";
|
|
2
|
+
import type { CacheDatabase } from "../driver.js";
|
|
3
|
+
type Request = ReturnType<typeof contextEvidenceRequest>;
|
|
4
|
+
/** Transcript cues keep their retained revision reference; a meeting title alone is not a transcript match. */
|
|
5
|
+
export declare const readMeetingContextEvidence: (database: CacheDatabase, request: Request) => ContextEvidencePage;
|
|
6
|
+
export {};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { CliError } from "@wirecat/cli-base";
|
|
2
|
+
import { contextCursorScope } from "../../context/evidence.js";
|
|
3
|
+
import { normalize } from "../normalize.js";
|
|
4
|
+
import { read } from "./transaction.js";
|
|
5
|
+
/** Transcript cues keep their retained revision reference; a meeting title alone is not a transcript match. */
|
|
6
|
+
export const readMeetingContextEvidence = (database, request) => {
|
|
7
|
+
const { source, words, after, before, limit, maxReadBytes, cursor, signal } = request;
|
|
8
|
+
signal?.throwIfAborted();
|
|
9
|
+
return read(database, () => {
|
|
10
|
+
const coverage = {
|
|
11
|
+
available: false,
|
|
12
|
+
index: "unknown",
|
|
13
|
+
hasMore: false,
|
|
14
|
+
hasUndated: false,
|
|
15
|
+
completeness: "unknown",
|
|
16
|
+
matching: "literal-words",
|
|
17
|
+
consistency: "snapshot-per-page",
|
|
18
|
+
};
|
|
19
|
+
const empty = () => ({ source, items: [], next: null, readBytes: 0, coverage });
|
|
20
|
+
const scope = [source.accountId, source.provider, source.account];
|
|
21
|
+
if (!database.prepare("SELECT 1 FROM accounts WHERE id=? AND provider=? AND external_id=?").get(...scope))
|
|
22
|
+
return empty();
|
|
23
|
+
coverage.available = true;
|
|
24
|
+
const base = `FROM meeting_transcript_rows r
|
|
25
|
+
JOIN meeting_transcripts t ON t.id=r.meeting_transcript_id
|
|
26
|
+
JOIN meetings m ON m.id=t.meeting_id
|
|
27
|
+
JOIN accounts a ON a.id=m.account_id
|
|
28
|
+
WHERE m.account_id=? AND a.provider=? AND a.external_id=?
|
|
29
|
+
AND m.deleted_at IS NULL AND t.deleted_at IS NULL AND t.superseded_at IS NULL`;
|
|
30
|
+
coverage.hasUndated = Boolean(database.prepare(`SELECT 1 ${base} AND m.started_at IS NULL LIMIT 1`).get(...scope));
|
|
31
|
+
if (!database.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='meeting_words'").get()) {
|
|
32
|
+
coverage.index = "missing";
|
|
33
|
+
return empty();
|
|
34
|
+
}
|
|
35
|
+
coverage.index = database
|
|
36
|
+
.prepare(`SELECT 1 ${base} AND m.started_at>=? AND m.started_at<?
|
|
37
|
+
AND EXISTS(SELECT 1 FROM meeting_index_pending p
|
|
38
|
+
WHERE p.indexable_type='meeting_transcript_row' AND p.id=r.id) LIMIT 1`)
|
|
39
|
+
.get(...scope, after, before)
|
|
40
|
+
? "pending"
|
|
41
|
+
: "ready";
|
|
42
|
+
const reference = "'meeting:'||m.account_id||'/'||m.id||'/'||t.id||'/'||r.position";
|
|
43
|
+
const selection = `SELECT r.id,m.title,r.text,a.provider,a.external_id AS account,
|
|
44
|
+
m.started_at AS at,max(m.updated_at,t.updated_at,r.created_at) AS updated_at,
|
|
45
|
+
${reference} AS ref ${base} AND m.started_at>=? AND m.started_at<?
|
|
46
|
+
AND (r.id*4+1) IN (SELECT rowid FROM meeting_words WHERE meeting_words MATCH ?)
|
|
47
|
+
AND NOT EXISTS(SELECT 1 FROM meeting_index_pending p
|
|
48
|
+
WHERE p.indexable_type='meeting_transcript_row' AND p.id=r.id)
|
|
49
|
+
${cursor ? "AND (m.started_at<? OR (m.started_at=? AND r.id<?))" : ""}
|
|
50
|
+
ORDER BY m.started_at DESC,r.id DESC LIMIT ?`;
|
|
51
|
+
const params = [
|
|
52
|
+
...scope,
|
|
53
|
+
after,
|
|
54
|
+
before,
|
|
55
|
+
words.map((word) => `normalized_text:"${normalize(word)}"`).join(" AND "),
|
|
56
|
+
];
|
|
57
|
+
if (cursor)
|
|
58
|
+
params.push(cursor.at, cursor.at, cursor.id);
|
|
59
|
+
params.push(limit + 1);
|
|
60
|
+
const probe = database
|
|
61
|
+
.prepare(`SELECT coalesce(sum(length(CAST(coalesce(title,'') AS BLOB))+
|
|
62
|
+
length(CAST(text AS BLOB))+length(CAST(provider AS BLOB))+
|
|
63
|
+
length(CAST(account AS BLOB))+length(CAST(ref AS BLOB))+length('meeting-start')),0) AS bytes FROM (${selection})`)
|
|
64
|
+
.get(...params);
|
|
65
|
+
const readBytes = Number(probe?.bytes);
|
|
66
|
+
if (!Number.isSafeInteger(readBytes) || readBytes < 0 || readBytes > maxReadBytes)
|
|
67
|
+
throw new CliError("validation_error", "meeting context evidence exceeds maxReadBytes before hydration", {
|
|
68
|
+
reason: "context.read-budget",
|
|
69
|
+
});
|
|
70
|
+
signal?.throwIfAborted();
|
|
71
|
+
const rows = database.prepare(selection).all(...params);
|
|
72
|
+
signal?.throwIfAborted();
|
|
73
|
+
coverage.hasMore = rows.length > limit;
|
|
74
|
+
const selected = rows.slice(0, limit);
|
|
75
|
+
const last = selected.at(-1);
|
|
76
|
+
return {
|
|
77
|
+
source,
|
|
78
|
+
readBytes,
|
|
79
|
+
coverage,
|
|
80
|
+
next: coverage.hasMore && last
|
|
81
|
+
? { at: Number(last.at), id: Number(last.id), scope: contextCursorScope(request) }
|
|
82
|
+
: null,
|
|
83
|
+
items: selected.map((row) => ({
|
|
84
|
+
ref: String(row.ref),
|
|
85
|
+
provider: String(row.provider),
|
|
86
|
+
account: String(row.account),
|
|
87
|
+
title: row.title === null ? null : String(row.title),
|
|
88
|
+
url: null,
|
|
89
|
+
text: String(row.text),
|
|
90
|
+
at: Number(row.at),
|
|
91
|
+
timeKind: "meeting-start",
|
|
92
|
+
updatedAt: Number(row.updated_at),
|
|
93
|
+
revision: null,
|
|
94
|
+
})),
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirecat/cli-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.358.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",
|