@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
package/dist/services/index.d.ts
CHANGED
|
@@ -181,6 +181,7 @@ export { type PackageUpgradeOutcome, type PackageUpgradePorts, type PackageUpgra
|
|
|
181
181
|
export { type PersonMeetingContext, type PersonMeetingContextItem, type PersonMeetingContextOptions, type PersonMeetingReadStore, personMeetingContext, } from "./person-meeting-context.js";
|
|
182
182
|
export { privatePeopleService } from "./private-people.js";
|
|
183
183
|
export { approveAsTask, type ProposalTaskStore, proposalSource } from "./proposal-tasks.js";
|
|
184
|
+
export { type Decided, type Decision, emptyProposalRegistry, type ProposalFilter, type ProposalKind, type ProposalRegistry, type ProposalRow, parseProposalIds, proposalRegistry, } from "./proposals/index.js";
|
|
184
185
|
export type { RetentionQuery, RetentionService } from "./retention.js";
|
|
185
186
|
export { retentionService } from "./retention.js";
|
|
186
187
|
export { RESOURCES_SEARCHED, type SearchAllFound, type SearchAllItem, type SearchAllMessenger, type SearchAllRequest, type SearchedResource, searchAll, } from "./search-all.js";
|
package/dist/services/index.js
CHANGED
|
@@ -139,6 +139,7 @@ export { upgradePackage, } from "./package-upgrade.js";
|
|
|
139
139
|
export { personMeetingContext, } from "./person-meeting-context.js";
|
|
140
140
|
export { privatePeopleService } from "./private-people.js";
|
|
141
141
|
export { approveAsTask, proposalSource } from "./proposal-tasks.js";
|
|
142
|
+
export { emptyProposalRegistry, parseProposalIds, proposalRegistry, } from "./proposals/index.js";
|
|
142
143
|
export { retentionService } from "./retention.js";
|
|
143
144
|
export { RESOURCES_SEARCHED, searchAll, } from "./search-all.js";
|
|
144
145
|
export { RESOURCES_SEARCHED_WITH_MEETINGS, searchAllIncludingMeetings, searchAllWithMeetings, } from "./search-all-meetings.js";
|
|
@@ -24,6 +24,8 @@ export interface ListWindow {
|
|
|
24
24
|
beforeTime?: number;
|
|
25
25
|
/** Read forward from this message or moment, parsed by the caller in its own words (`afterOf`). */
|
|
26
26
|
after?: After;
|
|
27
|
+
/** The window's last moment, epoch milliseconds, inclusive: a forward read stops here. */
|
|
28
|
+
until?: number;
|
|
27
29
|
/** Only the messages the owner starred (true) or did not (false); the store answers it, never the messenger. */
|
|
28
30
|
starred?: boolean;
|
|
29
31
|
/** Only this forum topic. */
|
|
@@ -10,7 +10,7 @@ import { codeOf, guardedWrite, sentIds } from "../sends/guarded.js";
|
|
|
10
10
|
import { sendAsCheck } from "../sends/send-as.js";
|
|
11
11
|
import { newOperationId, newSendId } from "../sends/send-id.js";
|
|
12
12
|
import { uploadSize } from "../sends/upload.js";
|
|
13
|
-
import { crossAccount, fromStore
|
|
13
|
+
import { crossAccount, fromStore } from "./deps.js";
|
|
14
14
|
import { readChatId, storedChatId } from "./message-resolution.js";
|
|
15
15
|
import { accountOf, keyOf, searchStore, statsStore, topUp } from "./messages-archive-search.js";
|
|
16
16
|
import { meaningLane } from "./messages-meaning.js";
|
|
@@ -84,8 +84,8 @@ export const messagesService = (deps) => {
|
|
|
84
84
|
await guardedWrite(guard, { operationId, chatId, kind: "pin", key: pinned ? "messages.pin" : "messages.unpin", messageId: message, notify }, () => act(chatId, message, { notify, ...(duration === undefined ? {} : { duration }) }));
|
|
85
85
|
return { operationId, chatId, messageId: message, pinned };
|
|
86
86
|
};
|
|
87
|
-
const topicPage = async (chat, threadId, { limit, before, beforeTime, after }) => {
|
|
88
|
-
if (beforeTime !== undefined || after !== undefined)
|
|
87
|
+
const topicPage = async (chat, threadId, { limit, before, beforeTime, after, until }) => {
|
|
88
|
+
if (beforeTime !== undefined || after !== undefined || until !== undefined)
|
|
89
89
|
throw new CliError("validation_error", "a topic is read back from its newest message, or from --before-id");
|
|
90
90
|
if (threadId === "1")
|
|
91
91
|
throw new CliError("validation_error", "the General topic's messages carry no topic id — read the whole chat without --topic");
|
|
@@ -95,11 +95,11 @@ export const messagesService = (deps) => {
|
|
|
95
95
|
}
|
|
96
96
|
return capability(await deps.connection(), "topicHistory", "read one forum topic")(chat, threadId, window);
|
|
97
97
|
};
|
|
98
|
-
const listPage = async (chat, { limit, before, beforeTime, after, threadId: typedThread, starred }) => {
|
|
98
|
+
const listPage = async (chat, { limit, before, beforeTime, after, until, threadId: typedThread, starred }) => {
|
|
99
99
|
if (starred !== undefined) {
|
|
100
100
|
if (!fromStore(deps))
|
|
101
101
|
throw new CliError("validation_error", "starred: reads the local store; not while reading from the messenger");
|
|
102
|
-
if (typedThread !== undefined || beforeTime !== undefined || after !== undefined)
|
|
102
|
+
if (typedThread !== undefined || beforeTime !== undefined || after !== undefined || until !== undefined)
|
|
103
103
|
throw new CliError("validation_error", "starred: pages back through the whole chat; give only id< with it");
|
|
104
104
|
return inStore(async (store, account) => store.messages.messages(account, await readChatId(deps, chat, store, account), {
|
|
105
105
|
limit,
|
|
@@ -109,28 +109,30 @@ export const messagesService = (deps) => {
|
|
|
109
109
|
}
|
|
110
110
|
const threadId = threadIdOf(typedThread);
|
|
111
111
|
if (threadId !== undefined)
|
|
112
|
-
return topicPage(chat, threadId, { limit, before, beforeTime, after });
|
|
113
|
-
if (
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
112
|
+
return topicPage(chat, threadId, { limit, before, beforeTime, after, until });
|
|
113
|
+
if (fromStore(deps)) {
|
|
114
|
+
const window = after
|
|
115
|
+
? { limit, after, ...(until === undefined ? {} : { until }) }
|
|
116
|
+
: {
|
|
117
|
+
limit,
|
|
118
|
+
...(before === undefined ? {} : { before }),
|
|
119
|
+
// historyBefore reads strictly before its moment; the store's end is inclusive.
|
|
120
|
+
...(beforeTime === undefined ? {} : { until: beforeTime - 1 }),
|
|
121
|
+
};
|
|
122
|
+
return inStore(async (store, account) => store.messages.messages(account, await readChatId(deps, chat, store, account), window));
|
|
120
123
|
}
|
|
124
|
+
const connection = await deps.connection();
|
|
125
|
+
if (beforeTime !== undefined)
|
|
126
|
+
return capability(connection, "historyBefore", "read back from a time")(chat, { limit, time: beforeTime });
|
|
121
127
|
if (after !== undefined) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
return capability(connection, "historyAfter", "read forward from a message")(chat, { limit, after });
|
|
128
|
+
const page = await capability(connection, "historyAfter", "read forward from a message")(chat, { limit, after });
|
|
129
|
+
if (until === undefined)
|
|
130
|
+
return page;
|
|
131
|
+
const items = page.items.filter((one) => Date.parse(one.timestamp) <= until);
|
|
132
|
+
return { items, hasMore: page.hasMore && items.length === page.items.length };
|
|
128
133
|
}
|
|
129
134
|
const window = { limit, ...(before === undefined ? {} : { before }) };
|
|
130
|
-
|
|
131
|
-
return inStore(async (store, account) => store.messages.messages(account, await readChatId(deps, chat, store, account), window));
|
|
132
|
-
}
|
|
133
|
-
return capability(await deps.connection(), "history", "read a chat's history")(chat, window);
|
|
135
|
+
return capability(connection, "history", "read a chat's history")(chat, window);
|
|
134
136
|
};
|
|
135
137
|
return {
|
|
136
138
|
// A provider caps its pages (Telegram at 100), so a larger --limit reads on from the last page's edge.
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
+
import { type Decider } from "../store/decisions.js";
|
|
1
2
|
import type { MessageStore, ProposedAction } from "../store/store.js";
|
|
2
3
|
import { type Task } from "../tasks/index.js";
|
|
3
|
-
export type ProposalTaskStore = Pick<MessageStore, "proposedActions" | "tasks">;
|
|
4
|
+
export type ProposalTaskStore = Pick<MessageStore, "proposedActions" | "tasks" | "decisionPolicy">;
|
|
4
5
|
/** The source a proposal's task keeps: found again on a retry, so one proposal never makes two tasks. */
|
|
5
6
|
export declare const proposalSource: (id: string) => string;
|
|
6
7
|
/**
|
|
7
8
|
* Approving a proposal makes it the owner's task in the account that would act; nothing is carried out.
|
|
8
9
|
* The task comes first, so an approval that failed half-way finds the same task when it is run again.
|
|
9
10
|
*/
|
|
10
|
-
export declare const approveAsTask: (store: ProposalTaskStore, id: string) => Promise<{
|
|
11
|
+
export declare const approveAsTask: (store: ProposalTaskStore, id: string, by: Decider) => Promise<{
|
|
11
12
|
proposal: ProposedAction;
|
|
12
13
|
task: Task;
|
|
13
14
|
}>;
|
|
@@ -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 } from "../store/decisions.js";
|
|
3
4
|
import { EVIDENCE_FINDINGS_KIND } from "../store/evidence-findings.js";
|
|
5
|
+
import { proposerOfAction } from "../store/index.js";
|
|
4
6
|
import { createTaskService } from "../tasks/index.js";
|
|
5
7
|
import { taskAccount } from "./task-rules.js";
|
|
6
8
|
/** The source a proposal's task keeps: found again on a retry, so one proposal never makes two tasks. */
|
|
@@ -9,7 +11,7 @@ export const proposalSource = (id) => `proposed-action:${id}`;
|
|
|
9
11
|
* Approving a proposal makes it the owner's task in the account that would act; nothing is carried out.
|
|
10
12
|
* The task comes first, so an approval that failed half-way finds the same task when it is run again.
|
|
11
13
|
*/
|
|
12
|
-
export const approveAsTask = async (store, id) => {
|
|
14
|
+
export const approveAsTask = async (store, id, by) => {
|
|
13
15
|
const proposal = await store.proposedActions.get(id);
|
|
14
16
|
if (proposal.kind === MEETING_FINDINGS_KIND)
|
|
15
17
|
throw new CliError("validation_error", "review meeting findings through the dedicated findings command", {
|
|
@@ -23,6 +25,7 @@ export const approveAsTask = async (store, id) => {
|
|
|
23
25
|
throw new CliError("validation_error", `a ${proposal.status} action cannot be approved`, {
|
|
24
26
|
reason: "proposal.wrong-state",
|
|
25
27
|
});
|
|
28
|
+
assertMayDecide({ id: proposal.id, proposedBy: proposerOfAction(proposal) }, by, store.decisionPolicy);
|
|
26
29
|
if (proposal.account === null)
|
|
27
30
|
throw new CliError("validation_error", "a proposal names the account that would act; this one names none");
|
|
28
31
|
const { task } = await createTaskService({ store: store.tasks }).add({
|
|
@@ -33,5 +36,5 @@ export const approveAsTask = async (store, id) => {
|
|
|
33
36
|
kind: "request",
|
|
34
37
|
origin: "agent",
|
|
35
38
|
});
|
|
36
|
-
return { proposal: await store.proposedActions.approve(proposal.id, { task: task.id }), task };
|
|
39
|
+
return { proposal: await store.proposedActions.approve(proposal.id, by, { task: task.id }), task };
|
|
37
40
|
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { CliError } from "@wirecat/cli-base";
|
|
2
|
+
const MAX_IDS = 500;
|
|
3
|
+
const refuse = (message) => {
|
|
4
|
+
throw new CliError("validation_error", message, { reason: "proposal.ids" });
|
|
5
|
+
};
|
|
6
|
+
const range = (prefix, from, to) => {
|
|
7
|
+
const [start, end] = [Number(from), Number(to)];
|
|
8
|
+
if (start > end)
|
|
9
|
+
return refuse(`a range runs upwards: ${from}-${to}`);
|
|
10
|
+
if (end - start + 1 > MAX_IDS)
|
|
11
|
+
return refuse(`a range takes at most ${MAX_IDS} ids`);
|
|
12
|
+
return Array.from({ length: end - start + 1 }, (_, n) => `${prefix}${start + n}`);
|
|
13
|
+
};
|
|
14
|
+
const one = (token) => {
|
|
15
|
+
const bare = /^([1-9]\d*)(?:-([1-9]\d*))?$/.exec(token);
|
|
16
|
+
if (bare?.[1])
|
|
17
|
+
return bare[2] ? range("", bare[1], bare[2]) : [bare[1]];
|
|
18
|
+
const part = /^([1-9]\d*)\/([A-Za-z0-9_-]{1,64})$/.exec(token);
|
|
19
|
+
if (part)
|
|
20
|
+
return [token];
|
|
21
|
+
const prefixed = /^([a-z][a-z_]*):([1-9]\d*)(?:-(?:([a-z][a-z_]*):)?([1-9]\d*))?$/.exec(token);
|
|
22
|
+
if (prefixed?.[1] && prefixed[2]) {
|
|
23
|
+
const [, prefix, from, other, to] = prefixed;
|
|
24
|
+
if (other !== undefined && other !== prefix)
|
|
25
|
+
return refuse(`a range stays inside one prefix: ${token}`);
|
|
26
|
+
return to ? range(`${prefix}:`, from, to) : [`${prefix}:${from}`];
|
|
27
|
+
}
|
|
28
|
+
if (/^[1-9]\d*-[a-z]/.test(token))
|
|
29
|
+
return refuse(`a range stays inside one prefix: ${token}`);
|
|
30
|
+
return refuse(`not a proposal id: ${token}; use 17070, 17070-17079, fact:512 or 17080/f2`);
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Proposal ids from the command line, before any reference parsing: `17070-17079,fact:500-502,17080/f1` becomes
|
|
34
|
+
* each id once, in the order given.
|
|
35
|
+
*/
|
|
36
|
+
export const parseProposalIds = (text) => {
|
|
37
|
+
const ids = [...new Set(text.flatMap((arg) => arg.split(",")).flatMap((token) => one(token.trim())))];
|
|
38
|
+
if (ids.length === 0)
|
|
39
|
+
return refuse("name at least one proposal id");
|
|
40
|
+
if (ids.length > MAX_IDS)
|
|
41
|
+
return refuse(`decide at most ${MAX_IDS} proposals at once`);
|
|
42
|
+
return ids;
|
|
43
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type MessageStore } from "../../../store/index.js";
|
|
2
|
+
import type { ProposalKind } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Every stored proposal no other kind claims: a reply, a ban, a task to create. Confirming makes it the owner's task
|
|
5
|
+
* in the account that would act; nothing is carried out. Meeting findings are reviewed with their meeting.
|
|
6
|
+
*/
|
|
7
|
+
export declare const actionKind: (store: Pick<MessageStore, "proposedActions" | "tasks" | "decisionPolicy">, claimed: () => readonly string[]) => ProposalKind;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { MEETING_FINDINGS_KIND } from "../../../meetings/index.js";
|
|
2
|
+
import { proposerOfAction } from "../../../store/index.js";
|
|
3
|
+
import { approveAsTask } from "../../proposal-tasks.js";
|
|
4
|
+
import { actionStatus, kept, projectOf, pushed, storedStatus } from "./shared.js";
|
|
5
|
+
const rowOf = (action) => ({
|
|
6
|
+
id: action.id,
|
|
7
|
+
kind: "action",
|
|
8
|
+
status: actionStatus(action.status),
|
|
9
|
+
project: projectOf(action.target),
|
|
10
|
+
subject: action.target ?? `action:${action.id}`,
|
|
11
|
+
object: null,
|
|
12
|
+
summary: action.kind,
|
|
13
|
+
proposedBy: proposerOfAction(action),
|
|
14
|
+
reason: action.reason,
|
|
15
|
+
evidence: [],
|
|
16
|
+
score: null,
|
|
17
|
+
signals: null,
|
|
18
|
+
parts: null,
|
|
19
|
+
createdAt: action.createdAt,
|
|
20
|
+
});
|
|
21
|
+
/**
|
|
22
|
+
* Every stored proposal no other kind claims: a reply, a ban, a task to create. Confirming makes it the owner's task
|
|
23
|
+
* in the account that would act; nothing is carried out. Meeting findings are reviewed with their meeting.
|
|
24
|
+
*/
|
|
25
|
+
export const actionKind = (store, claimed) => ({
|
|
26
|
+
kind: "action",
|
|
27
|
+
list: async (filter) => {
|
|
28
|
+
const status = storedStatus(filter);
|
|
29
|
+
const actions = await store.proposedActions.list({
|
|
30
|
+
except: [...claimed(), MEETING_FINDINGS_KIND],
|
|
31
|
+
...(status === undefined ? {} : { status }),
|
|
32
|
+
...pushed(filter),
|
|
33
|
+
});
|
|
34
|
+
return kept(actions.map(rowOf), filter);
|
|
35
|
+
},
|
|
36
|
+
get: async (id) => rowOf(await store.proposedActions.get(id)),
|
|
37
|
+
decide: async (id, { confirm, by, reason }) => {
|
|
38
|
+
if (!confirm) {
|
|
39
|
+
await store.proposedActions.reject(id, by, reason);
|
|
40
|
+
return { id, kind: "action", status: "rejected", made: [] };
|
|
41
|
+
}
|
|
42
|
+
const { task } = await approveAsTask(store, id, by);
|
|
43
|
+
return { id, kind: "action", status: "confirmed", made: [`task:${task.key ?? task.id}`] };
|
|
44
|
+
},
|
|
45
|
+
});
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { MessageStore } from "../../../store/index.js";
|
|
2
|
+
import type { ProposalKind } from "../types.js";
|
|
3
|
+
/** Facts keep their own table and lifecycle; an agent's waits as `proposed`. */
|
|
4
|
+
export declare const factKind: (store: Pick<MessageStore, "facts">) => ProposalKind;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { kept, proposerOfAuthor, pushed } from "./shared.js";
|
|
2
|
+
const STATUS = {
|
|
3
|
+
proposed: "proposed",
|
|
4
|
+
accepted: "confirmed",
|
|
5
|
+
superseded: "confirmed",
|
|
6
|
+
reversed: "confirmed",
|
|
7
|
+
rejected: "rejected",
|
|
8
|
+
};
|
|
9
|
+
const rowOf = (fact) => ({
|
|
10
|
+
id: fact.ref,
|
|
11
|
+
kind: "fact",
|
|
12
|
+
status: STATUS[fact.status],
|
|
13
|
+
project: fact.project,
|
|
14
|
+
subject: fact.subject ?? fact.ref,
|
|
15
|
+
object: null,
|
|
16
|
+
summary: fact.statement,
|
|
17
|
+
proposedBy: proposerOfAuthor(fact.author),
|
|
18
|
+
reason: null,
|
|
19
|
+
evidence: fact.evidence.slice(0, 5),
|
|
20
|
+
score: null,
|
|
21
|
+
signals: null,
|
|
22
|
+
parts: null,
|
|
23
|
+
createdAt: fact.createdAt,
|
|
24
|
+
});
|
|
25
|
+
/** Facts keep their own table and lifecycle; an agent's waits as `proposed`. */
|
|
26
|
+
export const factKind = (store) => ({
|
|
27
|
+
kind: "fact",
|
|
28
|
+
prefix: "fact",
|
|
29
|
+
list: async (filter) => {
|
|
30
|
+
const status = filter.status ?? "proposed";
|
|
31
|
+
const facts = await store.facts.list({
|
|
32
|
+
...pushed(filter),
|
|
33
|
+
...(status === "all" ? {} : { status: status === "confirmed" ? "accepted" : status }),
|
|
34
|
+
order: "created",
|
|
35
|
+
});
|
|
36
|
+
return kept(facts.map(rowOf), filter);
|
|
37
|
+
},
|
|
38
|
+
get: async (id) => rowOf(await store.facts.get(id)),
|
|
39
|
+
decide: async (id, { confirm, by, reason }) => {
|
|
40
|
+
const fact = confirm ? await store.facts.accept(id, by, reason) : await store.facts.reject(id, by, reason);
|
|
41
|
+
return { id, kind: "fact", status: confirm ? "confirmed" : "rejected", made: confirm ? [fact.ref] : [] };
|
|
42
|
+
},
|
|
43
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type MessageStore } from "../../../store/index.js";
|
|
2
|
+
import type { ProposalKind } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* An agent's batch of cited facts and tasks from stored messages, mail and documents. Confirming accepts the parts
|
|
5
|
+
* named, or every finding not rejected before; the rest are rejected. Meeting findings stay with their meeting.
|
|
6
|
+
*/
|
|
7
|
+
export declare const findingsKind: (store: Pick<MessageStore, "evidenceFindings" | "proposedActions">) => ProposalKind;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { EVIDENCE_FINDINGS_KIND } from "../../../store/index.js";
|
|
2
|
+
import { kept, proposerOfAuthor } from "./shared.js";
|
|
3
|
+
const statusOf = (batch) => batch.status === "proposed" ? "proposed" : batch.receipt?.accepted.length ? "confirmed" : "rejected";
|
|
4
|
+
const rowOf = (batch, createdAt) => {
|
|
5
|
+
const findings = batch.input.findings;
|
|
6
|
+
const outcome = (id) => batch.receipt?.items.find((item) => item.id === id)?.status ?? null;
|
|
7
|
+
return {
|
|
8
|
+
id: batch.id,
|
|
9
|
+
kind: "findings",
|
|
10
|
+
status: statusOf(batch),
|
|
11
|
+
project: batch.project,
|
|
12
|
+
subject: batch.subject ?? batch.project ?? `findings:${batch.id}`,
|
|
13
|
+
object: null,
|
|
14
|
+
summary: `${findings.length} finding${findings.length === 1 ? "" : "s"}`,
|
|
15
|
+
proposedBy: proposerOfAuthor(batch.author),
|
|
16
|
+
reason: null,
|
|
17
|
+
evidence: [...new Set(findings.flatMap((finding) => finding.evidence.map((one) => one.reference)))].slice(0, 5),
|
|
18
|
+
score: null,
|
|
19
|
+
signals: null,
|
|
20
|
+
parts: findings.map((finding) => ({
|
|
21
|
+
id: finding.id,
|
|
22
|
+
kind: finding.kind,
|
|
23
|
+
summary: finding.kind === "fact" ? finding.statement : finding.title,
|
|
24
|
+
outcome: outcome(finding.id),
|
|
25
|
+
})),
|
|
26
|
+
createdAt,
|
|
27
|
+
};
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* An agent's batch of cited facts and tasks from stored messages, mail and documents. Confirming accepts the parts
|
|
31
|
+
* named, or every finding not rejected before; the rest are rejected. Meeting findings stay with their meeting.
|
|
32
|
+
*/
|
|
33
|
+
export const findingsKind = (store) => {
|
|
34
|
+
// The batch view carries no staging time; its stored proposal does.
|
|
35
|
+
const created = async (batch) => rowOf(batch, (await store.proposedActions.get(batch.id)).createdAt);
|
|
36
|
+
return {
|
|
37
|
+
kind: "findings",
|
|
38
|
+
stored: [EVIDENCE_FINDINGS_KIND],
|
|
39
|
+
parts: true,
|
|
40
|
+
list: async (filter) => {
|
|
41
|
+
const status = filter.status ?? "proposed";
|
|
42
|
+
const rows = [];
|
|
43
|
+
// The findings list filters by project and status only; a proposer filter is applied page by page until full.
|
|
44
|
+
for (let offset = 0; rows.length < filter.limit; offset += 50) {
|
|
45
|
+
const page = await store.evidenceFindings.list({
|
|
46
|
+
...(filter.project === undefined ? {} : { project: filter.project }),
|
|
47
|
+
...(status === "proposed" ? { status: "proposed" } : {}),
|
|
48
|
+
...(status === "confirmed" || status === "rejected" ? { status: "reviewed" } : {}),
|
|
49
|
+
limit: 50,
|
|
50
|
+
offset,
|
|
51
|
+
});
|
|
52
|
+
rows.push(...kept(await Promise.all(page.items.map(created)), { ...filter, limit: 50 }));
|
|
53
|
+
if (!page.hasMore)
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
return kept(rows, filter);
|
|
57
|
+
},
|
|
58
|
+
get: async (id) => created(await store.evidenceFindings.get(id)),
|
|
59
|
+
decide: async (id, { confirm, by, parts, reason }) => {
|
|
60
|
+
const batch = await store.evidenceFindings.get(id);
|
|
61
|
+
const turnedDown = new Set(batch.previous.filter((one) => one.status === "rejected").map((one) => one.id));
|
|
62
|
+
const accept = !confirm
|
|
63
|
+
? []
|
|
64
|
+
: (parts ?? batch.input.findings.map((finding) => finding.id).filter((one) => !turnedDown.has(one)));
|
|
65
|
+
const reviewed = await store.evidenceFindings.review(id, {
|
|
66
|
+
fingerprint: batch.fingerprint,
|
|
67
|
+
accept,
|
|
68
|
+
by,
|
|
69
|
+
...(reason === undefined ? {} : { reason }),
|
|
70
|
+
});
|
|
71
|
+
const made = (reviewed.receipt?.items ?? []).flatMap((item) => item.status === "accepted" && item.reference && !item.reused ? [item.reference] : []);
|
|
72
|
+
return { id, kind: "findings", status: accept.length ? "confirmed" : "rejected", made };
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { MessageStore } from "../../../store/index.js";
|
|
2
|
+
import type { ProposalKind } from "../types.js";
|
|
3
|
+
type KeyDocumentStore = Pick<MessageStore, "knowledge" | "notes" | "proposedActions">;
|
|
4
|
+
/**
|
|
5
|
+
* A note, document or message proposed as one of a project's key documents. Stored `key_document` rows are the new
|
|
6
|
+
* home; older suggestions are unconfirmed `key` links, shown as `key:<id>`.
|
|
7
|
+
*/
|
|
8
|
+
export declare const keyDocumentKind: (store: KeyDocumentStore) => ProposalKind;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { CliError } from "@wirecat/cli-base";
|
|
2
|
+
import { proposerOfAction } from "../../../store/index.js";
|
|
3
|
+
import { actionStatus, kept, proposedLink, pushed, storedStatus } from "./shared.js";
|
|
4
|
+
const linkRow = (link) => ({
|
|
5
|
+
id: `key:${link.id}`,
|
|
6
|
+
kind: "key_document",
|
|
7
|
+
status: "proposed",
|
|
8
|
+
project: link.to,
|
|
9
|
+
subject: link.from,
|
|
10
|
+
object: link.to,
|
|
11
|
+
summary: null,
|
|
12
|
+
proposedBy: { type: "unknown" },
|
|
13
|
+
reason: link.evidence,
|
|
14
|
+
evidence: [link.from],
|
|
15
|
+
score: null,
|
|
16
|
+
signals: null,
|
|
17
|
+
parts: null,
|
|
18
|
+
createdAt: link.createdAt,
|
|
19
|
+
});
|
|
20
|
+
const storedRow = (action) => {
|
|
21
|
+
const payload = action.payload;
|
|
22
|
+
return {
|
|
23
|
+
id: action.id,
|
|
24
|
+
kind: "key_document",
|
|
25
|
+
status: actionStatus(action.status),
|
|
26
|
+
project: payload.project,
|
|
27
|
+
subject: payload.ref,
|
|
28
|
+
object: payload.project,
|
|
29
|
+
summary: null,
|
|
30
|
+
proposedBy: proposerOfAction(action),
|
|
31
|
+
reason: action.reason ?? payload.reason ?? null,
|
|
32
|
+
evidence: [payload.ref],
|
|
33
|
+
score: null,
|
|
34
|
+
signals: null,
|
|
35
|
+
parts: null,
|
|
36
|
+
createdAt: action.createdAt,
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* A note, document or message proposed as one of a project's key documents. Stored `key_document` rows are the new
|
|
41
|
+
* home; older suggestions are unconfirmed `key` links, shown as `key:<id>`.
|
|
42
|
+
*/
|
|
43
|
+
export const keyDocumentKind = (store) => {
|
|
44
|
+
const legacy = async () => (await store.notes.links({ kind: "related-to", role: "key", confirmed: false })).filter((link) => link.to?.startsWith("project:") && proposedLink(link.provenance));
|
|
45
|
+
const legacyLink = async (id) => {
|
|
46
|
+
const link = (await legacy()).find((one) => `key:${one.id}` === id);
|
|
47
|
+
if (!link)
|
|
48
|
+
throw new CliError("not_found", `no suggested key document ${id}`);
|
|
49
|
+
return link;
|
|
50
|
+
};
|
|
51
|
+
return {
|
|
52
|
+
kind: "key_document",
|
|
53
|
+
prefix: "key",
|
|
54
|
+
stored: ["key_document"],
|
|
55
|
+
list: async (filter) => {
|
|
56
|
+
const status = storedStatus(filter);
|
|
57
|
+
const stored = await store.proposedActions.list({
|
|
58
|
+
kinds: ["key_document"],
|
|
59
|
+
...(status === undefined ? {} : { status }),
|
|
60
|
+
...pushed(filter),
|
|
61
|
+
});
|
|
62
|
+
const links = (filter.status ?? "proposed") === "rejected" ? [] : await legacy();
|
|
63
|
+
return kept([...stored.map(storedRow), ...links.map(linkRow)], filter);
|
|
64
|
+
},
|
|
65
|
+
get: async (id) => id.startsWith("key:") ? linkRow(await legacyLink(id)) : storedRow(await store.proposedActions.get(id)),
|
|
66
|
+
decide: async (id, { confirm, by, reason }) => {
|
|
67
|
+
const status = confirm ? "confirmed" : "rejected";
|
|
68
|
+
const decision = { confirm, by, ...(reason === undefined ? {} : { reason }) };
|
|
69
|
+
const link = id.startsWith("key:")
|
|
70
|
+
? await store.knowledge.decideSuggestedLink(id.slice("key:".length), decision)
|
|
71
|
+
: (await store.knowledge.decideLinkProposal(id, decision)).link;
|
|
72
|
+
return { id, kind: "key_document", status, made: link ? [`link:${link.id}`] : [] };
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { kept, proposerOfAuthor, pushed } from "./shared.js";
|
|
2
|
+
/** A stale memory is the one a person turned down; a superseded one held until another replaced it. */
|
|
3
|
+
const STATUS = {
|
|
4
|
+
proposed: "proposed",
|
|
5
|
+
confirmed: "confirmed",
|
|
6
|
+
superseded: "confirmed",
|
|
7
|
+
stale: "rejected",
|
|
8
|
+
};
|
|
9
|
+
const rowOf = (memory) => ({
|
|
10
|
+
id: memory.ref,
|
|
11
|
+
kind: "memory",
|
|
12
|
+
status: STATUS[memory.status],
|
|
13
|
+
project: memory.project,
|
|
14
|
+
subject: memory.subject ?? memory.ref,
|
|
15
|
+
object: null,
|
|
16
|
+
summary: null,
|
|
17
|
+
proposedBy: proposerOfAuthor(memory.author),
|
|
18
|
+
reason: null,
|
|
19
|
+
evidence: memory.evidence.slice(0, 5),
|
|
20
|
+
score: null,
|
|
21
|
+
signals: null,
|
|
22
|
+
parts: null,
|
|
23
|
+
createdAt: memory.createdAt,
|
|
24
|
+
});
|
|
25
|
+
/** Memories keep their own table; rejecting one marks it stale. */
|
|
26
|
+
export const memoryKind = (store) => ({
|
|
27
|
+
kind: "memory",
|
|
28
|
+
prefix: "memory",
|
|
29
|
+
list: async (filter) => {
|
|
30
|
+
const status = filter.status ?? "proposed";
|
|
31
|
+
const memories = await store.memories.list({
|
|
32
|
+
...pushed(filter),
|
|
33
|
+
...(status === "all" ? {} : { status: status === "rejected" ? "stale" : status }),
|
|
34
|
+
});
|
|
35
|
+
return kept(memories.map(rowOf), filter);
|
|
36
|
+
},
|
|
37
|
+
get: async (id) => rowOf(await store.memories.get(id)),
|
|
38
|
+
decide: async (id, { confirm, by }) => {
|
|
39
|
+
const memory = confirm ? await store.memories.confirm(id, by) : await store.memories.markStale(id, by);
|
|
40
|
+
return { id, kind: "memory", status: confirm ? "confirmed" : "rejected", made: confirm ? [memory.ref] : [] };
|
|
41
|
+
},
|
|
42
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { MessageStore } from "../../../store/index.js";
|
|
2
|
+
import type { ProposalKind } from "../types.js";
|
|
3
|
+
type RelationshipStore = Pick<MessageStore, "knowledge" | "notes" | "proposedActions">;
|
|
4
|
+
/**
|
|
5
|
+
* A person, organization, chat or mail thread proposed as related to another thing. New proposals are stored
|
|
6
|
+
* `relationship` rows; suggestions written before them are unconfirmed links, shown as `link:<id>` until the store
|
|
7
|
+
* moves them.
|
|
8
|
+
*/
|
|
9
|
+
export declare const relationshipKind: (store: RelationshipStore) => ProposalKind;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { CliError } from "@wirecat/cli-base";
|
|
2
|
+
import { proposerOfAction } from "../../../store/index.js";
|
|
3
|
+
import { actionStatus, kept, projectOf, proposedLink, pushed, storedStatus } from "./shared.js";
|
|
4
|
+
const linkRow = (link) => ({
|
|
5
|
+
id: `link:${link.id}`,
|
|
6
|
+
kind: "relationship",
|
|
7
|
+
status: "proposed",
|
|
8
|
+
project: projectOf(link.to, link.from),
|
|
9
|
+
subject: link.from,
|
|
10
|
+
object: link.to,
|
|
11
|
+
summary: link.role,
|
|
12
|
+
proposedBy: { type: "unknown" },
|
|
13
|
+
reason: link.evidence,
|
|
14
|
+
evidence: [],
|
|
15
|
+
score: null,
|
|
16
|
+
signals: null,
|
|
17
|
+
parts: null,
|
|
18
|
+
createdAt: link.createdAt,
|
|
19
|
+
});
|
|
20
|
+
const storedRow = (action) => {
|
|
21
|
+
const payload = action.payload;
|
|
22
|
+
return {
|
|
23
|
+
id: action.id,
|
|
24
|
+
kind: "relationship",
|
|
25
|
+
status: actionStatus(action.status),
|
|
26
|
+
project: projectOf(payload.to, payload.from),
|
|
27
|
+
subject: payload.from,
|
|
28
|
+
object: payload.to,
|
|
29
|
+
summary: payload.role ?? null,
|
|
30
|
+
proposedBy: proposerOfAction(action),
|
|
31
|
+
reason: action.reason,
|
|
32
|
+
evidence: (payload.evidence ?? []).slice(0, 5),
|
|
33
|
+
score: payload.score ?? null,
|
|
34
|
+
signals: payload.signals ?? null,
|
|
35
|
+
parts: null,
|
|
36
|
+
createdAt: action.createdAt,
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* A person, organization, chat or mail thread proposed as related to another thing. New proposals are stored
|
|
41
|
+
* `relationship` rows; suggestions written before them are unconfirmed links, shown as `link:<id>` until the store
|
|
42
|
+
* moves them.
|
|
43
|
+
*/
|
|
44
|
+
export const relationshipKind = (store) => {
|
|
45
|
+
const legacy = async () => {
|
|
46
|
+
const [members, related] = await Promise.all([
|
|
47
|
+
store.notes.links({ kind: "member-of", confirmed: false }),
|
|
48
|
+
store.notes.links({ kind: "related-to", confirmed: false }),
|
|
49
|
+
]);
|
|
50
|
+
return [...members, ...related].filter((link) => link.to !== null && link.role !== "key" && proposedLink(link.provenance));
|
|
51
|
+
};
|
|
52
|
+
const legacyLink = async (id) => {
|
|
53
|
+
const link = (await legacy()).find((one) => `link:${one.id}` === id);
|
|
54
|
+
if (!link)
|
|
55
|
+
throw new CliError("not_found", `no proposed relationship ${id}`);
|
|
56
|
+
return link;
|
|
57
|
+
};
|
|
58
|
+
return {
|
|
59
|
+
kind: "relationship",
|
|
60
|
+
prefix: "link",
|
|
61
|
+
stored: ["relationship"],
|
|
62
|
+
list: async (filter) => {
|
|
63
|
+
const status = storedStatus(filter);
|
|
64
|
+
const stored = await store.proposedActions.list({
|
|
65
|
+
kinds: ["relationship"],
|
|
66
|
+
...(status === undefined ? {} : { status }),
|
|
67
|
+
...pushed(filter),
|
|
68
|
+
});
|
|
69
|
+
const links = (filter.status ?? "proposed") === "rejected" ? [] : await legacy();
|
|
70
|
+
return kept([...stored.map(storedRow), ...links.map(linkRow)], filter);
|
|
71
|
+
},
|
|
72
|
+
get: async (id) => id.startsWith("link:") ? linkRow(await legacyLink(id)) : storedRow(await store.proposedActions.get(id)),
|
|
73
|
+
decide: async (id, { confirm, by, reason }) => {
|
|
74
|
+
const status = confirm ? "confirmed" : "rejected";
|
|
75
|
+
const decision = { confirm, by, ...(reason === undefined ? {} : { reason }) };
|
|
76
|
+
const link = id.startsWith("link:")
|
|
77
|
+
? await store.knowledge.decideSuggestedLink(id.slice("link:".length), decision)
|
|
78
|
+
: (await store.knowledge.decideLinkProposal(id, decision)).link;
|
|
79
|
+
return { id, kind: "relationship", status, made: link ? [`relationship:${link.id}`] : [] };
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
};
|