@wirecat/cli-core 0.546.0 → 0.548.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 +173 -0
- package/dist/calendar/cli/answers.d.ts +7 -2
- package/dist/calendar/cli/answers.js +12 -4
- package/dist/calendar/cli/index.js +9 -8
- package/dist/calendar/feeds.d.ts +19 -0
- package/dist/calendar/feeds.js +52 -9
- package/dist/calendar/source.d.ts +10 -2
- package/dist/calendar/sync.d.ts +2 -0
- package/dist/calendar/sync.js +10 -6
- package/dist/calendar/testing/memory.js +39 -0
- package/dist/cli/messenger/history-query.d.ts +10 -4
- package/dist/cli/messenger/history-query.js +87 -31
- package/dist/cli/messenger/messages-command.js +10 -4
- package/dist/cli/messenger/messages-comments-command.js +4 -2
- package/dist/cli/messenger/store-maintenance-command.d.ts +1 -1
- package/dist/cli/option-hints.d.ts +6 -0
- package/dist/cli/option-hints.js +15 -0
- package/dist/errors/reasons.js +13 -1
- package/dist/mail/cli/index.js +4 -4
- package/dist/mail/index.d.ts +2 -0
- package/dist/mail/index.js +2 -0
- package/dist/mail/partial.d.ts +34 -0
- package/dist/mail/partial.js +94 -0
- package/dist/mail/server-search.js +17 -9
- package/dist/mail/source.d.ts +13 -2
- package/dist/mail/source.js +7 -1
- package/dist/mail/sync-types.d.ts +112 -0
- package/dist/mail/sync-types.js +1 -0
- package/dist/mail/sync.d.ts +4 -106
- package/dist/mail/sync.js +49 -4
- package/dist/mail/testing/fake.d.ts +3 -1
- package/dist/mail/testing/fake.js +10 -2
- package/dist/meetings/event-store.d.ts +10 -0
- package/dist/meetings/testing/event-store-contract.js +36 -0
- package/dist/services/deps.d.ts +0 -2
- package/dist/services/deps.js +0 -2
- package/dist/services/documents/ingestion.js +1 -1
- package/dist/services/documents/knowledge.d.ts +10 -6
- package/dist/services/documents/knowledge.js +25 -11
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +1 -0
- package/dist/services/messages.d.ts +2 -0
- package/dist/services/messages.js +25 -23
- package/dist/services/proposal-tasks.d.ts +3 -2
- package/dist/services/proposal-tasks.js +5 -2
- package/dist/services/proposals/ids.d.ts +5 -0
- package/dist/services/proposals/ids.js +43 -0
- package/dist/services/proposals/index.d.ts +3 -0
- package/dist/services/proposals/index.js +2 -0
- package/dist/services/proposals/kinds/action.d.ts +7 -0
- package/dist/services/proposals/kinds/action.js +45 -0
- package/dist/services/proposals/kinds/fact.d.ts +4 -0
- package/dist/services/proposals/kinds/fact.js +43 -0
- package/dist/services/proposals/kinds/findings.d.ts +7 -0
- package/dist/services/proposals/kinds/findings.js +75 -0
- package/dist/services/proposals/kinds/key-document.d.ts +9 -0
- package/dist/services/proposals/kinds/key-document.js +75 -0
- package/dist/services/proposals/kinds/memory.d.ts +4 -0
- package/dist/services/proposals/kinds/memory.js +42 -0
- package/dist/services/proposals/kinds/relationship.d.ts +10 -0
- package/dist/services/proposals/kinds/relationship.js +82 -0
- package/dist/services/proposals/kinds/shared.d.ts +25 -0
- package/dist/services/proposals/kinds/shared.js +49 -0
- package/dist/services/proposals/registry.d.ts +9 -0
- package/dist/services/proposals/registry.js +129 -0
- package/dist/services/proposals/types.d.ts +85 -0
- package/dist/services/proposals/types.js +1 -0
- package/dist/services/server-search.d.ts +7 -4
- package/dist/services/server-search.js +5 -2
- package/dist/services/tasks.js +8 -4
- package/dist/store/decisions.d.ts +65 -0
- package/dist/store/decisions.js +55 -0
- package/dist/store/evidence-findings.d.ts +6 -6
- package/dist/store/index.d.ts +4 -3
- package/dist/store/index.js +2 -1
- package/dist/store/meeting-findings.d.ts +2 -0
- package/dist/store/messages.d.ts +4 -9
- package/dist/store/sqlite/accounts.d.ts +8 -0
- package/dist/store/sqlite/accounts.js +2 -2
- package/dist/store/sqlite/actors.d.ts +13 -2
- package/dist/store/sqlite/actors.js +24 -1
- package/dist/store/sqlite/calendar-forget.d.ts +17 -0
- package/dist/store/sqlite/calendar-forget.js +68 -0
- package/dist/store/sqlite/calendar.js +8 -0
- package/dist/store/sqlite/email-locations.d.ts +16 -0
- package/dist/store/sqlite/email-locations.js +26 -0
- package/dist/store/sqlite/emails.d.ts +6 -2
- package/dist/store/sqlite/emails.js +2 -1
- package/dist/store/sqlite/evidence-findings.js +13 -3
- package/dist/store/sqlite/facts.d.ts +16 -8
- package/dist/store/sqlite/facts.js +48 -15
- package/dist/store/sqlite/findings-core.d.ts +2 -1
- package/dist/store/sqlite/findings-core.js +4 -4
- package/dist/store/sqlite/knowledge.d.ts +43 -7
- package/dist/store/sqlite/knowledge.js +55 -15
- package/dist/store/sqlite/meeting-findings.js +6 -6
- package/dist/store/sqlite/memories.d.ts +12 -8
- package/dist/store/sqlite/memories.js +33 -10
- package/dist/store/sqlite/notes.d.ts +5 -1
- package/dist/store/sqlite/notes.js +18 -2
- package/dist/store/sqlite/open-store.d.ts +4 -1
- package/dist/store/sqlite/open-store.js +8 -2
- package/dist/store/sqlite/open.d.ts +3 -0
- package/dist/store/sqlite/proposed-actions.d.ts +21 -8
- package/dist/store/sqlite/proposed-actions.js +29 -9
- package/dist/store/sqlite/reads.d.ts +13 -2
- package/dist/store/sqlite/reads.js +8 -4
- package/dist/store/sqlite/relation-proposals.d.ts +34 -0
- package/dist/store/sqlite/relation-proposals.js +114 -0
- package/dist/store/store.d.ts +3 -0
- package/dist/store/store.js +2 -0
- package/package.json +1 -1
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
2
|
import { MEETING_FINDINGS_KIND } from "../../meetings/index.js";
|
|
3
|
+
import { assertMayDecide, deciderAuthor } from "../decisions.js";
|
|
3
4
|
import { EVIDENCE_FINDINGS_KIND } from "../evidence-findings.js";
|
|
5
|
+
import { proposedBySql } from "./actors.js";
|
|
4
6
|
import { authorActor } from "./memories.js";
|
|
5
7
|
import { requiredThing } from "./notes.js";
|
|
6
8
|
import { referenceOfThing, storedThing } from "./things.js";
|
|
@@ -15,8 +17,14 @@ const json = (value) => (value == null ? null : JSON.parse(String(value)));
|
|
|
15
17
|
*/
|
|
16
18
|
const STATUS = `CASE WHEN p.status = 'approved' AND p.kind IN ('${MEETING_FINDINGS_KIND}', '${EVIDENCE_FINDINGS_KIND}') ` +
|
|
17
19
|
"AND json_array_length(p.result, '$.accepted') = 0 THEN 'rejected' ELSE p.status END";
|
|
18
|
-
const NAMED = `SELECT p.*, ${STATUS} AS shown_status, b.name AS proposer_name FROM proposed_actions p ` +
|
|
20
|
+
const NAMED = `SELECT p.*, ${STATUS} AS shown_status, b.name AS proposer_name, b.kind AS proposer_kind FROM proposed_actions p ` +
|
|
19
21
|
"LEFT JOIN bots b ON p.proposed_by_type = 'bot' AND b.id = p.proposed_by_id";
|
|
22
|
+
/** Who proposed an action, for `mayDecide`. */
|
|
23
|
+
export const proposerOfAction = ({ proposedBy }) => proposedBy.type === "person"
|
|
24
|
+
? { type: "person", id: proposedBy.id }
|
|
25
|
+
: proposedBy.name === null
|
|
26
|
+
? { type: "unknown" }
|
|
27
|
+
: { type: "bot", id: proposedBy.id, name: proposedBy.name, kind: proposedBy.kind ?? "agent" };
|
|
20
28
|
export const proposalOperationsOver = (context) => {
|
|
21
29
|
const { database, now } = context;
|
|
22
30
|
const actionOf = (row) => {
|
|
@@ -37,6 +45,11 @@ export const proposalOperationsOver = (context) => {
|
|
|
37
45
|
type: row.proposed_by_type,
|
|
38
46
|
id: String(row.proposed_by_id),
|
|
39
47
|
name: row.proposer_name == null ? null : String(row.proposer_name),
|
|
48
|
+
kind: row.proposed_by_type !== "bot"
|
|
49
|
+
? null
|
|
50
|
+
: row.proposer_kind === "rule" || row.proposer_name === "rule"
|
|
51
|
+
? "rule"
|
|
52
|
+
: "agent",
|
|
40
53
|
},
|
|
41
54
|
decidedBy: row.decided_by_type == null
|
|
42
55
|
? null
|
|
@@ -108,16 +121,21 @@ export const proposalOperationsOver = (context) => {
|
|
|
108
121
|
.prepare(`${NAMED} WHERE p.status = 'proposed' ORDER BY p.created_at, p.id LIMIT ?`)
|
|
109
122
|
.all(Math.min(Math.max(limit, 1), 500))
|
|
110
123
|
.map(actionOf),
|
|
111
|
-
list: ({ status, limit = 100 } = {}) => {
|
|
124
|
+
list: ({ status, limit = 100, kinds, except, proposedBy, project } = {}) => {
|
|
112
125
|
if (status !== undefined && !PROPOSAL_STATUSES.includes(status))
|
|
113
126
|
throw new CliError("validation_error", `a status is one of ${PROPOSAL_STATUSES.join(", ")}`);
|
|
127
|
+
const by = proposedBySql("p.proposed_by_type", "p.proposed_by_id", proposedBy);
|
|
114
128
|
return database
|
|
115
|
-
.prepare(`${NAMED} WHERE (? IS NULL OR ${STATUS} = ?)
|
|
116
|
-
.
|
|
129
|
+
.prepare(`${NAMED} WHERE (? IS NULL OR ${STATUS} = ?) AND (? IS NULL OR p.kind IN (SELECT value FROM json_each(?))) ` +
|
|
130
|
+
"AND p.kind NOT IN (SELECT value FROM json_each(?)) AND (? IS NULL OR (p.target_type = 'project' AND " +
|
|
131
|
+
"'project:' || p.target_id = ?) OR json_extract(p.payload, '$.to') = ? OR json_extract(p.payload, '$.from') = ? " +
|
|
132
|
+
`OR json_extract(p.payload, '$.project') = ?) AND ${by.sql} ORDER BY p.created_at DESC, p.id DESC LIMIT ?`)
|
|
133
|
+
.all(status ?? null, status ?? null, kinds === undefined ? null : 1, JSON.stringify(kinds ?? []), JSON.stringify(except ?? []), ...Array(5).fill(project ?? null), ...by.params, Math.min(Math.max(limit, 1), 500))
|
|
117
134
|
.map(actionOf);
|
|
118
135
|
},
|
|
119
136
|
approve: (id, result, by) => decide(id, "approved", result, by),
|
|
120
|
-
reject: (id) => decide(id, "rejected"),
|
|
137
|
+
reject: (id, by, result) => decide(id, "rejected", result, by),
|
|
138
|
+
proposer: (id) => proposerOfAction(actionOf(rowOf(id))),
|
|
121
139
|
executed: (id, result) => move(id, ["approved"], "executed", ", executed_at = ?, result = ?", [
|
|
122
140
|
now(),
|
|
123
141
|
result === undefined ? null : JSON.stringify(result),
|
|
@@ -161,13 +179,15 @@ export const proposedActionsStoreOver = (context) => {
|
|
|
161
179
|
get: async (id) => operations.get(id),
|
|
162
180
|
pending: async (options) => operations.pending(options),
|
|
163
181
|
list: async (options) => operations.list(options),
|
|
164
|
-
approve: async (id, result) => {
|
|
182
|
+
approve: async (id, by, result) => {
|
|
165
183
|
generic(id);
|
|
166
|
-
|
|
184
|
+
assertMayDecide({ id, proposedBy: operations.proposer(id) }, by, context.decisions);
|
|
185
|
+
return operations.approve(id, result, deciderAuthor(by));
|
|
167
186
|
},
|
|
168
|
-
reject: async (id) => {
|
|
187
|
+
reject: async (id, by, reason) => {
|
|
169
188
|
generic(id);
|
|
170
|
-
|
|
189
|
+
assertMayDecide({ id, proposedBy: operations.proposer(id) }, by, context.decisions);
|
|
190
|
+
return operations.reject(id, deciderAuthor(by), reason?.trim() ? { reason: reason.trim() } : undefined);
|
|
171
191
|
},
|
|
172
192
|
executed: async (id, result) => {
|
|
173
193
|
generic(id);
|
|
@@ -898,14 +898,25 @@ export declare const messagesWindow: (context: StoreContext, chatKey: number, {
|
|
|
898
898
|
before: number;
|
|
899
899
|
after: number;
|
|
900
900
|
}) => Message[];
|
|
901
|
-
|
|
901
|
+
/**
|
|
902
|
+
* `before` pages back from a message, newest page first. `after` reads forward from a message or strictly after a
|
|
903
|
+
* moment (epoch ms), oldest first. `until` (epoch ms, inclusive) ends either read.
|
|
904
|
+
*/
|
|
905
|
+
export interface MessagePageWindow {
|
|
902
906
|
limit: number;
|
|
903
907
|
before?: Id;
|
|
908
|
+
after?: {
|
|
909
|
+
id: Id;
|
|
910
|
+
} | {
|
|
911
|
+
time: number;
|
|
912
|
+
};
|
|
904
913
|
since?: string;
|
|
914
|
+
until?: number;
|
|
905
915
|
threadId?: Id;
|
|
906
916
|
outgoing?: boolean;
|
|
907
917
|
starred?: boolean;
|
|
908
|
-
}
|
|
918
|
+
}
|
|
919
|
+
export declare const messagePage: (context: StoreContext, chatKey: number, { limit, before: beforeId, after: start, since, until, threadId, outgoing, starred }: MessagePageWindow) => Page<Message>;
|
|
909
920
|
export declare const around: (context: StoreContext, chatKey: number | undefined, messageId: Id, { before: earlier, after: later }: {
|
|
910
921
|
before: number;
|
|
911
922
|
after: number;
|
|
@@ -33,6 +33,7 @@ export const selectMessages = ({ orm }) => orm
|
|
|
33
33
|
.innerJoin(chats, eq(chats.id, messages.chatId))
|
|
34
34
|
.leftJoin(identities, eq(identities.id, messages.senderIdentityId));
|
|
35
35
|
export const newestFirst = [desc(messages.sentAt), desc(messages.id)];
|
|
36
|
+
const oldestFirst = [asc(messages.sentAt), asc(messages.id)];
|
|
36
37
|
export const before = (sentAt, pk) => sql `(${messages.sentAt}, ${messages.id}) < (${sentAt}, ${pk})`;
|
|
37
38
|
const after = (sentAt, pk) => sql `(${messages.sentAt}, ${messages.id}) > (${sentAt}, ${pk})`;
|
|
38
39
|
const live = (chatKey, ...conditions) => and(eq(messages.chatId, chatKey), isNull(messages.deletedAt), ...conditions);
|
|
@@ -92,7 +93,9 @@ export const messagesWindow = (context, chatKey, { at, before: earlier, after: l
|
|
|
92
93
|
.all();
|
|
93
94
|
return toMessages(context, [...older.reverse(), ...newer]);
|
|
94
95
|
};
|
|
95
|
-
export const messagePage = (context, chatKey, { limit, before:
|
|
96
|
+
export const messagePage = (context, chatKey, { limit, before: beforeId, after: start, since, until, threadId, outgoing, starred }) => {
|
|
97
|
+
const forward = start !== undefined;
|
|
98
|
+
const anchorId = forward ? ("id" in start ? start.id : undefined) : beforeId;
|
|
96
99
|
const anchor = anchorId === undefined
|
|
97
100
|
? undefined
|
|
98
101
|
: context.orm
|
|
@@ -104,7 +107,7 @@ export const messagePage = (context, chatKey, { limit, before: anchorId, since,
|
|
|
104
107
|
throw new CliError("not_found", `message ${anchorId} is not in the local copy of this chat`);
|
|
105
108
|
}
|
|
106
109
|
const rows = selectMessages(context)
|
|
107
|
-
.where(live(chatKey, anchor ? before(anchor.sentAt, anchor.pk) : undefined, since === undefined ? undefined : gte(messages.sentAt, toMs(since)), threadId === undefined ? undefined : eq(messages.threadExternalId, threadId), outgoing === undefined
|
|
110
|
+
.where(live(chatKey, anchor ? (forward ? after : before)(anchor.sentAt, anchor.pk) : undefined, start !== undefined && "time" in start ? gt(messages.sentAt, start.time) : undefined, since === undefined ? undefined : gte(messages.sentAt, toMs(since)), until === undefined ? undefined : lte(messages.sentAt, until), threadId === undefined ? undefined : eq(messages.threadExternalId, threadId), outgoing === undefined
|
|
108
111
|
? undefined
|
|
109
112
|
: outgoing
|
|
110
113
|
? eq(messages.outgoing, 1)
|
|
@@ -113,10 +116,11 @@ export const messagePage = (context, chatKey, { limit, before: anchorId, since,
|
|
|
113
116
|
: starred
|
|
114
117
|
? sql `${messages.starred} = 1`
|
|
115
118
|
: or(eq(messages.starred, 0), isNull(messages.starred))))
|
|
116
|
-
.orderBy(...newestFirst)
|
|
119
|
+
.orderBy(...(forward ? oldestFirst : newestFirst))
|
|
117
120
|
.limit(limit + 1)
|
|
118
121
|
.all();
|
|
119
|
-
|
|
122
|
+
const page = toMessages(context, rows.slice(0, limit));
|
|
123
|
+
return { items: forward ? page : page.reverse(), hasMore: rows.length > limit };
|
|
120
124
|
};
|
|
121
125
|
export const around = (context, chatKey, messageId, { before: earlier, after: later }) => {
|
|
122
126
|
const anchor = chatKey === undefined
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type Decider } from "../decisions.js";
|
|
2
|
+
import type { Author } from "./memories.js";
|
|
3
|
+
import { type Link } from "./notes.js";
|
|
4
|
+
import type { StoreContext } from "./open.js";
|
|
5
|
+
import { type ProposedAction } from "./proposed-actions.js";
|
|
6
|
+
import { type Thing } from "./things.js";
|
|
7
|
+
/** `relationship` proposals in `proposed_actions`: an agent's or a rule's, and the rejected ones a scan skips. */
|
|
8
|
+
export declare const relationProposalsOver: (context: StoreContext) => {
|
|
9
|
+
/** The same pending pair returns the proposal already held. */
|
|
10
|
+
propose: (from: Thing, input: {
|
|
11
|
+
from: string;
|
|
12
|
+
to: string;
|
|
13
|
+
kind: string;
|
|
14
|
+
role?: string;
|
|
15
|
+
evidence?: string[];
|
|
16
|
+
reason?: string;
|
|
17
|
+
}, by: Exclude<Author, "owner">) => ProposedAction;
|
|
18
|
+
rejected: (link: Link, by: Decider, reason?: string) => void;
|
|
19
|
+
/**
|
|
20
|
+
* Decides a stored `relationship` or `key_document` proposal in one transaction: the confirmed link and the
|
|
21
|
+
* decision land together or not at all.
|
|
22
|
+
*/
|
|
23
|
+
decideProposal: (id: string, decision: LinkDecision) => {
|
|
24
|
+
proposal: ProposedAction;
|
|
25
|
+
link: Link | null;
|
|
26
|
+
};
|
|
27
|
+
/** Decides an unconfirmed link written before proposals were stored; only the owner may, as its author is unknown. */
|
|
28
|
+
decideLink: (id: string, decision: LinkDecision) => Link | null;
|
|
29
|
+
};
|
|
30
|
+
export interface LinkDecision {
|
|
31
|
+
confirm: boolean;
|
|
32
|
+
by: Decider;
|
|
33
|
+
reason?: string;
|
|
34
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { CliError } from "@wirecat/cli-base";
|
|
2
|
+
import { assertMayDecide, deciderAuthor } from "../decisions.js";
|
|
3
|
+
import { actorOfOrigin, botNamed, ownerPerson } from "./actors.js";
|
|
4
|
+
import { insertLink, linkOf } from "./notes.js";
|
|
5
|
+
import { proposalOperationsOver, proposerOfAction } from "./proposed-actions.js";
|
|
6
|
+
import { thingOf } from "./things.js";
|
|
7
|
+
import { write } from "./transaction.js";
|
|
8
|
+
/** `relationship` proposals in `proposed_actions`: an agent's or a rule's, and the rejected ones a scan skips. */
|
|
9
|
+
export const relationProposalsOver = (context) => {
|
|
10
|
+
const { database, now } = context;
|
|
11
|
+
const operations = proposalOperationsOver(context);
|
|
12
|
+
/** The rejected `relationship` proposal a removed link leaves behind, so a scan never proposes it again. */
|
|
13
|
+
const rejected = (link, by, reason) => {
|
|
14
|
+
const at = now();
|
|
15
|
+
const proposed = link.confirmed ? ownerPerson(database) : actorOfOrigin(database, "agent");
|
|
16
|
+
const decided = by === "owner" ? ownerPerson(database) : botNamed(database, by.bot, at);
|
|
17
|
+
const why = [
|
|
18
|
+
`${link.confirmed ? "removed" : "rejected"} by ${by === "owner" ? "the owner" : `agent:${by.bot}`}`,
|
|
19
|
+
by === "owner" ? undefined : by.reason.trim(),
|
|
20
|
+
reason?.trim() || undefined,
|
|
21
|
+
].filter(Boolean);
|
|
22
|
+
const from = thingOf(database, link.from);
|
|
23
|
+
database
|
|
24
|
+
.prepare("INSERT INTO proposed_actions (kind, target_type, target_id, payload, reason, status, proposed_by_type, " +
|
|
25
|
+
"proposed_by_id, decided_by_type, decided_by_id, decided_at, result, created_at, updated_at) " +
|
|
26
|
+
"VALUES ('relationship', ?, ?, ?, ?, 'rejected', ?, ?, ?, ?, ?, ?, ?, ?)")
|
|
27
|
+
.run(from?.type ?? null, from?.id ?? null, JSON.stringify({ from: link.from, to: link.to, kind: link.kind, ...(link.role ? { role: link.role } : {}) }), link.evidence, proposed.type, proposed.id, decided.type, decided.id, at, JSON.stringify({ reason: why.join(": "), link: link.id }), at, at);
|
|
28
|
+
};
|
|
29
|
+
return {
|
|
30
|
+
/** The same pending pair returns the proposal already held. */
|
|
31
|
+
propose: (from, input, by) => {
|
|
32
|
+
const evidence = input.evidence ?? [];
|
|
33
|
+
if (!Array.isArray(evidence) || evidence.length > 5 || evidence.some((ref) => typeof ref !== "string"))
|
|
34
|
+
throw new CliError("validation_error", "a relationship proposal cites up to 5 references");
|
|
35
|
+
return write(database, () => {
|
|
36
|
+
const held = database
|
|
37
|
+
.prepare("SELECT id FROM proposed_actions WHERE kind='relationship' AND status='proposed' AND target_type=? AND target_id=? " +
|
|
38
|
+
"AND json_extract(payload,'$.to')=? AND json_extract(payload,'$.kind')=? ORDER BY id LIMIT 1")
|
|
39
|
+
.get(from.type, from.id, input.to, input.kind);
|
|
40
|
+
if (held)
|
|
41
|
+
return operations.get(String(held.id));
|
|
42
|
+
return operations.propose({
|
|
43
|
+
kind: "relationship",
|
|
44
|
+
by,
|
|
45
|
+
target: input.from,
|
|
46
|
+
payload: {
|
|
47
|
+
from: input.from,
|
|
48
|
+
to: input.to,
|
|
49
|
+
kind: input.kind,
|
|
50
|
+
...(input.role === undefined ? {} : { role: input.role }),
|
|
51
|
+
evidence,
|
|
52
|
+
},
|
|
53
|
+
...(input.reason === undefined ? {} : { reason: input.reason }),
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
rejected: (link, by, reason) => rejected(link, by, reason),
|
|
58
|
+
/**
|
|
59
|
+
* Decides a stored `relationship` or `key_document` proposal in one transaction: the confirmed link and the
|
|
60
|
+
* decision land together or not at all.
|
|
61
|
+
*/
|
|
62
|
+
decideProposal: (id, decision) => write(database, () => {
|
|
63
|
+
const proposal = operations.get(id);
|
|
64
|
+
if (proposal.kind !== "relationship" && proposal.kind !== "key_document")
|
|
65
|
+
throw new CliError("validation_error", `${id} is a ${proposal.kind} proposal, not a link`);
|
|
66
|
+
assertMayDecide({ id, proposedBy: proposerOfAction(proposal) }, decision.by, context.decisions);
|
|
67
|
+
const author = deciderAuthor(decision.by);
|
|
68
|
+
const reason = decision.reason?.trim() || undefined;
|
|
69
|
+
if (!decision.confirm)
|
|
70
|
+
return { proposal: operations.reject(id, author, reason === undefined ? undefined : { reason }), link: null };
|
|
71
|
+
const payload = proposal.payload;
|
|
72
|
+
const key = proposal.kind === "key_document";
|
|
73
|
+
const link = insertLink(context, {
|
|
74
|
+
from: String(key ? payload.ref : payload.from),
|
|
75
|
+
to: String(key ? payload.project : payload.to),
|
|
76
|
+
kind: key ? "related-to" : (payload.kind ?? "member-of"),
|
|
77
|
+
...(key ? { role: "key" } : payload.role === undefined ? {} : { role: payload.role }),
|
|
78
|
+
...(proposal.reason === null ? {} : { evidence: proposal.reason }),
|
|
79
|
+
...decidedOrigin(decision.by),
|
|
80
|
+
});
|
|
81
|
+
return {
|
|
82
|
+
proposal: operations.approve(id, { link: link.id, ...(reason === undefined ? {} : { reason }) }, author),
|
|
83
|
+
link,
|
|
84
|
+
};
|
|
85
|
+
}),
|
|
86
|
+
/** Decides an unconfirmed link written before proposals were stored; only the owner may, as its author is unknown. */
|
|
87
|
+
decideLink: (id, decision) => write(database, () => {
|
|
88
|
+
const row = /^\d+$/.test(id)
|
|
89
|
+
? database.prepare("SELECT * FROM links WHERE id = ? AND confirmed = 0").get(Number(id))
|
|
90
|
+
: undefined;
|
|
91
|
+
if (!row)
|
|
92
|
+
throw new CliError("not_found", `no suggested link ${id}`);
|
|
93
|
+
assertMayDecide({ id: `link:${id}`, proposedBy: { type: "unknown" } }, decision.by, context.decisions);
|
|
94
|
+
const link = linkOf(database, row);
|
|
95
|
+
if (!decision.confirm) {
|
|
96
|
+
rejected(link, decision.by, decision.reason);
|
|
97
|
+
database.prepare("DELETE FROM links WHERE id = ?").run(Number(id));
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
database
|
|
101
|
+
.prepare("UPDATE links SET confirmed=1, source='owner', author='owner', updated_at=? WHERE id=?")
|
|
102
|
+
.run(now(), Number(id));
|
|
103
|
+
return linkOf(database, database.prepare("SELECT * FROM links WHERE id = ?").get(Number(id)));
|
|
104
|
+
}),
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
/** A link an agent confirmed for the owner says so in its provenance. */
|
|
108
|
+
const decidedOrigin = (by) => by === "owner"
|
|
109
|
+
? { origin: "owner" }
|
|
110
|
+
: {
|
|
111
|
+
origin: "suggested",
|
|
112
|
+
confirmed: true,
|
|
113
|
+
provenance: `agent:${by.bot} for the owner: ${by.reason.trim()}`,
|
|
114
|
+
};
|
package/dist/store/store.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { type AttachmentsStore } from "./attachments.js";
|
|
|
6
6
|
import { type ChatsStore } from "./chats.js";
|
|
7
7
|
import { type ConversationsStore } from "./conversations.js";
|
|
8
8
|
import { type CountersStore } from "./counters.js";
|
|
9
|
+
import { type DecisionPolicy } from "./decisions.js";
|
|
9
10
|
import type { EvidenceFindingsStore } from "./evidence-findings.js";
|
|
10
11
|
import type { MeetingFindingsStore } from "./meeting-findings.js";
|
|
11
12
|
import { type MentionsStore } from "./mentions.js";
|
|
@@ -95,6 +96,8 @@ export interface MessageStore {
|
|
|
95
96
|
readonly memories: MemoriesStore;
|
|
96
97
|
/** What an agent wants done outside the store, waiting for the owner's approval. */
|
|
97
98
|
readonly proposedActions: ProposedActionsStore;
|
|
99
|
+
/** Who may decide proposals, fixed when the store was opened; the store's decide methods and the registry read it. */
|
|
100
|
+
readonly decisionPolicy: DecisionPolicy;
|
|
98
101
|
/** One row per tool an agent called, never its arguments. */
|
|
99
102
|
readonly agentActions: AgentActionsStore;
|
|
100
103
|
/** Meetings and calendar events: the port `src/meetings/` defines. */
|
package/dist/store/store.js
CHANGED
|
@@ -4,6 +4,7 @@ import { accountAttachmentsOver, attachmentsStoreOver, } from "./attachments.js"
|
|
|
4
4
|
import { chatsStoreOver } from "./chats.js";
|
|
5
5
|
import { conversationsStoreOver } from "./conversations.js";
|
|
6
6
|
import { accountCountersOver, countersStoreOver } from "./counters.js";
|
|
7
|
+
import { DEFAULT_DECISION_POLICY } from "./decisions.js";
|
|
7
8
|
import { accountMentionsOver, mentionsStoreOver } from "./mentions.js";
|
|
8
9
|
import { accountMessagesOver, messagesStoreOver } from "./messages.js";
|
|
9
10
|
import { accountPeopleOver, peopleStoreOver } from "./people.js";
|
|
@@ -207,6 +208,7 @@ const storeOver = (context) => {
|
|
|
207
208
|
get memories() {
|
|
208
209
|
return memories();
|
|
209
210
|
},
|
|
211
|
+
decisionPolicy: context.decisions ?? DEFAULT_DECISION_POLICY,
|
|
210
212
|
get proposedActions() {
|
|
211
213
|
return proposedActions();
|
|
212
214
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirecat/cli-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.548.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",
|