@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.
- package/CHANGELOG.md +235 -0
- package/README.md +1 -1
- package/THIRD_PARTY_NOTICES +41 -0
- package/dist/background/schedules.d.ts +12 -1
- package/dist/background/schedules.js +65 -24
- package/dist/background/units.d.ts +16 -9
- package/dist/background/units.js +26 -11
- package/dist/calendar/ics.d.ts +1 -1
- package/dist/calendar/ics.js +5 -2
- package/dist/calendar/windows-zones.generated.d.ts +1 -0
- package/dist/calendar/windows-zones.generated.js +146 -0
- package/dist/cli/compact.js +2 -0
- package/dist/cli/context.d.ts +2 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +3 -0
- package/dist/cli/meeting-findings-input.js +4 -3
- package/dist/cli/meeting-preparation-answer.d.ts +3 -0
- package/dist/cli/meeting-preparation-projects.d.ts +3 -0
- package/dist/cli/meeting-preparation-read.d.ts +3 -0
- package/dist/cli/messenger/backfill-command.js +28 -1
- package/dist/cli/messenger/context.js +1 -0
- package/dist/cli/messenger/history-gaps.js +4 -4
- package/dist/cli/messenger/mcp-command.js +1 -0
- package/dist/cli/messenger/messages-delete-command.js +7 -8
- package/dist/cli/messenger/projects-command.js +47 -2
- package/dist/cli/messenger/search-answers.js +8 -1
- package/dist/cli/messenger/serve-catch-up.d.ts +43 -0
- package/dist/cli/messenger/serve-catch-up.js +90 -0
- package/dist/cli/messenger/serve-command.js +54 -1
- package/dist/cli/messenger/server-command.d.ts +4 -3
- package/dist/cli/messenger/server-command.js +30 -12
- package/dist/cli/payment-query.d.ts +8 -0
- package/dist/cli/payment-query.js +27 -0
- package/dist/cli/program.js +9 -4
- package/dist/cli/project-status-rows.d.ts +8 -0
- package/dist/cli/project-status-rows.js +102 -0
- package/dist/cli/project-status.d.ts +132 -0
- package/dist/cli/project-status.js +248 -0
- package/dist/cli/schedule/schedule-command.js +5 -1
- package/dist/cli/steps.js +4 -1
- package/dist/domain/knowledge.d.ts +1 -1
- package/dist/domain/knowledge.js +1 -1
- package/dist/domain/status-words.d.ts +3 -0
- package/dist/domain/status-words.js +44 -0
- package/dist/entities/fact.d.ts +12 -1
- package/dist/entities/fact.js +13 -0
- package/dist/entities/hit.d.ts +5 -0
- package/dist/entities/hit.js +5 -0
- package/dist/entities/index.d.ts +45 -2
- package/dist/entities/listener.d.ts +6 -0
- package/dist/entities/listener.js +1 -0
- package/dist/entities/meeting-findings.d.ts +1 -1
- package/dist/entities/meeting-preparation.d.ts +3 -0
- package/dist/entities/message-transfer.d.ts +16 -0
- package/dist/entities/message-transfer.js +6 -0
- package/dist/errors/reasons.js +1 -0
- package/dist/mcp/app-server.js +1 -0
- package/dist/mcp/http/oauth.d.ts +2 -1
- package/dist/mcp/http/oauth.js +2 -2
- package/dist/mcp/http/serve.d.ts +2 -0
- package/dist/mcp/http/serve.js +2 -1
- package/dist/mcp/meeting-preparation.d.ts +3 -0
- package/dist/mcp/server.js +1 -0
- package/dist/mcp/surface.js +1 -1
- package/dist/meetings/answers.d.ts +5 -0
- package/dist/meetings/answers.js +8 -0
- package/dist/meetings/cli/index.js +14 -0
- package/dist/meetings/findings-rules.d.ts +14 -3
- package/dist/meetings/findings-rules.js +16 -2
- package/dist/meetings/index.d.ts +5 -3
- package/dist/meetings/index.js +3 -1
- package/dist/meetings/ingest.js +2 -1
- package/dist/meetings/language.d.ts +5 -0
- package/dist/meetings/language.js +57 -0
- package/dist/meetings/mcp/search.d.ts +3 -0
- package/dist/meetings/meeting-context.d.ts +2 -3
- package/dist/meetings/meeting-context.js +3 -4
- package/dist/meetings/payment.d.ts +27 -0
- package/dist/meetings/payment.js +81 -0
- package/dist/meetings/project-evidence-answer.d.ts +3 -0
- package/dist/meetings/result-items.d.ts +9 -0
- package/dist/meetings/result-items.js +7 -0
- package/dist/meetings/store.d.ts +22 -0
- package/dist/meetings/testing/memory.js +3 -0
- package/dist/meetings/transcription.d.ts +2 -0
- package/dist/meetings/transcription.js +5 -1
- package/dist/search/lucene/types.d.ts +7 -0
- package/dist/search/lucene/types.js +11 -1
- package/dist/services/archive.d.ts +13 -0
- package/dist/services/archive.js +56 -5
- package/dist/services/deps.d.ts +3 -0
- package/dist/services/history-limits.d.ts +2 -0
- package/dist/services/index.d.ts +4 -1
- package/dist/services/index.js +4 -1
- package/dist/services/job-query.d.ts +5 -2
- package/dist/services/job-query.js +5 -2
- package/dist/services/link-scorers/alias-hits.d.ts +7 -0
- package/dist/services/link-scorers/alias-hits.js +54 -0
- package/dist/services/link-scorers/chat-senders.d.ts +6 -0
- package/dist/services/link-scorers/chat-senders.js +56 -0
- package/dist/services/link-scorers/chat-title.d.ts +6 -0
- package/dist/services/link-scorers/chat-title.js +44 -0
- package/dist/services/link-scorers/email-domain.d.ts +6 -0
- package/dist/services/link-scorers/email-domain.js +64 -0
- package/dist/services/link-scorers/index.d.ts +9 -0
- package/dist/services/link-scorers/index.js +8 -0
- package/dist/services/link-scorers/mail-thread.d.ts +3 -0
- package/dist/services/link-scorers/mail-thread.js +74 -0
- package/dist/services/link-scorers/member-chats.d.ts +3 -0
- package/dist/services/link-scorers/member-chats.js +40 -0
- package/dist/services/link-scorers/member-dialog.d.ts +3 -0
- package/dist/services/link-scorers/member-dialog.js +34 -0
- package/dist/services/link-scorers/scan.d.ts +41 -0
- package/dist/services/link-scorers/scan.js +244 -0
- package/dist/services/link-scorers/shared.d.ts +26 -0
- package/dist/services/link-scorers/shared.js +48 -0
- package/dist/services/link-scorers/types.d.ts +77 -0
- package/dist/services/link-scorers/types.js +1 -0
- package/dist/services/messages-archive-search.js +3 -2
- package/dist/services/messages-combined.js +2 -2
- package/dist/services/messages-discovery.js +2 -2
- package/dist/services/notes-search.js +4 -5
- package/dist/services/project-status/index.d.ts +71 -0
- package/dist/services/project-status/index.js +196 -0
- package/dist/services/project-status/items.d.ts +10 -0
- package/dist/services/project-status/items.js +23 -0
- package/dist/services/project-status/signals/amounts.d.ts +20 -0
- package/dist/services/project-status/signals/amounts.js +91 -0
- package/dist/services/project-status/signals/children.d.ts +13 -0
- package/dist/services/project-status/signals/children.js +28 -0
- package/dist/services/project-status/signals/coverage.d.ts +13 -0
- package/dist/services/project-status/signals/coverage.js +30 -0
- package/dist/services/project-status/signals/index.d.ts +3 -0
- package/dist/services/project-status/signals/index.js +21 -0
- package/dist/services/project-status/signals/last-message.d.ts +4 -0
- package/dist/services/project-status/signals/last-message.js +22 -0
- package/dist/services/project-status/signals/payments.d.ts +9 -0
- package/dist/services/project-status/signals/payments.js +24 -0
- package/dist/services/project-status/signals/promises.d.ts +17 -0
- package/dist/services/project-status/signals/promises.js +109 -0
- package/dist/services/project-status/signals/questions.d.ts +8 -0
- package/dist/services/project-status/signals/questions.js +38 -0
- package/dist/services/project-status/signals/reply-next.d.ts +19 -0
- package/dist/services/project-status/signals/reply-next.js +75 -0
- package/dist/services/project-status/signals/topics.d.ts +10 -0
- package/dist/services/project-status/signals/topics.js +50 -0
- package/dist/services/project-status/types.d.ts +84 -0
- package/dist/services/project-status/types.js +1 -0
- package/dist/services/proposals/index.d.ts +1 -0
- package/dist/services/proposals/index.js +1 -0
- package/dist/services/proposals/kinds/findings.d.ts +3 -0
- package/dist/services/proposals/kinds/findings.js +8 -1
- package/dist/services/search-all-meetings.d.ts +3 -0
- package/dist/services/search-all-meetings.js +6 -1
- package/dist/speech/local.d.ts +2 -0
- package/dist/speech/local.js +8 -1
- package/dist/speech/models.d.ts +2 -0
- package/dist/speech/models.js +2 -0
- package/dist/store/decisions.d.ts +5 -1
- package/dist/store/decisions.js +5 -1
- package/dist/store/driver.d.ts +11 -0
- package/dist/store/driver.js +13 -0
- package/dist/store/drivers/node-sqlite.d.ts +1 -1
- package/dist/store/drivers/node-sqlite.js +3 -0
- package/dist/store/evidence-findings.js +1 -0
- package/dist/store/index.d.ts +2 -1
- package/dist/store/index.js +1 -0
- package/dist/store/project-transfer/request.d.ts +1 -1
- package/dist/store/project-transfer/schema.d.ts +2 -2
- package/dist/store/project-transfer/snapshot-model.d.ts +2 -2
- package/dist/store/sqlite/evidence-findings.js +5 -1
- package/dist/store/sqlite/facts.d.ts +13 -0
- package/dist/store/sqlite/facts.js +64 -6
- package/dist/store/sqlite/findings-core.js +14 -1
- package/dist/store/sqlite/knowledge.d.ts +16 -2
- package/dist/store/sqlite/knowledge.js +1 -0
- package/dist/store/sqlite/lucene.d.ts +1 -1
- package/dist/store/sqlite/lucene.js +33 -7
- package/dist/store/sqlite/meeting-hit-search.js +10 -3
- package/dist/store/sqlite/meeting-languages.d.ts +4 -0
- package/dist/store/sqlite/meeting-languages.js +36 -0
- package/dist/store/sqlite/meetings.d.ts +2 -1
- package/dist/store/sqlite/meetings.js +9 -3
- package/dist/store/sqlite/note-search.js +2 -2
- package/dist/store/sqlite/project-transfer/snapshot.d.ts +1 -1
- package/dist/store/sqlite/relation-proposals.d.ts +29 -2
- package/dist/store/sqlite/relation-proposals.js +75 -4
- package/dist/store/store.d.ts +1 -0
- package/package.json +3 -3
- package/parity.json +11 -0
package/dist/speech/models.js
CHANGED
|
@@ -8,6 +8,7 @@ export const MODELS = [
|
|
|
8
8
|
id: "gigaam-v3",
|
|
9
9
|
title: "GigaAM v3, with punctuation",
|
|
10
10
|
languages: "Russian — the best of the three for Russian",
|
|
11
|
+
language: "ru",
|
|
11
12
|
featureDim: 64,
|
|
12
13
|
files: [
|
|
13
14
|
{
|
|
@@ -45,6 +46,7 @@ export const MODELS = [
|
|
|
45
46
|
id: "gigaam-v3-ctc",
|
|
46
47
|
title: "GigaAM v3 CTC, with punctuation — a little faster",
|
|
47
48
|
languages: "Russian — a little faster, rougher with capital letters",
|
|
49
|
+
language: "ru",
|
|
48
50
|
featureDim: 64,
|
|
49
51
|
files: [
|
|
50
52
|
{
|
|
@@ -38,9 +38,12 @@ export interface Refusal {
|
|
|
38
38
|
* The one check every decide path runs before it confirms or rejects a proposal. `null` means allowed. Every Claude
|
|
39
39
|
* Code session is the bot `claude-code`, so comparing bot names keeps one session from deciding another's proposal.
|
|
40
40
|
*/
|
|
41
|
-
export declare const mayDecide: (
|
|
41
|
+
export declare const mayDecide: (
|
|
42
|
+
/** `ownerOnly`: what only the owner may say, such as that a payment was made. */
|
|
43
|
+
row: {
|
|
42
44
|
id: string;
|
|
43
45
|
proposedBy: Proposer;
|
|
46
|
+
ownerOnly?: boolean;
|
|
44
47
|
}, by: Decider, policy?: DecisionPolicy) => Refusal | null;
|
|
45
48
|
/** One bot whatever its letter case or spacing: `Claude-Code` is `claude-code`. */
|
|
46
49
|
export declare const botKey: (name: string) => string;
|
|
@@ -59,6 +62,7 @@ export declare const assertMayWithdraw: (row: {
|
|
|
59
62
|
export declare const assertMayDecide: (row: {
|
|
60
63
|
id: string;
|
|
61
64
|
proposedBy: Proposer;
|
|
65
|
+
ownerOnly?: boolean;
|
|
62
66
|
}, by: Decider, policy?: DecisionPolicy) => void;
|
|
63
67
|
/** The author a decision is recorded under. */
|
|
64
68
|
export declare const deciderAuthor: (by: Decider) => "owner" | {
|
package/dist/store/decisions.js
CHANGED
|
@@ -4,7 +4,9 @@ export const DEFAULT_DECISION_POLICY = { agentDecisions: true, ruleProposalsOwne
|
|
|
4
4
|
* The one check every decide path runs before it confirms or rejects a proposal. `null` means allowed. Every Claude
|
|
5
5
|
* Code session is the bot `claude-code`, so comparing bot names keeps one session from deciding another's proposal.
|
|
6
6
|
*/
|
|
7
|
-
export const mayDecide = (
|
|
7
|
+
export const mayDecide = (
|
|
8
|
+
/** `ownerOnly`: what only the owner may say, such as that a payment was made. */
|
|
9
|
+
row, by, policy = DEFAULT_DECISION_POLICY) => {
|
|
8
10
|
if (by === "owner")
|
|
9
11
|
return null;
|
|
10
12
|
const refuse = (code, reason, message) => ({
|
|
@@ -13,6 +15,8 @@ export const mayDecide = (row, by, policy = DEFAULT_DECISION_POLICY) => {
|
|
|
13
15
|
reason,
|
|
14
16
|
message,
|
|
15
17
|
});
|
|
18
|
+
if (row.ownerOnly)
|
|
19
|
+
return refuse("permission_error", "proposal.owner-only", `only the owner decides ${row.id}`);
|
|
16
20
|
if (!policy.agentDecisions)
|
|
17
21
|
return refuse("permission_error", "proposals.agent-decisions-off", "agent decisions are turned off; the owner decides");
|
|
18
22
|
if (typeof by.reason !== "string" || !by.reason.trim() || by.reason.length > 2000)
|
package/dist/store/driver.d.ts
CHANGED
|
@@ -23,7 +23,18 @@ export interface CacheDatabase {
|
|
|
23
23
|
exec(sql: string): void;
|
|
24
24
|
prepare(sql: string): CacheStatement;
|
|
25
25
|
close(): void;
|
|
26
|
+
/**
|
|
27
|
+
* Set when the connection has `TIME_LIMIT_FUNCTION`. A statement runs to its end before any timer can fire, so a
|
|
28
|
+
* long one is stopped from inside: `WHERE … AND wc_time_limit(<epoch ms>)` throws `TimeLimitReached` once past it.
|
|
29
|
+
*/
|
|
30
|
+
timeLimit?: true;
|
|
26
31
|
}
|
|
32
|
+
export declare const TIME_LIMIT_FUNCTION = "wc_time_limit";
|
|
33
|
+
export declare class TimeLimitReached extends Error {
|
|
34
|
+
constructor();
|
|
35
|
+
}
|
|
36
|
+
/** The SQL function's body: 1 until `until` (epoch milliseconds) has passed. */
|
|
37
|
+
export declare const beforeTimeLimit: (until: unknown) => number;
|
|
27
38
|
export type OpenDatabase = (path: string) => CacheDatabase;
|
|
28
39
|
/**
|
|
29
40
|
* **Write-ahead mode, and a timeout rather than an instant failure.**
|
package/dist/store/driver.js
CHANGED
|
@@ -9,6 +9,19 @@
|
|
|
9
9
|
* The surface is deliberately tiny. Everything above it is ordinary SQL, so replacing the driver —
|
|
10
10
|
* or adding a third — is writing thirty lines, not learning an abstraction.
|
|
11
11
|
*/
|
|
12
|
+
export const TIME_LIMIT_FUNCTION = "wc_time_limit";
|
|
13
|
+
export class TimeLimitReached extends Error {
|
|
14
|
+
constructor() {
|
|
15
|
+
super("the statement ran past its time limit");
|
|
16
|
+
this.name = "TimeLimitReached";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** The SQL function's body: 1 until `until` (epoch milliseconds) has passed. */
|
|
20
|
+
export const beforeTimeLimit = (until) => {
|
|
21
|
+
if (Date.now() > Number(until))
|
|
22
|
+
throw new TimeLimitReached();
|
|
23
|
+
return 1;
|
|
24
|
+
};
|
|
12
25
|
/**
|
|
13
26
|
* **Write-ahead mode, and a timeout rather than an instant failure.**
|
|
14
27
|
*
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DatabaseSync } from "node:sqlite";
|
|
2
|
-
import type
|
|
2
|
+
import { type CacheDatabase } from "../driver.js";
|
|
3
3
|
/**
|
|
4
4
|
* Import this module only through a dynamic import that follows `quietSqliteWarning()`: a static import loads
|
|
5
5
|
* `node:sqlite`, and on Node 22 its ExperimentalWarning, before the filter can run.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// biome-ignore-all lint/plugin/no-sqlite-driver: cli-core owns the store, so it opens the driver itself
|
|
2
2
|
import { DatabaseSync } from "node:sqlite";
|
|
3
|
+
import { beforeTimeLimit, TIME_LIMIT_FUNCTION, } from "../driver.js";
|
|
3
4
|
import { loadVectorExtension, vectorExtension } from "../vector-extension.js";
|
|
4
5
|
/**
|
|
5
6
|
* Import this module only through a dynamic import that follows `quietSqliteWarning()`: a static import loads
|
|
@@ -29,6 +30,7 @@ export const nodeClient = async (path, { readOnly = false } = {}) => {
|
|
|
29
30
|
export const STATEMENT_CACHE_SIZE = 1000;
|
|
30
31
|
export const cacheOverNodeSqlite = (database, cacheSize = STATEMENT_CACHE_SIZE) => {
|
|
31
32
|
const statements = new Map();
|
|
33
|
+
database.function(TIME_LIMIT_FUNCTION, { deterministic: false, directOnly: true }, beforeTimeLimit);
|
|
32
34
|
const prepare = (sql) => {
|
|
33
35
|
const statement = database.prepare(sql);
|
|
34
36
|
return {
|
|
@@ -38,6 +40,7 @@ export const cacheOverNodeSqlite = (database, cacheSize = STATEMENT_CACHE_SIZE)
|
|
|
38
40
|
};
|
|
39
41
|
};
|
|
40
42
|
return {
|
|
43
|
+
timeLimit: true,
|
|
41
44
|
exec: (sql) => database.exec(sql),
|
|
42
45
|
prepare: (sql) => {
|
|
43
46
|
const cached = statements.get(sql);
|
package/dist/store/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export type { TagRedirectIndex, TagRedirectResolution, TagRedirectSnapshot } fro
|
|
|
6
6
|
export { createTagRedirectIndex } from "../domain/tag-redirects.js";
|
|
7
7
|
export { note } from "../entities/note.js";
|
|
8
8
|
export type { FindingEvidence, FindingOutcome, MeetingFactFinding, MeetingFinding, MeetingFindingsInput, MeetingFindingsReceipt, MeetingTaskFinding, } from "../meetings/index.js";
|
|
9
|
+
export { PAYMENT_DIRECTIONS, PAYMENT_STATES, type Payment, paymentTitle, validatePayment } from "../meetings/index.js";
|
|
9
10
|
export { CLOSED_STATES, isClosedState, PRIORITY_LABELS, type PriorityLabel, priorityLabel, priorityOf, priorityOption, TASK_STATES, type TaskState, } from "../tasks/index.js";
|
|
10
11
|
export type { AccountMail, AccountMeetings, AccountStore } from "./account-store.js";
|
|
11
12
|
export { assertMayDecide, DEFAULT_DECISION_POLICY, type Decider, type DecisionPolicy, mayDecide, type Proposer, type Refusal, } from "./decisions.js";
|
|
@@ -35,7 +36,7 @@ export type { ChatMetadata } from "./sqlite/chats.js";
|
|
|
35
36
|
export type { Email, EmailAddress, EmailAttachment, EmailFacts, EmailFilter, EmailInput, EmailInThread, EmailLocationInput, EmailRecipient, EmailRef, EmailThread, EmailVectorHit, FolderEmail, Mailbox, MailboxInput, MailFilter, MailStore, PartialEmail, SenderScore, ThreadDetails, ThreadSave, } from "./sqlite/emails.js";
|
|
36
37
|
export { EVENT_FIELDS, type EventChunkToEmbed, type EventHit, type EventQuery, type EventSearch, type NearestEvent, } from "./sqlite/event-search.js";
|
|
37
38
|
export type { EvidenceSource } from "./sqlite/evidence-sources.js";
|
|
38
|
-
export type { Fact, FactInput, FactsStore } from "./sqlite/facts.js";
|
|
39
|
+
export type { Fact, FactInput, FactsStore, PaymentFilter } from "./sqlite/facts.js";
|
|
39
40
|
export type { FileListQuery, FilesStore, StoredFile } from "./sqlite/files.js";
|
|
40
41
|
export { IMPORTED_DOCUMENT_QUERY, type ImportedDocuments } from "./sqlite/imported-documents.js";
|
|
41
42
|
export type { Involvement, InvolvementStore } from "./sqlite/involvements.js";
|
package/dist/store/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { FACT_STATUSES, FACT_TYPES, MEMORY_KINDS, MEMORY_SCOPES, MEMORY_STATUSES, TASK_TYPES, WORK_TASK_TYPES, } from "../domain/knowledge.js";
|
|
2
2
|
export { createTagRedirectIndex } from "../domain/tag-redirects.js";
|
|
3
3
|
export { note } from "../entities/note.js";
|
|
4
|
+
export { PAYMENT_DIRECTIONS, PAYMENT_STATES, paymentTitle, validatePayment } from "../meetings/index.js";
|
|
4
5
|
export { CLOSED_STATES, isClosedState, PRIORITY_LABELS, priorityLabel, priorityOf, priorityOption, TASK_STATES, } from "../tasks/index.js";
|
|
5
6
|
export { assertMayDecide, DEFAULT_DECISION_POLICY, mayDecide, } from "./decisions.js";
|
|
6
7
|
export { applyDocumentTransfer, previewDocumentTransfer } from "./document-transfer/index.js";
|
|
@@ -4,7 +4,7 @@ export declare const projectAnnotationRequest: (request: ProjectAnnotationTarget
|
|
|
4
4
|
format: "wirecat.project-annotations";
|
|
5
5
|
facts: {
|
|
6
6
|
ref: string;
|
|
7
|
-
type: "decision" | "fact" | "promise" | "question" | "statement";
|
|
7
|
+
type: "decision" | "fact" | "payment" | "promise" | "question" | "statement";
|
|
8
8
|
statement: string;
|
|
9
9
|
status: "accepted" | "proposed" | "rejected" | "reversed" | "superseded";
|
|
10
10
|
subject: string | null;
|
|
@@ -23,7 +23,7 @@ export declare const projectAnnotationsSchema: v.StrictObjectSchema<{
|
|
|
23
23
|
readonly aliases: v.SchemaWithPipe<readonly [v.ArraySchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>, undefined>, v.MaxLengthAction<string[], 1000, undefined>]>;
|
|
24
24
|
readonly facts: v.SchemaWithPipe<readonly [v.ArraySchema<v.StrictObjectSchema<{
|
|
25
25
|
readonly ref: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>;
|
|
26
|
-
readonly type: v.PicklistSchema<readonly ["fact", "statement", "question", "decision", "promise"], undefined>;
|
|
26
|
+
readonly type: v.PicklistSchema<readonly ["fact", "statement", "question", "decision", "promise", "payment"], undefined>;
|
|
27
27
|
readonly statement: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>;
|
|
28
28
|
readonly status: v.PicklistSchema<readonly ["proposed", "accepted", "superseded", "reversed", "rejected"], undefined>;
|
|
29
29
|
readonly subject: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>, undefined>;
|
|
@@ -38,7 +38,7 @@ export declare const projectAnnotationsSchema: v.StrictObjectSchema<{
|
|
|
38
38
|
readonly updated_at: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.SafeIntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
39
39
|
}, undefined>, undefined>, v.MaxLengthAction<{
|
|
40
40
|
ref: string;
|
|
41
|
-
type: "decision" | "fact" | "promise" | "question" | "statement";
|
|
41
|
+
type: "decision" | "fact" | "payment" | "promise" | "question" | "statement";
|
|
42
42
|
statement: string;
|
|
43
43
|
status: "accepted" | "proposed" | "rejected" | "reversed" | "superseded";
|
|
44
44
|
subject: string | null;
|
|
@@ -4,7 +4,7 @@ export declare const projectAnnotationSnapshotSchema: v.StrictObjectSchema<{
|
|
|
4
4
|
readonly format: v.LiteralSchema<"wirecat.project-annotations", undefined>;
|
|
5
5
|
readonly facts: v.SchemaWithPipe<readonly [v.ArraySchema<v.StrictObjectSchema<{
|
|
6
6
|
readonly ref: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>;
|
|
7
|
-
readonly type: v.PicklistSchema<readonly ["fact", "statement", "question", "decision", "promise"], undefined>;
|
|
7
|
+
readonly type: v.PicklistSchema<readonly ["fact", "statement", "question", "decision", "promise", "payment"], undefined>;
|
|
8
8
|
readonly statement: v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>;
|
|
9
9
|
readonly status: v.PicklistSchema<readonly ["proposed", "accepted", "superseded", "reversed", "rejected"], undefined>;
|
|
10
10
|
readonly subject: v.NullableSchema<v.SchemaWithPipe<readonly [v.StringSchema<undefined>, v.MinLengthAction<string, 1, undefined>, v.MaxLengthAction<string, number, undefined>]>, undefined>;
|
|
@@ -19,7 +19,7 @@ export declare const projectAnnotationSnapshotSchema: v.StrictObjectSchema<{
|
|
|
19
19
|
readonly updated_at: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.SafeIntegerAction<number, undefined>, v.MinValueAction<number, 0, undefined>]>;
|
|
20
20
|
}, undefined>, undefined>, v.MaxLengthAction<{
|
|
21
21
|
ref: string;
|
|
22
|
-
type: "decision" | "fact" | "promise" | "question" | "statement";
|
|
22
|
+
type: "decision" | "fact" | "payment" | "promise" | "question" | "statement";
|
|
23
23
|
statement: string;
|
|
24
24
|
status: "accepted" | "proposed" | "rejected" | "reversed" | "superseded";
|
|
25
25
|
subject: string | null;
|
|
@@ -167,7 +167,11 @@ export const evidenceFindingsStoreOver = (context) => {
|
|
|
167
167
|
: reason && checked.by === "agent"
|
|
168
168
|
? { ...checked, reason: `${checked.reason}: ${reason}` }
|
|
169
169
|
: checked;
|
|
170
|
-
assertMayDecide({
|
|
170
|
+
assertMayDecide({
|
|
171
|
+
id,
|
|
172
|
+
proposedBy: proposals.proposer(id),
|
|
173
|
+
ownerOnly: batch.input.findings.some((finding) => finding.kind === "payment"),
|
|
174
|
+
}, decision.by, context.decisions);
|
|
171
175
|
if (batch.receipt) {
|
|
172
176
|
if (JSON.stringify(batch.receipt.accepted) !== JSON.stringify(accepted))
|
|
173
177
|
return invalid("this findings batch was already reviewed with a different decision");
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type FACT_STATUSES, FACT_TYPES, MEMORY_SCOPES } from "../../domain/knowledge.js";
|
|
2
|
+
import { type PAYMENT_DIRECTIONS, type PAYMENT_STATES, type Payment } from "../../meetings/index.js";
|
|
2
3
|
import { type Decider } from "../decisions.js";
|
|
3
4
|
import { type Actor, type ProposedByFilter } from "./actors.js";
|
|
4
5
|
import { type EvidenceSource } from "./evidence-sources.js";
|
|
@@ -34,6 +35,8 @@ export interface Fact {
|
|
|
34
35
|
scope: (typeof MEMORY_SCOPES)[number] | null;
|
|
35
36
|
source: string;
|
|
36
37
|
evidence: string[];
|
|
38
|
+
/** Set on a `payment` fact only. */
|
|
39
|
+
payment: Payment | null;
|
|
37
40
|
createdAt: string;
|
|
38
41
|
updatedAt: string;
|
|
39
42
|
}
|
|
@@ -52,6 +55,14 @@ export interface FactInput {
|
|
|
52
55
|
supersedes?: string;
|
|
53
56
|
/** The memory an agent drew it from: a `created-from` link. */
|
|
54
57
|
createdFrom?: string;
|
|
58
|
+
/** Required on a `payment` fact, refused on any other; checked as `validatePayment` does. */
|
|
59
|
+
payment?: Payment;
|
|
60
|
+
}
|
|
61
|
+
/** `open` is an expected payment that still holds: not superseded, rejected or cancelled. */
|
|
62
|
+
export interface PaymentFilter {
|
|
63
|
+
direction?: (typeof PAYMENT_DIRECTIONS)[number];
|
|
64
|
+
state?: (typeof PAYMENT_STATES)[number];
|
|
65
|
+
open?: boolean;
|
|
55
66
|
}
|
|
56
67
|
export interface FactsStore {
|
|
57
68
|
add(input: FactInput): Promise<Fact>;
|
|
@@ -62,6 +73,8 @@ export interface FactsStore {
|
|
|
62
73
|
subject?: string;
|
|
63
74
|
status?: Fact["status"];
|
|
64
75
|
proposedBy?: ProposedByFilter;
|
|
76
|
+
/** Narrows to payment facts by their fields. */
|
|
77
|
+
payment?: PaymentFilter;
|
|
65
78
|
/** `created` lists the newest written first; by default the newest by when it happened. */
|
|
66
79
|
order?: "occurred" | "created";
|
|
67
80
|
limit?: number;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
2
|
import { FACT_TYPES, MEMORY_SCOPES } from "../../domain/knowledge.js";
|
|
3
3
|
import { calendarMoment } from "../../domain/moment.js";
|
|
4
|
+
import { validatePayment } from "../../meetings/index.js";
|
|
4
5
|
import { assertMayDecide, assertMayWithdraw, deciderAuthor } from "../decisions.js";
|
|
5
6
|
import { proposedBySql, proposerOf } from "./actors.js";
|
|
6
7
|
import { evidenceSourcesOf } from "./evidence-sources.js";
|
|
@@ -35,6 +36,7 @@ export const factOperationsOver = (context) => {
|
|
|
35
36
|
scope: row.scope == null ? null : row.scope,
|
|
36
37
|
source: String(row.source),
|
|
37
38
|
evidence: evidenceOf(context, { type: "fact", id: Number(row.id) }),
|
|
39
|
+
payment: row.type === "payment" ? paymentOf(row.metadata) : null,
|
|
38
40
|
createdAt: iso(row.created_at),
|
|
39
41
|
updatedAt: iso(row.updated_at),
|
|
40
42
|
});
|
|
@@ -49,7 +51,11 @@ export const factOperationsOver = (context) => {
|
|
|
49
51
|
proposedBy: proposerOf(database, row.author_type, row.author_id),
|
|
50
52
|
proposed: row.status === "proposed",
|
|
51
53
|
}, by);
|
|
52
|
-
const decided = (row, by) => assertMayDecide({
|
|
54
|
+
const decided = (row, by) => assertMayDecide({
|
|
55
|
+
id: `fact:${row.id}`,
|
|
56
|
+
proposedBy: proposerOf(database, row.author_type, row.author_id),
|
|
57
|
+
ownerOnly: row.type === "payment",
|
|
58
|
+
}, by, context.decisions);
|
|
53
59
|
/** Keeps who decided for the owner and why in `metadata.decision`; the decider's actor is in `confirmed_by`. */
|
|
54
60
|
const keepDecision = (id, by, reason) => {
|
|
55
61
|
const why = [by === "owner" ? undefined : by.reason.trim(), reason?.trim() || undefined].filter(Boolean).join(": ");
|
|
@@ -85,6 +91,7 @@ export const factOperationsOver = (context) => {
|
|
|
85
91
|
const occurredAt = input.occurredAt === undefined ? null : calendarMoment(input.occurredAt, "occurredAt");
|
|
86
92
|
if (occurredAt !== null && !Number.isFinite(occurredAt))
|
|
87
93
|
throw new CliError("validation_error", "occurredAt takes an ISO timestamp");
|
|
94
|
+
const payment = paymentInput(type, input);
|
|
88
95
|
const id = write(database, () => {
|
|
89
96
|
const project = input.project === undefined ? undefined : requiredThing(database, input.project, "the project");
|
|
90
97
|
if (project && project.type !== "project")
|
|
@@ -93,14 +100,18 @@ export const factOperationsOver = (context) => {
|
|
|
93
100
|
const replaced = input.supersedes === undefined ? undefined : rowOf(input.supersedes);
|
|
94
101
|
if (replaced && replaced.status !== "proposed" && replaced.status !== "accepted")
|
|
95
102
|
throw new CliError("validation_error", `${input.supersedes} is ${String(replaced.status)}; only a proposed or accepted fact can be corrected`);
|
|
103
|
+
if (replaced && (replaced.type === "payment") !== (type === "payment"))
|
|
104
|
+
throw new CliError("validation_error", "a payment fact corrects a payment fact, and only one");
|
|
105
|
+
if (payment?.counterparty)
|
|
106
|
+
requiredThing(database, payment.counterparty, "the counterparty");
|
|
96
107
|
const owner = input.by === "owner";
|
|
97
108
|
const { actor, model } = authorActor(context, input.by);
|
|
98
109
|
const at = now();
|
|
99
110
|
const made = Number(database
|
|
100
111
|
.prepare("INSERT INTO facts (id, type, statement, status, project_id, subject_type, subject_id, occurred_at, supersedes_id, " +
|
|
101
|
-
"author_type, author_id, model, scope, source, created_at, updated_at) " +
|
|
102
|
-
"VALUES (?, ?, ?, 'proposed', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING id")
|
|
103
|
-
.get(nextId(database, "facts"), type, statement, project?.id ?? null, subject?.type ?? null, subject?.id ?? null, occurredAt, replaced === undefined ? null : Number(replaced.id), actor.type, actor.id, model, scope, owner ? "owner" : "agent", at, at)?.id);
|
|
112
|
+
"author_type, author_id, model, scope, source, metadata, created_at, updated_at) " +
|
|
113
|
+
"VALUES (?, ?, ?, 'proposed', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING id")
|
|
114
|
+
.get(nextId(database, "facts"), type, statement, project?.id ?? null, subject?.type ?? null, subject?.id ?? null, occurredAt, replaced === undefined ? null : Number(replaced.id), actor.type, actor.id, model, scope, owner ? "owner" : "agent", payment ? JSON.stringify({ payment }) : null, at, at)?.id);
|
|
104
115
|
linkEvidence(context, { type: "fact", id: made }, input.evidence ?? [], owner ? "owner" : "agent");
|
|
105
116
|
if (input.createdFrom !== undefined) {
|
|
106
117
|
const from = requiredThing(database, input.createdFrom, "the source");
|
|
@@ -121,11 +132,12 @@ export const factOperationsOver = (context) => {
|
|
|
121
132
|
const subject = options.subject === undefined ? undefined : requiredThing(database, options.subject);
|
|
122
133
|
const by = proposedBySql("f.author_type", "f.author_id", options.proposedBy);
|
|
123
134
|
const order = options.order === "created" ? "f.created_at" : "coalesce(f.occurred_at, f.created_at)";
|
|
135
|
+
const paid = paymentSql(options.payment);
|
|
124
136
|
return database
|
|
125
137
|
.prepare(`${NAMED} WHERE f.deleted_at IS NULL AND (? IS NULL OR f.type = ?) AND (? IS NULL OR f.project_id = ?) ` +
|
|
126
138
|
"AND (? IS NULL OR (f.subject_type = ? AND f.subject_id = ?)) AND (? IS NULL OR f.status = ?) " +
|
|
127
|
-
`AND ${by.sql} ORDER BY ${order} DESC, f.id DESC LIMIT ?`)
|
|
128
|
-
.all(options.type ?? null, options.type ?? null, project?.id ?? null, project?.id ?? null, subject?.type ?? null, subject?.type ?? null, subject?.id ?? null, options.status ?? null, options.status ?? null, ...by.params, Math.min(Math.max(options.limit ?? 100, 1), 500))
|
|
139
|
+
`AND ${by.sql}${paid.sql} ORDER BY ${order} DESC, f.id DESC LIMIT ?`)
|
|
140
|
+
.all(options.type ?? null, options.type ?? null, project?.id ?? null, project?.id ?? null, subject?.type ?? null, subject?.type ?? null, subject?.id ?? null, options.status ?? null, options.status ?? null, ...by.params, ...paid.params, Math.min(Math.max(options.limit ?? 100, 1), 500))
|
|
129
141
|
.map(factOf);
|
|
130
142
|
},
|
|
131
143
|
accept: (reference, by, reason) => {
|
|
@@ -183,6 +195,52 @@ export const factOperationsOver = (context) => {
|
|
|
183
195
|
sources: (reference) => evidenceSourcesOf(database, { type: "fact", id: Number(rowOf(reference).id) }),
|
|
184
196
|
};
|
|
185
197
|
};
|
|
198
|
+
const paymentInput = (type, input) => {
|
|
199
|
+
if (type !== "payment") {
|
|
200
|
+
if (input.payment !== undefined)
|
|
201
|
+
throw new CliError("validation_error", "only a payment fact takes a payment");
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
204
|
+
if (input.payment === undefined)
|
|
205
|
+
throw new CliError("validation_error", "a payment fact needs its payment: amount, currency, direction and state");
|
|
206
|
+
const evidence = input.evidence?.length ?? 0;
|
|
207
|
+
if (evidence < 1 || evidence > 8)
|
|
208
|
+
throw new CliError("validation_error", "a payment fact cites 1–8 references: the ask, the transfer or a sheet row");
|
|
209
|
+
return validatePayment(input.payment);
|
|
210
|
+
};
|
|
211
|
+
const paymentOf = (metadata) => {
|
|
212
|
+
if (metadata == null)
|
|
213
|
+
return null;
|
|
214
|
+
try {
|
|
215
|
+
const { payment } = JSON.parse(String(metadata));
|
|
216
|
+
return payment ?? null;
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
const paymentSql = (filter) => {
|
|
223
|
+
if (filter === undefined)
|
|
224
|
+
return { sql: "", params: [] };
|
|
225
|
+
const parts = ["f.type = 'payment'"];
|
|
226
|
+
const params = [];
|
|
227
|
+
const state = filter.open ? "expected" : filter.state;
|
|
228
|
+
if (filter.open && filter.state !== undefined && filter.state !== "expected")
|
|
229
|
+
parts.push("0");
|
|
230
|
+
if (filter.direction !== undefined) {
|
|
231
|
+
parts.push("json_extract(f.metadata, '$.payment.direction') = ?");
|
|
232
|
+
params.push(filter.direction);
|
|
233
|
+
}
|
|
234
|
+
if (state !== undefined) {
|
|
235
|
+
parts.push("json_extract(f.metadata, '$.payment.state') = ?");
|
|
236
|
+
params.push(state);
|
|
237
|
+
}
|
|
238
|
+
if (filter.open)
|
|
239
|
+
parts.push("f.status IN ('proposed', 'accepted')");
|
|
240
|
+
if (filter.open === false)
|
|
241
|
+
parts.push("NOT (json_extract(f.metadata, '$.payment.state') = 'expected' AND f.status IN ('proposed', 'accepted'))");
|
|
242
|
+
return { sql: ` AND ${parts.join(" AND ")}`, params };
|
|
243
|
+
};
|
|
186
244
|
const migratedFrom = (metadata) => {
|
|
187
245
|
try {
|
|
188
246
|
const { memory } = JSON.parse(metadata);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
|
-
import { findingFingerprint, findingHash, } from "../../meetings/index.js";
|
|
2
|
+
import { findingFingerprint, findingHash, paymentTitle, } from "../../meetings/index.js";
|
|
3
3
|
import { factOperationsOver } from "./facts.js";
|
|
4
4
|
import { linkEvidence } from "./memories.js";
|
|
5
5
|
import { requiredThing } from "./notes.js";
|
|
@@ -81,6 +81,19 @@ export const findingsCoreOver = (context, kind) => {
|
|
|
81
81
|
});
|
|
82
82
|
return facts.acceptChecked(fact.ref, decidedBy).ref;
|
|
83
83
|
}
|
|
84
|
+
if (finding.kind === "payment") {
|
|
85
|
+
const fact = facts.add({
|
|
86
|
+
type: "payment",
|
|
87
|
+
statement: finding.statement ?? paymentTitle(finding),
|
|
88
|
+
payment: finding.payment,
|
|
89
|
+
...(finding.project === undefined ? {} : { project: finding.project }),
|
|
90
|
+
...(finding.subject === undefined ? {} : { subject: finding.subject }),
|
|
91
|
+
...(finding.supersedes === undefined ? {} : { supersedes: finding.supersedes }),
|
|
92
|
+
evidence,
|
|
93
|
+
by: agent,
|
|
94
|
+
});
|
|
95
|
+
return facts.acceptChecked(fact.ref, decidedBy).ref;
|
|
96
|
+
}
|
|
84
97
|
if (finding.project === undefined)
|
|
85
98
|
return invalid("a task finding names the project the task goes in");
|
|
86
99
|
const task = tasks.create({
|
|
@@ -6,7 +6,7 @@ import type { Author } from "./memories.js";
|
|
|
6
6
|
import { type Link } from "./notes.js";
|
|
7
7
|
import type { StoreContext } from "./open.js";
|
|
8
8
|
import type { ProposedAction } from "./proposed-actions.js";
|
|
9
|
-
import { type KnowledgeRelation, type LinkDecision, type RelateInput, type RelationUpdate } from "./relation-proposals.js";
|
|
9
|
+
import { type KnowledgeRelation, type LinkDecision, type RelateInput, type RelationUpdate, type Scored } from "./relation-proposals.js";
|
|
10
10
|
import type { TagCreationOptions } from "./tag-creation.js";
|
|
11
11
|
import { type Label, type TagKind, type TagRow } from "./tags.js";
|
|
12
12
|
import { type ThingState } from "./things.js";
|
|
@@ -190,7 +190,21 @@ export interface KnowledgeStore extends VocabularyStore, MeetingProjectsStore {
|
|
|
190
190
|
purpose?: string;
|
|
191
191
|
evidence?: string[];
|
|
192
192
|
reason?: string;
|
|
193
|
-
}, by: Exclude<Author, "owner">): Promise<ProposedAction>;
|
|
193
|
+
} & Scored, by: Exclude<Author, "owner">): Promise<ProposedAction>;
|
|
194
|
+
/**
|
|
195
|
+
* Where a pair stands for a scan: `linked` when a link already joins it, `rejected` when any `relationship`
|
|
196
|
+
* proposal for it was rejected, and the pending proposal if one waits. `undefined` when either end is not stored.
|
|
197
|
+
*/
|
|
198
|
+
relationState(key: KnowledgeScope, input: {
|
|
199
|
+
from: string;
|
|
200
|
+
to: string;
|
|
201
|
+
kind: KnowledgeRelation["kind"];
|
|
202
|
+
}): Promise<{
|
|
203
|
+
linked: boolean;
|
|
204
|
+
confirmed: boolean;
|
|
205
|
+
rejected: boolean;
|
|
206
|
+
pending: ProposedAction | null;
|
|
207
|
+
} | undefined>;
|
|
194
208
|
/** The owner sets a relationship's role and purpose; `null` clears one. */
|
|
195
209
|
updateRelation(key: KnowledgeScope, id: string, input: RelationUpdate & {
|
|
196
210
|
by: "owner";
|
|
@@ -562,6 +562,7 @@ export const knowledgeStoreOver = (context) => {
|
|
|
562
562
|
});
|
|
563
563
|
return proposals.propose(from, input, by);
|
|
564
564
|
},
|
|
565
|
+
relationState: async (key, input) => proposals.state(() => pair(key, input), input),
|
|
565
566
|
updateRelation: (async (key, id, input) => {
|
|
566
567
|
checked(key);
|
|
567
568
|
const link = linkOf(database, relation(id));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Page } from "../../domain/models.js";
|
|
2
2
|
import type { QueryExecution } from "../../search/lucene/resolved.js";
|
|
3
|
-
import type
|
|
3
|
+
import { type SqlValue } from "../driver.js";
|
|
4
4
|
import type { StoreContext } from "./open.js";
|
|
5
5
|
import type { ScoredHit } from "./words.js";
|
|
6
6
|
export { type Corpus, prefixOf } from "./lucene-fields.js";
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
2
|
import { compileAutomaton } from "../../search/lucene/automaton.js";
|
|
3
3
|
import { isStemmed } from "../../search/lucene/resolved.js";
|
|
4
|
-
import { exhausted, QUERY_LIMITS, searchError } from "../../search/lucene/types.js";
|
|
4
|
+
import { exhausted, outOfTime, QUERY_LIMITS, searchError } from "../../search/lucene/types.js";
|
|
5
|
+
import { TIME_LIMIT_FUNCTION, TimeLimitReached } from "../driver.js";
|
|
5
6
|
import { emailHitsByPk } from "./emails.js";
|
|
6
7
|
import { placeHits } from "./hit-places.js";
|
|
7
8
|
import { combine, FIELD_COMPILERS, ftsMatch, SPEECH, } from "./lucene-fields.js";
|
|
@@ -54,7 +55,7 @@ export const countMatches = (context, execution, cap) => matchingPks(context, ex
|
|
|
54
55
|
* The keys of the messages that match, no more than `cap + 1`: one SQL query, unranked. `undefined` when a leaf is
|
|
55
56
|
* checked row by row (a preset, a pattern), where it would cost as much as the search itself.
|
|
56
57
|
*/
|
|
57
|
-
export const matchingPks = (context, execution, cap) => {
|
|
58
|
+
export const matchingPks = (context, execution, cap) => inTime(() => {
|
|
58
59
|
const { database, check, where, from, withRanking, ranked, leaves } = compileQuery(context, {
|
|
59
60
|
...execution,
|
|
60
61
|
newest: true,
|
|
@@ -66,6 +67,19 @@ export const matchingPks = (context, execution, cap) => {
|
|
|
66
67
|
.all(...ranked.params, ...where.params, cap + 1);
|
|
67
68
|
check();
|
|
68
69
|
return rows.map(({ id }) => Number(id));
|
|
70
|
+
});
|
|
71
|
+
const timeLimited = (error) => {
|
|
72
|
+
if (error instanceof TimeLimitReached)
|
|
73
|
+
outOfTime();
|
|
74
|
+
throw error;
|
|
75
|
+
};
|
|
76
|
+
const inTime = (work) => {
|
|
77
|
+
try {
|
|
78
|
+
return work();
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
return timeLimited(error);
|
|
82
|
+
}
|
|
69
83
|
};
|
|
70
84
|
const compileQuery = (context, execution, boundedAttachments = false) => {
|
|
71
85
|
const { database } = context;
|
|
@@ -74,6 +88,7 @@ const compileQuery = (context, execution, boundedAttachments = false) => {
|
|
|
74
88
|
if (mail && (execution.conversationIds || execution.conversationSince !== undefined))
|
|
75
89
|
throw new CliError("validation_error", "mail has no conversations to search within");
|
|
76
90
|
const started = context.now();
|
|
91
|
+
const until = Date.now() + QUERY_LIMITS.milliseconds;
|
|
77
92
|
const budget = { work: 0 };
|
|
78
93
|
let states = 0, expansions = 0;
|
|
79
94
|
const patterns = new Map();
|
|
@@ -82,7 +97,7 @@ const compileQuery = (context, execution, boundedAttachments = false) => {
|
|
|
82
97
|
if (execution.signal?.aborted)
|
|
83
98
|
throw new CliError("validation_error", "search was aborted", { reason: "query.aborted", complete: false });
|
|
84
99
|
if (context.now() - started > QUERY_LIMITS.milliseconds)
|
|
85
|
-
|
|
100
|
+
outOfTime();
|
|
86
101
|
};
|
|
87
102
|
const patternOf = (pattern, node) => {
|
|
88
103
|
check();
|
|
@@ -197,7 +212,10 @@ const compileQuery = (context, execution, boundedAttachments = false) => {
|
|
|
197
212
|
exact: true,
|
|
198
213
|
})), "OR"),
|
|
199
214
|
], "AND");
|
|
200
|
-
|
|
215
|
+
const timeLimit = database.timeLimit
|
|
216
|
+
? [{ sql: `${TIME_LIMIT_FUNCTION}(?)`, params: [until], exact: true }]
|
|
217
|
+
: [];
|
|
218
|
+
let where = combine([{ sql: "m.deleted_at IS NULL", params: [], exact: true }, scope, expression, ...timeLimit], "AND");
|
|
201
219
|
if (execution.conversationIds !== undefined || execution.conversationSince !== undefined) {
|
|
202
220
|
const ids = execution.conversationIds;
|
|
203
221
|
where = combine([
|
|
@@ -404,7 +422,7 @@ const compileQuery = (context, execution, boundedAttachments = false) => {
|
|
|
404
422
|
/** The matcher and its consumer run synchronously in one read snapshot; exact keys stay in SQL. */
|
|
405
423
|
export const withQuerySelection = (context, execution, consume) => {
|
|
406
424
|
const { database } = context;
|
|
407
|
-
return read(database, () => {
|
|
425
|
+
return inTime(() => read(database, () => {
|
|
408
426
|
const query = compileQuery(context, execution, true);
|
|
409
427
|
const { check, leaves, where, from, withRanking, ranked } = query;
|
|
410
428
|
check();
|
|
@@ -448,9 +466,17 @@ export const withQuerySelection = (context, execution, consume) => {
|
|
|
448
466
|
throw new Error("query selection consumers must run synchronously");
|
|
449
467
|
check();
|
|
450
468
|
return result;
|
|
451
|
-
});
|
|
469
|
+
}));
|
|
470
|
+
};
|
|
471
|
+
const runQuery = (context, execution, by) => {
|
|
472
|
+
try {
|
|
473
|
+
return runCompiled(context, execution, by).catch(timeLimited);
|
|
474
|
+
}
|
|
475
|
+
catch (error) {
|
|
476
|
+
return timeLimited(error);
|
|
477
|
+
}
|
|
452
478
|
};
|
|
453
|
-
const
|
|
479
|
+
const runCompiled = async (context, execution, by) => {
|
|
454
480
|
const { corpus, rowsFrom, hits, database, check, grouped, where, from, withRanking, ranked, leaves, stemmed, exactTier, driver, baseFrom, filtered, ranking, relevance, rankedFrom, exactColumn, order, placeTier, projection, projectionParams, joinedFrom, evaluate, } = compileQuery(context, execution);
|
|
455
481
|
if (by === "pks" && leaves.every(({ test }) => !test)) {
|
|
456
482
|
const rows = database
|
|
@@ -121,9 +121,9 @@ export function meetingHitSearchOver({ database }) {
|
|
|
121
121
|
if (after)
|
|
122
122
|
params.push(after.tier, after.tier, after.started, after.started, after.meeting, after.meeting, after.kind, after.kind, after.id);
|
|
123
123
|
const sql = `WITH matches AS (${matching}), matching AS (SELECT rid,tier FROM matches h WHERE NOT EXISTS(SELECT 1 FROM meeting_index_pending p WHERE p.id=h.rid/4 AND p.indexable_type=CASE h.rid%4 WHEN 1 THEN 'meeting_transcript_row' WHEN 2 THEN 'meeting_chat_message' ELSE 'meeting_summary' END)), selected AS (SELECT m.id,coalesce(m.started_at,0) AS started FROM meetings m WHERE ${clauses.join(" AND ")}), hits AS (
|
|
124
|
-
SELECT h.tier,m.started,m.id AS meeting_id,0 AS kind,r.id,r.start_ms,${bytes("r", ["text"])} AS bytes,t.id AS transcript_id,r.position FROM matching h JOIN meeting_transcript_rows r ON h.rid%4=1 AND r.id=h.rid/4 JOIN meeting_transcripts t ON t.id=r.meeting_transcript_id AND t.superseded_at IS NULL AND t.deleted_at IS NULL JOIN selected m ON m.id=t.meeting_id
|
|
125
|
-
UNION ALL SELECT h.tier,m.started,m.id,1,c.id,NULL,${bytes("c", ["text"])},NULL,NULL FROM matching h JOIN meeting_chat_messages c ON h.rid%4=2 AND c.id=h.rid/4 JOIN selected m ON m.id=c.meeting_id
|
|
126
|
-
UNION ALL SELECT h.tier,m.started,m.id,2,s.id,NULL,${bytes("s", fields)},NULL,NULL FROM matching h JOIN meeting_summaries s ON h.rid%4=3 AND s.id=h.rid/4 JOIN selected m ON m.id=s.meeting_id)
|
|
124
|
+
SELECT h.tier,m.started,m.id AS meeting_id,0 AS kind,r.id,r.start_ms,${bytes("r", ["text"])} AS bytes,t.id AS transcript_id,r.position,t.language,(SELECT count(*) FROM meeting_transcript_rows n WHERE n.meeting_transcript_id=t.id) AS lines_total FROM matching h JOIN meeting_transcript_rows r ON h.rid%4=1 AND r.id=h.rid/4 JOIN meeting_transcripts t ON t.id=r.meeting_transcript_id AND t.superseded_at IS NULL AND t.deleted_at IS NULL JOIN selected m ON m.id=t.meeting_id
|
|
125
|
+
UNION ALL SELECT h.tier,m.started,m.id,1,c.id,NULL,${bytes("c", ["text"])},NULL,NULL,NULL,NULL FROM matching h JOIN meeting_chat_messages c ON h.rid%4=2 AND c.id=h.rid/4 JOIN selected m ON m.id=c.meeting_id
|
|
126
|
+
UNION ALL SELECT h.tier,m.started,m.id,2,s.id,NULL,${bytes("s", fields)},NULL,NULL,NULL,NULL FROM matching h JOIN meeting_summaries s ON h.rid%4=3 AND s.id=h.rid/4 JOIN selected m ON m.id=s.meeting_id)
|
|
127
127
|
SELECT h.*,${bytes("m", ["title"])}+${bytes("s", ["doc_url"])} AS context_bytes FROM hits h
|
|
128
128
|
JOIN meetings m ON m.id=h.meeting_id LEFT JOIN meeting_summaries s ON h.kind=2 AND s.id=h.id
|
|
129
129
|
${seek} ORDER BY h.tier,h.started DESC,h.meeting_id,h.kind,h.id LIMIT ?`;
|
|
@@ -176,6 +176,13 @@ export function meetingHitSearchOver({ database }) {
|
|
|
176
176
|
id: Number(row.id),
|
|
177
177
|
text,
|
|
178
178
|
startMs: row.start_ms === null ? null : Number(row.start_ms),
|
|
179
|
+
...(kind === 0
|
|
180
|
+
? {
|
|
181
|
+
language: row.language === null ? null : String(row.language),
|
|
182
|
+
line: Number(row.position) + 1,
|
|
183
|
+
linesTotal: Number(row.lines_total),
|
|
184
|
+
}
|
|
185
|
+
: {}),
|
|
179
186
|
};
|
|
180
187
|
});
|
|
181
188
|
const fresh = items.flatMap((item) => {
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type MeetingStore } from "../../meetings/index.js";
|
|
2
|
+
import type { StoreContext } from "./open.js";
|
|
3
|
+
export type MeetingLanguageStore = Required<Pick<MeetingStore, "fillTranscriptLanguages">>;
|
|
4
|
+
export declare const meetingLanguagesOver: ({ database }: StoreContext) => MeetingLanguageStore;
|