@wirecat/cli-core 0.549.0 → 0.551.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 +223 -22
- package/THIRD_PARTY_NOTICES +41 -0
- 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/index.d.ts +3 -0
- package/dist/cli/index.js +3 -0
- package/dist/cli/meeting-findings-input.js +4 -3
- 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/payment-query.d.ts +8 -0
- package/dist/cli/payment-query.js +27 -0
- 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/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/index.d.ts +18 -2
- package/dist/entities/meeting-findings.d.ts +1 -1
- 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/mail/partial.d.ts +9 -3
- package/dist/mail/partial.js +35 -10
- package/dist/mail/source.d.ts +7 -1
- package/dist/mail/sync.js +1 -1
- 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/server.js +1 -0
- package/dist/mcp/surface.js +1 -1
- package/dist/meetings/findings-rules.d.ts +14 -3
- package/dist/meetings/findings-rules.js +16 -2
- package/dist/meetings/index.d.ts +3 -2
- package/dist/meetings/index.js +2 -1
- 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/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/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/store/decisions.d.ts +5 -1
- package/dist/store/decisions.js +5 -1
- 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/email-locations.d.ts +22 -1
- package/dist/store/sqlite/email-locations.js +29 -2
- package/dist/store/sqlite/emails.d.ts +3 -7
- package/dist/store/sqlite/emails.js +4 -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/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
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { normalize } from "../../../store/normalize.js";
|
|
2
|
+
import { evidenceOf, fromOwner } from "../items.js";
|
|
3
|
+
const escaped = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4
|
+
const wholeWord = (word) => new RegExp(`(?<![\\p{L}\\p{N}])${escaped(word)}(?![\\p{L}\\p{N}])`, "u");
|
|
5
|
+
/** Every word of the query, folded, as a whole word somewhere in the text: `projects evidence`'s reading. */
|
|
6
|
+
const matcherOf = (query) => {
|
|
7
|
+
const words = normalize(query).split(" ").filter(Boolean).map(wholeWord);
|
|
8
|
+
return (text) => {
|
|
9
|
+
const folded = normalize(text);
|
|
10
|
+
return words.length > 0 && words.every((word) => word.test(folded));
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
/** Per `--context-query`, the newest message of each side that holds every one of its words, in the window. */
|
|
14
|
+
export const topics = {
|
|
15
|
+
id: "topics",
|
|
16
|
+
version: 1,
|
|
17
|
+
scope: "chat",
|
|
18
|
+
read: async (context, chat) => {
|
|
19
|
+
if (!chat)
|
|
20
|
+
return [];
|
|
21
|
+
return context.contextQueries.flatMap((query) => {
|
|
22
|
+
const matches = matcherOf(query);
|
|
23
|
+
const naming = chat.messages.filter((message) => matches(message.text));
|
|
24
|
+
const yours = naming.findLast(fromOwner);
|
|
25
|
+
const theirs = naming.findLast((message) => !fromOwner(message));
|
|
26
|
+
if (!yours && !theirs)
|
|
27
|
+
return [];
|
|
28
|
+
const silent = theirs && yours && Date.parse(yours.timestamp) > Date.parse(theirs.timestamp);
|
|
29
|
+
const newest = [yours, theirs]
|
|
30
|
+
.filter((one) => one !== undefined)
|
|
31
|
+
.sort((a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp));
|
|
32
|
+
return [
|
|
33
|
+
{
|
|
34
|
+
signal: "topics",
|
|
35
|
+
chat: chat.ref,
|
|
36
|
+
at: newest[0]?.timestamp ?? null,
|
|
37
|
+
who: null,
|
|
38
|
+
evidence: evidenceOf(chat, ...newest),
|
|
39
|
+
basis: "store",
|
|
40
|
+
data: {
|
|
41
|
+
query,
|
|
42
|
+
yoursLast: yours?.timestamp ?? null,
|
|
43
|
+
theirsLast: theirs?.timestamp ?? null,
|
|
44
|
+
silentSince: silent ? theirs.timestamp : null,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { ChatKind, Id, Message } from "../../domain/models.js";
|
|
2
|
+
import type { AccountKey, MessageStore } from "../../store/store.js";
|
|
3
|
+
import type { ChatCoverage } from "../chat-coverage.js";
|
|
4
|
+
/** What every chat signal reads; loaded once per chat by the pipeline. Never printed. */
|
|
5
|
+
export interface StatusChat {
|
|
6
|
+
/** `chat:<provider>/<account>/<chat>` */
|
|
7
|
+
ref: string;
|
|
8
|
+
account: AccountKey;
|
|
9
|
+
chatId: Id;
|
|
10
|
+
title: string | null;
|
|
11
|
+
kind: ChatKind;
|
|
12
|
+
/** The link's role: "client", "contractor", or null. */
|
|
13
|
+
role: string | null;
|
|
14
|
+
/** The chat's purpose in the project; `work` until links carry one. */
|
|
15
|
+
purpose: string;
|
|
16
|
+
/** `project:<id>`; a child's id when rolled up. */
|
|
17
|
+
project: string;
|
|
18
|
+
coverage: ChatCoverage;
|
|
19
|
+
/** Oldest first, the window, at most `STATUS_WINDOW`. */
|
|
20
|
+
messages: Message[];
|
|
21
|
+
}
|
|
22
|
+
export interface StatusProject {
|
|
23
|
+
ref: string;
|
|
24
|
+
key: string | null;
|
|
25
|
+
name: string;
|
|
26
|
+
}
|
|
27
|
+
export interface StatusChild extends StatusProject {
|
|
28
|
+
parent: string;
|
|
29
|
+
depth: number;
|
|
30
|
+
}
|
|
31
|
+
export interface StatusContext {
|
|
32
|
+
now: number;
|
|
33
|
+
/** Window start, ms. */
|
|
34
|
+
since: number;
|
|
35
|
+
chats: StatusChat[];
|
|
36
|
+
project: StatusProject & {
|
|
37
|
+
children: StatusChild[];
|
|
38
|
+
/** Children not read: too deep, seen twice, or past the limit. */
|
|
39
|
+
cut: {
|
|
40
|
+
ref: string;
|
|
41
|
+
reason: "depth" | "cycle" | "limit";
|
|
42
|
+
}[];
|
|
43
|
+
};
|
|
44
|
+
/** Read only: facts, tasks, chats. */
|
|
45
|
+
store: MessageStore;
|
|
46
|
+
/** `--context-query` texts: every word of one must appear in one message. */
|
|
47
|
+
contextQueries: string[];
|
|
48
|
+
}
|
|
49
|
+
export interface StatusWho {
|
|
50
|
+
ref: string | null;
|
|
51
|
+
name: string | null;
|
|
52
|
+
owner: boolean;
|
|
53
|
+
}
|
|
54
|
+
/** One finding of a signal. Stable keys; the signal adds its own under `data`. */
|
|
55
|
+
export interface StatusItem<D = Record<string, unknown>> {
|
|
56
|
+
signal: string;
|
|
57
|
+
/** Null for a project level finding. */
|
|
58
|
+
chat: string | null;
|
|
59
|
+
/** ISO, when it happened. */
|
|
60
|
+
at: string | null;
|
|
61
|
+
who: StatusWho | null;
|
|
62
|
+
/** `msg:`, `fact:`, `task:` refs. */
|
|
63
|
+
evidence: {
|
|
64
|
+
ref: string;
|
|
65
|
+
fingerprint: string | null;
|
|
66
|
+
}[];
|
|
67
|
+
/** How sure: `pattern` is a word match, to check before it is trusted. */
|
|
68
|
+
basis: "store" | "fact" | "task" | "pattern";
|
|
69
|
+
data: D;
|
|
70
|
+
}
|
|
71
|
+
export interface StatusAttention {
|
|
72
|
+
chat: string;
|
|
73
|
+
weight: number;
|
|
74
|
+
why: string;
|
|
75
|
+
}
|
|
76
|
+
export interface StatusSignal<D = Record<string, unknown>> {
|
|
77
|
+
id: string;
|
|
78
|
+
/** Bumped when the logic changes, so a saved answer says which rules made it. */
|
|
79
|
+
version: number;
|
|
80
|
+
scope: "chat" | "project";
|
|
81
|
+
read(context: StatusContext, chat?: StatusChat): Promise<StatusItem<D>[]>;
|
|
82
|
+
/** Why a chat should be read next, weight 0 to 3; the pipeline sums them per chat. */
|
|
83
|
+
attention?(items: StatusItem<D>[], context: StatusContext): StatusAttention[];
|
|
84
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { parseProposalIds } from "./ids.js";
|
|
2
|
+
export { FACT_SUMMARIES } from "./kinds/findings.js";
|
|
2
3
|
export { emptyProposalRegistry, proposalRegistry } from "./registry.js";
|
|
3
4
|
export type { Decided, Decision, ProposalFilter, ProposalKind, ProposalRegistry, ProposalRow } from "./types.js";
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import type { Finding } from "../../../meetings/index.js";
|
|
1
2
|
import { type MessageStore } from "../../../store/index.js";
|
|
2
3
|
import type { ProposalKind } from "../types.js";
|
|
4
|
+
/** A finding's one line in the inbox, per kind; a new fact kind adds its row here. */
|
|
5
|
+
export declare const FACT_SUMMARIES: Record<string, (finding: Finding) => string>;
|
|
3
6
|
/**
|
|
4
7
|
* An agent's batch of cited facts and tasks from stored messages, mail and documents. Confirming accepts the parts
|
|
5
8
|
* named, or every finding not rejected before; the rest are rejected. Meeting findings stay with their meeting.
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
+
import { paymentTitle } from "../../../meetings/index.js";
|
|
1
2
|
import { EVIDENCE_FINDINGS_KIND } from "../../../store/index.js";
|
|
2
3
|
import { kept, proposerOfAuthor } from "./shared.js";
|
|
4
|
+
/** A finding's one line in the inbox, per kind; a new fact kind adds its row here. */
|
|
5
|
+
export const FACT_SUMMARIES = {
|
|
6
|
+
fact: (finding) => finding.statement,
|
|
7
|
+
task: (finding) => finding.title,
|
|
8
|
+
payment: (finding) => paymentTitle(finding),
|
|
9
|
+
};
|
|
3
10
|
const statusOf = (batch) => batch.status === "proposed" ? "proposed" : batch.receipt?.accepted.length ? "confirmed" : "rejected";
|
|
4
11
|
const rowOf = (batch, createdAt) => {
|
|
5
12
|
const findings = batch.input.findings;
|
|
@@ -20,7 +27,7 @@ const rowOf = (batch, createdAt) => {
|
|
|
20
27
|
parts: findings.map((finding) => ({
|
|
21
28
|
id: finding.id,
|
|
22
29
|
kind: finding.kind,
|
|
23
|
-
summary: finding.kind
|
|
30
|
+
summary: FACT_SUMMARIES[finding.kind]?.(finding) ?? finding.kind,
|
|
24
31
|
outcome: outcome(finding.id),
|
|
25
32
|
})),
|
|
26
33
|
createdAt,
|
|
@@ -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/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;
|
|
@@ -23,7 +23,8 @@ export declare const locate: (database: CacheDatabase, accountId: number, emailI
|
|
|
23
23
|
export declare const folderEmails: (database: CacheDatabase, accountId: number, folder: string, uidvalidity: string) => FolderEmail[];
|
|
24
24
|
/**
|
|
25
25
|
* An email stored from its first bytes only (`metadata.partial`): a sync reads it whole and clears the mark.
|
|
26
|
-
* `partialEmails` lists an account's live ones newest first, with how many there are in all
|
|
26
|
+
* `partialEmails` lists an account's live ones newest first, with how many there are in all; one given up on
|
|
27
|
+
* (`partial.stuck`) is left out and only counted, so it never holds back newer ones. Its newest location comes first.
|
|
27
28
|
*/
|
|
28
29
|
export interface PartialEmail {
|
|
29
30
|
externalId: string;
|
|
@@ -35,6 +36,26 @@ export interface PartialEmail {
|
|
|
35
36
|
export declare const partialEmails: (database: CacheDatabase, accountId: number, limit: number) => {
|
|
36
37
|
emails: PartialEmail[];
|
|
37
38
|
total: number;
|
|
39
|
+
stuck: number;
|
|
38
40
|
};
|
|
41
|
+
/** Replaces a partial email's `metadata.partial`, as a failed read records itself; a whole email is left alone. */
|
|
42
|
+
export declare const markPartial: (database: CacheDatabase, accountId: number, externalId: string, mark: Metadata) => {
|
|
43
|
+
changes: number;
|
|
44
|
+
};
|
|
45
|
+
export interface PartialMailStore {
|
|
46
|
+
partialEmails(accountId: number, limit: number): Promise<{
|
|
47
|
+
emails: PartialEmail[];
|
|
48
|
+
total: number;
|
|
49
|
+
stuck: number;
|
|
50
|
+
}>;
|
|
51
|
+
/** Records a failed read of a partial email in its `metadata.partial`; a whole email is left alone. */
|
|
52
|
+
markPartial(accountId: number, externalId: string, mark: Metadata): Promise<void>;
|
|
53
|
+
}
|
|
54
|
+
export declare const partialMail: (database: CacheDatabase) => PartialMailStore;
|
|
55
|
+
/**
|
|
56
|
+
* A live email stored whole, which a partial copy must not replace: a server search checks what is held before its
|
|
57
|
+
* slow read, and a sync in another process may store the email whole in between.
|
|
58
|
+
*/
|
|
59
|
+
export declare const storedWhole: (database: CacheDatabase, emailId: number) => boolean;
|
|
39
60
|
export declare const setLocations: (database: CacheDatabase, accountId: number, folder: string, uidvalidity: string, found: ReadonlyMap<string, number[]>) => number;
|
|
40
61
|
export declare const forgetLocations: (database: CacheDatabase, accountId: number, folder: string, uidvalidity: string, uids: number[]) => void;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { fromJson } from "./events.js";
|
|
2
|
+
import { write } from "./transaction.js";
|
|
2
3
|
export const locate = (database, accountId, emailId, where) => database
|
|
3
4
|
.prepare(`INSERT OR REPLACE INTO email_locations (mailbox_id, uidvalidity, uid, email_id)
|
|
4
5
|
SELECT id, ?, ?, ? FROM mailboxes WHERE account_id = ? AND external_id = ?`)
|
|
@@ -26,14 +27,22 @@ export const partialEmails = (database, accountId, limit) => {
|
|
|
26
27
|
const rows = database
|
|
27
28
|
.prepare(`SELECT e.external_id, t.external_id AS thread, coalesce(e.received_at, e.sent_at) AS at, e.metadata,
|
|
28
29
|
count(*) OVER () AS total,
|
|
29
|
-
(SELECT json_group_array(json_object('folder',
|
|
30
|
-
FROM
|
|
30
|
+
(SELECT json_group_array(json_object('folder', folder, 'uidvalidity', uidvalidity, 'uid', uid))
|
|
31
|
+
FROM (SELECT b.external_id AS folder, l.uidvalidity, l.uid
|
|
32
|
+
FROM email_locations l JOIN mailboxes b ON b.id = l.mailbox_id
|
|
33
|
+
WHERE l.email_id = e.id ORDER BY l.rowid DESC)) AS places
|
|
31
34
|
FROM emails e JOIN email_threads t ON t.id = e.email_thread_id
|
|
32
35
|
WHERE e.account_id = ? AND e.deleted_at IS NULL AND json_extract(e.metadata, '$.partial') IS NOT NULL
|
|
36
|
+
AND json_extract(e.metadata, '$.partial.stuck') IS NULL
|
|
33
37
|
ORDER BY at DESC, e.id DESC LIMIT ?`)
|
|
34
38
|
.all(accountId, limit);
|
|
39
|
+
const stuck = database
|
|
40
|
+
.prepare(`SELECT count(*) AS n FROM emails WHERE account_id = ? AND deleted_at IS NULL
|
|
41
|
+
AND json_extract(metadata, '$.partial.stuck') IS NOT NULL`)
|
|
42
|
+
.get(accountId);
|
|
35
43
|
return {
|
|
36
44
|
total: rows.length ? Number(rows[0]?.total) : 0,
|
|
45
|
+
stuck: Number(stuck?.n ?? 0),
|
|
37
46
|
emails: rows.map((row) => ({
|
|
38
47
|
externalId: String(row.external_id),
|
|
39
48
|
threadExternalId: String(row.thread),
|
|
@@ -47,6 +56,24 @@ export const partialEmails = (database, accountId, limit) => {
|
|
|
47
56
|
})),
|
|
48
57
|
};
|
|
49
58
|
};
|
|
59
|
+
/** Replaces a partial email's `metadata.partial`, as a failed read records itself; a whole email is left alone. */
|
|
60
|
+
export const markPartial = (database, accountId, externalId, mark) => database
|
|
61
|
+
.prepare(`UPDATE emails SET metadata = json_set(metadata, '$.partial', json(?)) WHERE account_id = ? AND external_id = ?
|
|
62
|
+
AND json_extract(metadata, '$.partial') IS NOT NULL`)
|
|
63
|
+
.run(JSON.stringify(mark), accountId, externalId);
|
|
64
|
+
export const partialMail = (database) => ({
|
|
65
|
+
partialEmails: async (accountId, limit) => partialEmails(database, accountId, limit),
|
|
66
|
+
markPartial: async (accountId, externalId, mark) => {
|
|
67
|
+
write(database, () => markPartial(database, accountId, externalId, mark));
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
/**
|
|
71
|
+
* A live email stored whole, which a partial copy must not replace: a server search checks what is held before its
|
|
72
|
+
* slow read, and a sync in another process may store the email whole in between.
|
|
73
|
+
*/
|
|
74
|
+
export const storedWhole = (database, emailId) => database
|
|
75
|
+
.prepare("SELECT 1 FROM emails WHERE id = ? AND deleted_at IS NULL AND json_extract(metadata, '$.partial') IS NULL")
|
|
76
|
+
.get(emailId) !== undefined;
|
|
50
77
|
export const setLocations = (database, accountId, folder, uidvalidity, found) => {
|
|
51
78
|
database
|
|
52
79
|
.prepare(`DELETE FROM email_locations WHERE uidvalidity <> ?
|
|
@@ -3,13 +3,13 @@ import type { Attachment, Metadata } from "../../meetings/index.js";
|
|
|
3
3
|
import type { Stemmer, Stemmers } from "../../search/stem.js";
|
|
4
4
|
import type { CacheDatabase } from "../driver.js";
|
|
5
5
|
import type { ConversationSummary, StoredHit } from "../types.js";
|
|
6
|
-
import { type EmailLocationInput, type FolderEmail, type
|
|
6
|
+
import { type EmailLocationInput, type FolderEmail, type PartialMailStore } from "./email-locations.js";
|
|
7
7
|
import { type MailRulesStore } from "./mail-rules.js";
|
|
8
8
|
import { type AttachmentInput } from "./meetings.js";
|
|
9
9
|
import type { StoreContext } from "./open.js";
|
|
10
10
|
import { type SyncStateSave } from "./sync.js";
|
|
11
11
|
import { type ThreadFilter } from "./thread-columns.js";
|
|
12
|
-
export type { EmailLocationInput, FolderEmail, PartialEmail };
|
|
12
|
+
export type { EmailLocationInput, FolderEmail, PartialEmail } from "./email-locations.js";
|
|
13
13
|
export interface EmailAddress {
|
|
14
14
|
address: string;
|
|
15
15
|
name: string | null;
|
|
@@ -205,7 +205,7 @@ export interface SenderScore {
|
|
|
205
205
|
demoted?: boolean;
|
|
206
206
|
}
|
|
207
207
|
/** Mail in tables of its own: threads, emails, recipients and mailboxes, keyed by account and Message-ID. */
|
|
208
|
-
export interface MailStore {
|
|
208
|
+
export interface MailStore extends PartialMailStore {
|
|
209
209
|
saveThread(input: ThreadSave): Promise<ThreadDetails>;
|
|
210
210
|
/** Several threads and sync state in one transaction: a slice of mail commits with its resume point, or not at all. */
|
|
211
211
|
saveThreads(inputs: ThreadSave[], states?: SyncStateSave[]): Promise<ThreadDetails[]>;
|
|
@@ -217,10 +217,6 @@ export interface MailStore {
|
|
|
217
217
|
emails(filter?: EmailFilter): Promise<Email[]>;
|
|
218
218
|
/** Each matching email's Message-ID and mailboxes, without text or people: cheap over a whole mailbox. */
|
|
219
219
|
emailRefs(filter?: Omit<EmailFilter, "limit" | "offset">): Promise<EmailRef[]>;
|
|
220
|
-
partialEmails(accountId: number, limit: number): Promise<{
|
|
221
|
-
emails: PartialEmail[];
|
|
222
|
-
total: number;
|
|
223
|
-
}>;
|
|
224
220
|
/** Each matching email's sender, recipients, list, size, flag and mailboxes, oldest first; no text. */
|
|
225
221
|
emailFacts(filter?: Omit<EmailFilter, "limit" | "offset">): Promise<EmailFacts[]>;
|
|
226
222
|
/**
|
|
@@ -2,7 +2,7 @@ import { formatLocator } from "../../domain/locator.js";
|
|
|
2
2
|
import { formatReference } from "../../domain/references.js";
|
|
3
3
|
import { fold, normalize } from "../normalize.js";
|
|
4
4
|
import { chunkAttachments, drainEmailIndex, dropAttachmentChunks, MAIL_PIECES, queueThreads, threadPieces, titleOf, } from "./email-index.js";
|
|
5
|
-
import { folderEmails, forgetLocations, locate,
|
|
5
|
+
import { folderEmails, forgetLocations, locate, partialMail, setLocations, storedWhole, } from "./email-locations.js";
|
|
6
6
|
import { markEmailsDeleted, markEmailsRestored, recountEmailThread as recount, setEmailFlags } from "./email-state.js";
|
|
7
7
|
import { bool, flag, fromJson, int, page, str, toJson } from "./events.js";
|
|
8
8
|
import { folded } from "./folded.js";
|
|
@@ -204,6 +204,8 @@ const saveEmail = (database, orm, accountId, threadId, mail, now) => {
|
|
|
204
204
|
const found = database
|
|
205
205
|
.prepare("SELECT id, email_thread_id FROM emails WHERE account_id = ? AND external_id = ?")
|
|
206
206
|
.get(accountId, mail.externalId);
|
|
207
|
+
if (found && mail.metadata?.partial !== undefined && storedWhole(database, Number(found.id)))
|
|
208
|
+
return null;
|
|
207
209
|
let emailId;
|
|
208
210
|
if (found) {
|
|
209
211
|
emailId = Number(found.id);
|
|
@@ -469,7 +471,7 @@ export const mailStoreOver = ({ database, orm, now }, stemmerFor = stemmerCache(
|
|
|
469
471
|
mailboxes: JSON.parse(String(row.boxes)),
|
|
470
472
|
}));
|
|
471
473
|
},
|
|
472
|
-
|
|
474
|
+
...partialMail(database),
|
|
473
475
|
async emailFacts(filter = {}) {
|
|
474
476
|
const [where, params] = emailWhere(filter);
|
|
475
477
|
return database
|
|
@@ -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;
|