@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
|
@@ -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));
|
|
@@ -5,7 +5,7 @@ export declare const projectAnnotationSnapshot: (database: CacheDatabase, projec
|
|
|
5
5
|
format: "wirecat.project-annotations";
|
|
6
6
|
facts: {
|
|
7
7
|
ref: string;
|
|
8
|
-
type: "decision" | "fact" | "promise" | "question" | "statement";
|
|
8
|
+
type: "decision" | "fact" | "payment" | "promise" | "question" | "statement";
|
|
9
9
|
statement: string;
|
|
10
10
|
status: "accepted" | "proposed" | "rejected" | "reversed" | "superseded";
|
|
11
11
|
subject: string | null;
|
|
@@ -5,6 +5,7 @@ import { type Link } from "./notes.js";
|
|
|
5
5
|
import type { StoreContext } from "./open.js";
|
|
6
6
|
import { type ProposedAction } from "./proposed-actions.js";
|
|
7
7
|
import { type Thing } from "./things.js";
|
|
8
|
+
type Row = Record<string, unknown>;
|
|
8
9
|
export interface KnowledgeRelation {
|
|
9
10
|
id: string;
|
|
10
11
|
from: string;
|
|
@@ -32,7 +33,23 @@ export interface RelateInput {
|
|
|
32
33
|
export declare const relationOf: (link: Link) => KnowledgeRelation;
|
|
33
34
|
/** `relationship` proposals in `proposed_actions`: an agent's or a rule's, and the rejected ones a scan skips. */
|
|
34
35
|
export declare const relationProposalsOver: (context: StoreContext) => {
|
|
35
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Where a pair stands before a scan proposes it: a link joins it, a `relationship` proposal for it was rejected,
|
|
38
|
+
* or one is pending. A rejection is matched by the pair alone, whoever proposed it and with whatever scorer.
|
|
39
|
+
*/
|
|
40
|
+
state: (paired: () => {
|
|
41
|
+
from: Thing;
|
|
42
|
+
existing: Row | undefined;
|
|
43
|
+
}, { to, kind }: {
|
|
44
|
+
to: string;
|
|
45
|
+
kind: string;
|
|
46
|
+
}) => {
|
|
47
|
+
linked: boolean;
|
|
48
|
+
confirmed: boolean;
|
|
49
|
+
rejected: boolean;
|
|
50
|
+
pending: ProposedAction | null;
|
|
51
|
+
} | undefined;
|
|
52
|
+
/** The same pending pair returns the proposal already held; a scored one updates its score in place. */
|
|
36
53
|
propose: (from: Thing, input: {
|
|
37
54
|
from: string;
|
|
38
55
|
to: string;
|
|
@@ -41,7 +58,7 @@ export declare const relationProposalsOver: (context: StoreContext) => {
|
|
|
41
58
|
purpose?: string;
|
|
42
59
|
evidence?: string[];
|
|
43
60
|
reason?: string;
|
|
44
|
-
}, by: Exclude<Author, "owner">) => ProposedAction;
|
|
61
|
+
} & Scored, by: Exclude<Author, "owner">) => ProposedAction;
|
|
45
62
|
rejected: (link: Link, by: Decider, reason?: string) => void;
|
|
46
63
|
update: (link: Link, input: RelationUpdate) => Link;
|
|
47
64
|
proposeUpdate: (link: Link, input: RelationUpdate & {
|
|
@@ -62,6 +79,15 @@ export declare const relationProposalsOver: (context: StoreContext) => {
|
|
|
62
79
|
/** Decides an unconfirmed link written before proposals were stored; only the owner may, as its author is unknown. */
|
|
63
80
|
decideLink: (id: string, decision: LinkDecision) => Link | null;
|
|
64
81
|
};
|
|
82
|
+
/** What a scan adds to a relationship proposal: the combined score and each scorer's part in it. */
|
|
83
|
+
export interface Scored {
|
|
84
|
+
score?: number;
|
|
85
|
+
signals?: {
|
|
86
|
+
scorer: string;
|
|
87
|
+
weight: number;
|
|
88
|
+
why: string;
|
|
89
|
+
}[];
|
|
90
|
+
}
|
|
65
91
|
/** A relationship's role and purpose to set; `null` clears one. */
|
|
66
92
|
export interface RelationUpdate {
|
|
67
93
|
role?: string | null;
|
|
@@ -72,3 +98,4 @@ export interface LinkDecision {
|
|
|
72
98
|
by: Decider;
|
|
73
99
|
reason?: string;
|
|
74
100
|
}
|
|
101
|
+
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
|
-
import { assertMayDecide, deciderAuthor } from "../decisions.js";
|
|
2
|
+
import { assertMayDecide, botKey, deciderAuthor } from "../decisions.js";
|
|
3
3
|
import { actorOfOrigin, botNamed, ownerPerson } from "./actors.js";
|
|
4
4
|
import { insertLink, linkOf, purposeOf } from "./notes.js";
|
|
5
5
|
import { proposalOperationsOver, proposerOfAction } from "./proposed-actions.js";
|
|
@@ -47,19 +47,74 @@ export const relationProposalsOver = (context) => {
|
|
|
47
47
|
.run(Number("role" in changes), changes.role ?? null, Number("purpose" in changes), changes.purpose ?? null, now(), Number(link.id));
|
|
48
48
|
return linkOf(database, database.prepare("SELECT * FROM links WHERE id = ?").get(Number(link.id)));
|
|
49
49
|
};
|
|
50
|
+
/**
|
|
51
|
+
* A scan's newer score on a pending proposal, in place under the same id. A rule's own row takes the new score,
|
|
52
|
+
* signals, evidence and purpose; another proposer's row keeps its author, evidence and purpose and gains the signals.
|
|
53
|
+
*/
|
|
54
|
+
const rescored = (action, input, scored, by) => {
|
|
55
|
+
const proposer = proposerOfAction(action);
|
|
56
|
+
const own = proposer.type === "bot" && proposer.kind === "rule" && botKey(proposer.name) === botKey(by.bot);
|
|
57
|
+
const before = action.payload;
|
|
58
|
+
const payload = own
|
|
59
|
+
? {
|
|
60
|
+
...before,
|
|
61
|
+
...scored,
|
|
62
|
+
evidence: input.evidence ?? [],
|
|
63
|
+
...(input.purpose === undefined ? {} : { purpose: purposeOf(input.purpose) }),
|
|
64
|
+
}
|
|
65
|
+
: { ...before, ...scored };
|
|
66
|
+
const reason = own && input.reason !== undefined ? input.reason : action.reason;
|
|
67
|
+
if (JSON.stringify(payload) === JSON.stringify(before) && reason === action.reason)
|
|
68
|
+
return action;
|
|
69
|
+
database
|
|
70
|
+
.prepare("UPDATE proposed_actions SET payload = ?, reason = ?, updated_at = ? WHERE id = ? AND status = 'proposed'")
|
|
71
|
+
.run(JSON.stringify(payload), reason, now(), Number(action.id));
|
|
72
|
+
return operations.get(action.id);
|
|
73
|
+
};
|
|
50
74
|
return {
|
|
51
|
-
/**
|
|
75
|
+
/**
|
|
76
|
+
* Where a pair stands before a scan proposes it: a link joins it, a `relationship` proposal for it was rejected,
|
|
77
|
+
* or one is pending. A rejection is matched by the pair alone, whoever proposed it and with whatever scorer.
|
|
78
|
+
*/
|
|
79
|
+
state: (paired, { to, kind }) => {
|
|
80
|
+
let found;
|
|
81
|
+
try {
|
|
82
|
+
found = paired();
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
if (error instanceof CliError && (error.code === "not_found" || error.code === "validation_error"))
|
|
86
|
+
return;
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
const { from, existing } = found;
|
|
90
|
+
const rows = database
|
|
91
|
+
.prepare("SELECT id, status FROM proposed_actions WHERE kind='relationship' AND status IN ('proposed','rejected') " +
|
|
92
|
+
"AND target_type=? AND target_id=? AND json_extract(payload,'$.to')=? AND json_extract(payload,'$.kind')=? " +
|
|
93
|
+
"ORDER BY id")
|
|
94
|
+
.all(from.type, from.id, to, kind);
|
|
95
|
+
const pending = rows.find((row) => row.status === "proposed");
|
|
96
|
+
return {
|
|
97
|
+
linked: existing !== undefined,
|
|
98
|
+
confirmed: existing !== undefined && Number(existing.confirmed) === 1,
|
|
99
|
+
rejected: rows.some((row) => row.status === "rejected"),
|
|
100
|
+
pending: pending ? operations.get(String(pending.id)) : null,
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
/** The same pending pair returns the proposal already held; a scored one updates its score in place. */
|
|
52
104
|
propose: (from, input, by) => {
|
|
53
105
|
const evidence = input.evidence ?? [];
|
|
54
106
|
if (!Array.isArray(evidence) || evidence.length > 5 || evidence.some((ref) => typeof ref !== "string"))
|
|
55
107
|
throw new CliError("validation_error", "a relationship proposal cites up to 5 references");
|
|
108
|
+
const scored = scoredOf(input);
|
|
56
109
|
return write(database, () => {
|
|
57
110
|
const held = database
|
|
58
111
|
.prepare("SELECT id FROM proposed_actions WHERE kind='relationship' AND status='proposed' AND target_type=? AND target_id=? " +
|
|
59
112
|
"AND json_extract(payload,'$.to')=? AND json_extract(payload,'$.kind')=? ORDER BY id LIMIT 1")
|
|
60
113
|
.get(from.type, from.id, input.to, input.kind);
|
|
61
|
-
if (held)
|
|
62
|
-
|
|
114
|
+
if (held) {
|
|
115
|
+
const action = operations.get(String(held.id));
|
|
116
|
+
return scored ? rescored(action, input, scored, by) : action;
|
|
117
|
+
}
|
|
63
118
|
return operations.propose({
|
|
64
119
|
kind: "relationship",
|
|
65
120
|
by,
|
|
@@ -71,6 +126,7 @@ export const relationProposalsOver = (context) => {
|
|
|
71
126
|
...(input.role === undefined ? {} : { role: input.role }),
|
|
72
127
|
...(input.purpose === undefined ? {} : { purpose: purposeOf(input.purpose) }),
|
|
73
128
|
evidence,
|
|
129
|
+
...scored,
|
|
74
130
|
},
|
|
75
131
|
...(input.reason === undefined ? {} : { reason: input.reason }),
|
|
76
132
|
});
|
|
@@ -174,6 +230,21 @@ export const relationProposalsOver = (context) => {
|
|
|
174
230
|
}),
|
|
175
231
|
};
|
|
176
232
|
};
|
|
233
|
+
const scoredOf = (input) => {
|
|
234
|
+
if (input.score === undefined && input.signals === undefined)
|
|
235
|
+
return undefined;
|
|
236
|
+
const { score, signals } = input;
|
|
237
|
+
if (typeof score !== "number" || !(score >= 0 && score <= 1) || !Array.isArray(signals) || signals.length > 20)
|
|
238
|
+
throw new CliError("validation_error", "a scored proposal takes a score of 0–1 and up to 20 signals");
|
|
239
|
+
for (const signal of signals)
|
|
240
|
+
if (typeof signal?.scorer !== "string" ||
|
|
241
|
+
typeof signal.why !== "string" ||
|
|
242
|
+
signal.why.length > 500 ||
|
|
243
|
+
typeof signal.weight !== "number" ||
|
|
244
|
+
!(signal.weight >= 0 && signal.weight <= 1))
|
|
245
|
+
throw new CliError("validation_error", "a signal names its scorer, a weight of 0–1 and why, up to 500 characters");
|
|
246
|
+
return { score, signals: signals.map(({ scorer, weight, why }) => ({ scorer, weight, why })) };
|
|
247
|
+
};
|
|
177
248
|
/** What a relationship update sets, checked; `null` clears a field and an absent one is left alone. */
|
|
178
249
|
const changesOf = (input) => {
|
|
179
250
|
const changes = {};
|
package/dist/store/store.d.ts
CHANGED
|
@@ -112,6 +112,7 @@ export interface MessageStore {
|
|
|
112
112
|
forAccount(key: AccountKey): AccountStore;
|
|
113
113
|
close(): Promise<void>;
|
|
114
114
|
}
|
|
115
|
+
export type { PaymentFilter } from "./sqlite/facts.js";
|
|
115
116
|
export type { ChatHistoryShape, StoredHistorySkip } from "./sqlite/history-skips.js";
|
|
116
117
|
export { BACKFILL_ON_OPEN, type StoreOptions } from "./sqlite/open-store.js";
|
|
117
118
|
export { fileFacts, readStoreTables, type TableKind } from "./sqlite/store-tables.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirecat/cli-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.551.0",
|
|
4
4
|
"description": "The messenger-neutral half of a messaging CLI: domain model, message locators, rendering, name resolution, the SQLite seam, the command skeleton and shared read commands, the send guard, run records and the message store",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Viacheslav Ptsarev",
|
|
@@ -251,7 +251,7 @@
|
|
|
251
251
|
"@types/node": "^22.20.5",
|
|
252
252
|
"@types/pngjs": "6.0.5",
|
|
253
253
|
"@vitest/coverage-v8": "^5.0.5",
|
|
254
|
-
"@wirecat/cli-base": "0.
|
|
254
|
+
"@wirecat/cli-base": "0.75.0",
|
|
255
255
|
"@wirecat/cli-core-legacy-tags": "npm:@wirecat/cli-core@0.414.0",
|
|
256
256
|
"commander": "15.0.0",
|
|
257
257
|
"cspell": "10.4.0",
|
|
@@ -271,7 +271,7 @@
|
|
|
271
271
|
},
|
|
272
272
|
"peerDependencies": {
|
|
273
273
|
"@napi-rs/canvas": ">=0.1.69 <2",
|
|
274
|
-
"@wirecat/cli-base": ">=0.
|
|
274
|
+
"@wirecat/cli-base": ">=0.75.0 <0.76.0",
|
|
275
275
|
"commander": ">=15"
|
|
276
276
|
},
|
|
277
277
|
"bin": {
|
package/parity.json
CHANGED
|
@@ -3715,6 +3715,17 @@
|
|
|
3715
3715
|
"--sync-time": "all"
|
|
3716
3716
|
}
|
|
3717
3717
|
},
|
|
3718
|
+
"projects status": {
|
|
3719
|
+
"in": ["max", "tg"],
|
|
3720
|
+
"planned": {
|
|
3721
|
+
"wa": "wa backlog: mount projectsCommand once wa is on cli-core 0.504.0"
|
|
3722
|
+
},
|
|
3723
|
+
"options": {
|
|
3724
|
+
"--before-time": "all",
|
|
3725
|
+
"--context-query": "all",
|
|
3726
|
+
"--since-time": "all"
|
|
3727
|
+
}
|
|
3728
|
+
},
|
|
3718
3729
|
"reactions": {
|
|
3719
3730
|
"in": "all"
|
|
3720
3731
|
},
|