@wirecat/cli-core 0.545.0 → 0.547.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 +228 -0
- package/dist/calendar/busy.js +4 -1
- package/dist/calendar/cli/answers.d.ts +31 -0
- package/dist/calendar/cli/answers.js +8 -0
- package/dist/calendar/cli/index.d.ts +6 -0
- package/dist/calendar/cli/index.js +43 -4
- package/dist/calendar/cli/remind.js +2 -1
- package/dist/calendar/expand.d.ts +17 -0
- package/dist/calendar/expand.js +340 -0
- package/dist/calendar/feeds.d.ts +96 -0
- package/dist/calendar/feeds.js +324 -0
- package/dist/calendar/ics.d.ts +15 -4
- package/dist/calendar/ics.js +18 -7
- package/dist/calendar/index.d.ts +3 -1
- package/dist/calendar/index.js +2 -0
- package/dist/calendar/server-search.js +3 -1
- package/dist/calendar/writes.js +15 -4
- package/dist/cli/after-dashes.d.ts +16 -0
- package/dist/cli/after-dashes.js +41 -0
- package/dist/cli/bot/context.d.ts +1 -1
- package/dist/cli/bot/context.js +9 -1
- package/dist/cli/bot/messages.js +2 -0
- package/dist/cli/bot/setup.js +1 -0
- package/dist/cli/contract-templates.js +2 -2
- package/dist/cli/entry.d.ts +2 -0
- package/dist/cli/entry.js +3 -2
- package/dist/cli/index.d.ts +2 -1
- package/dist/cli/index.js +2 -1
- package/dist/cli/messenger/admin-commands.js +5 -1
- package/dist/cli/messenger/admin-contacts-command.js +5 -1
- package/dist/cli/messenger/admin-folders-command.js +5 -1
- package/dist/cli/messenger/complete-command.js +1 -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 +12 -23
- package/dist/cli/messenger/messages-comments-command.js +4 -2
- package/dist/cli/messenger/messages-edit-command.js +1 -0
- package/dist/cli/messenger/messages-press-command.js +1 -0
- package/dist/cli/messenger/messages-send-command.js +1 -0
- package/dist/cli/messenger/observed.js +2 -2
- package/dist/cli/messenger/polls-command.js +1 -0
- package/dist/cli/messenger/private-people-command.js +5 -1
- package/dist/cli/messenger/store-maintenance-command.d.ts +1 -1
- package/dist/cli/messenger/stored.js +56 -3
- package/dist/cli/messenger/topics-command.js +1 -1
- package/dist/cli/option-hints.d.ts +6 -0
- package/dist/cli/option-hints.js +15 -0
- package/dist/cli/program.js +17 -6
- package/dist/cli/schedule/schedule-command.js +1 -0
- package/dist/domain/message-link.d.ts +3 -0
- package/dist/domain/message-link.js +9 -14
- package/dist/domain/money-words.d.ts +11 -0
- package/dist/domain/money-words.js +38 -0
- package/dist/domain/providers.d.ts +2 -0
- package/dist/domain/providers.js +15 -3
- package/dist/domain/ref-arguments.d.ts +31 -0
- package/dist/domain/ref-arguments.js +79 -0
- package/dist/domain/references.d.ts +20 -2
- package/dist/domain/references.js +132 -47
- package/dist/errors/reasons.js +31 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -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/mcp/index.d.ts +15 -15
- package/dist/oauth/client-file.d.ts +14 -0
- package/dist/oauth/client-file.js +28 -0
- package/dist/oauth/index.d.ts +1 -0
- package/dist/oauth/index.js +1 -0
- package/dist/ports/messenger.d.ts +3 -2
- package/dist/resolve.d.ts +3 -1
- package/dist/resolve.js +8 -0
- package/dist/search/task-signature.d.ts +5 -1
- package/dist/search/task-signature.js +27 -9
- package/dist/services/agenda.d.ts +5 -0
- package/dist/services/agenda.js +3 -1
- 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 +6 -1
- package/dist/services/index.js +6 -1
- package/dist/services/message-resolution.d.ts +4 -1
- package/dist/services/message-resolution.js +19 -3
- package/dist/services/messages.d.ts +2 -0
- package/dist/services/messages.js +40 -36
- package/dist/services/money-words.d.ts +3 -0
- package/dist/services/money-words.js +5 -0
- 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/questions.d.ts +7 -2
- package/dist/services/questions.js +13 -4
- package/dist/services/server-search.d.ts +7 -4
- package/dist/services/server-search.js +5 -2
- package/dist/services/stored-project-gathering.d.ts +9 -0
- package/dist/services/stored-project-gathering.js +9 -4
- package/dist/services/task-categories.d.ts +2 -0
- package/dist/services/task-categories.js +19 -8
- 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/actors.d.ts +13 -2
- package/dist/store/sqlite/actors.js +24 -1
- 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/evidence-sources.js +2 -2
- 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/identities.js +1 -1
- package/dist/store/sqlite/knowledge.d.ts +43 -7
- package/dist/store/sqlite/knowledge.js +62 -17
- 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 +22 -6
- 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/sqlite/things.d.ts +0 -1
- package/dist/store/sqlite/things.js +25 -24
- package/dist/store/store.d.ts +3 -0
- package/dist/store/store.js +2 -0
- package/package.json +1 -1
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { MEMORY_KINDS, MEMORY_SCOPES, type MEMORY_STATUSES } from "../../domain/knowledge.js";
|
|
2
|
-
import { type
|
|
2
|
+
import { type Decider } from "../decisions.js";
|
|
3
|
+
import { type Actor, type ProposedByFilter } from "./actors.js";
|
|
3
4
|
import { type EvidenceSource } from "./evidence-sources.js";
|
|
4
5
|
import { type NoteIndexState } from "./note-index.js";
|
|
5
6
|
import type { StoreContext } from "./open.js";
|
|
6
|
-
/** Who wrote it: the owner, or an agent by its bot handle and the model it ran on. */
|
|
7
|
+
/** Who wrote it: the owner, or an agent (or a rule scorer) by its bot handle and the model it ran on. */
|
|
7
8
|
export type Author = "owner" | {
|
|
8
9
|
bot: string;
|
|
9
10
|
model?: string;
|
|
11
|
+
kind?: "agent" | "rule";
|
|
10
12
|
};
|
|
11
13
|
export interface Memory {
|
|
12
14
|
id: string;
|
|
@@ -22,11 +24,12 @@ export interface Memory {
|
|
|
22
24
|
/** The project it belongs to, as `project:<id>`. */
|
|
23
25
|
project: string | null;
|
|
24
26
|
projectName: string | null;
|
|
25
|
-
/** `name` is a bot's handle; `null` for a person. */
|
|
27
|
+
/** `name` is a bot's handle and `kind` whether it is an agent or a rule; both `null` for a person. */
|
|
26
28
|
author: {
|
|
27
29
|
type: Actor["type"];
|
|
28
30
|
id: string;
|
|
29
31
|
name: string | null;
|
|
32
|
+
kind: "agent" | "rule" | null;
|
|
30
33
|
};
|
|
31
34
|
model: string | null;
|
|
32
35
|
confidence: number | null;
|
|
@@ -63,21 +66,22 @@ export interface MemoriesStore {
|
|
|
63
66
|
kind?: Memory["kind"];
|
|
64
67
|
status?: Memory["status"];
|
|
65
68
|
scope?: Memory["scope"];
|
|
69
|
+
proposedBy?: ProposedByFilter;
|
|
66
70
|
limit?: number;
|
|
67
71
|
}): Promise<Memory[]>;
|
|
68
72
|
/** Moves it to another project, or to none with `null`. */
|
|
69
73
|
update(reference: string, change: {
|
|
70
74
|
project: string | null;
|
|
71
75
|
}): Promise<Memory>;
|
|
72
|
-
/** The owner stands behind it; its evidence was checked now. */
|
|
73
|
-
confirm(reference: string): Promise<Memory>;
|
|
74
|
-
/** Its evidence no longer holds. */
|
|
75
|
-
markStale(reference: string): Promise<Memory>;
|
|
76
|
+
/** The owner, or an agent for the owner, stands behind it; its evidence was checked now. */
|
|
77
|
+
confirm(reference: string, by: Decider): Promise<Memory>;
|
|
78
|
+
/** Its evidence no longer holds; for an agent's memory this is how it is turned down, so `by` must be allowed. */
|
|
79
|
+
markStale(reference: string, by: Decider): Promise<Memory>;
|
|
76
80
|
/**
|
|
77
81
|
* Erases the memory: its row, its text in the search and meaning indexes, and every link from or to it. A memory
|
|
78
82
|
* that replaced it no longer names it. A backup made before keeps it.
|
|
79
83
|
*/
|
|
80
|
-
remove(reference: string): Promise<{
|
|
84
|
+
remove(reference: string, by: Decider): Promise<{
|
|
81
85
|
ref: string;
|
|
82
86
|
removed: true;
|
|
83
87
|
links: number;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { CliError } from "@wirecat/cli-base";
|
|
2
2
|
import { MEMORY_KINDS, MEMORY_SCOPES } from "../../domain/knowledge.js";
|
|
3
|
+
import { assertMayDecide, assertMayWithdraw } from "../decisions.js";
|
|
3
4
|
import { normalize } from "../normalize.js";
|
|
4
|
-
import { botNamed, ownerPerson } from "./actors.js";
|
|
5
|
+
import { botNamed, ownerPerson, proposedBySql, proposerOf } from "./actors.js";
|
|
5
6
|
import { evidenceSourcesOf } from "./evidence-sources.js";
|
|
6
7
|
import { linkKind } from "./link-kinds.js";
|
|
7
8
|
import { CORPORA, corpusIndexState, drainCorpus } from "./note-index.js";
|
|
@@ -9,7 +10,7 @@ import { requiredThing } from "./notes.js";
|
|
|
9
10
|
import { stemmerCache } from "./stems.js";
|
|
10
11
|
import { referenceOfThing, storedThing } from "./things.js";
|
|
11
12
|
import { write } from "./transaction.js";
|
|
12
|
-
const NAMED = "SELECT m.*, b.name AS author_name FROM memories m LEFT JOIN bots b ON m.author_type = 'bot' AND b.id = m.author_id";
|
|
13
|
+
const NAMED = "SELECT m.*, b.name AS author_name, b.kind AS author_kind FROM memories m LEFT JOIN bots b ON m.author_type = 'bot' AND b.id = m.author_id";
|
|
13
14
|
const iso = (value) => new Date(Number(value)).toISOString();
|
|
14
15
|
const oneOf = (value, allowed, what) => {
|
|
15
16
|
if (typeof value !== "string" || !allowed.includes(value))
|
|
@@ -20,7 +21,7 @@ export const authorActor = (context, author) => {
|
|
|
20
21
|
if (author === "owner")
|
|
21
22
|
return { actor: ownerPerson(context.database), model: null };
|
|
22
23
|
return {
|
|
23
|
-
actor: botNamed(context.database, author.bot, context.now(), "agent"),
|
|
24
|
+
actor: botNamed(context.database, author.bot, context.now(), author.kind ?? "agent"),
|
|
24
25
|
model: author.model === undefined ? null : author.model.trim().slice(0, 200) || null,
|
|
25
26
|
};
|
|
26
27
|
};
|
|
@@ -28,6 +29,7 @@ export const authorOf = (row) => ({
|
|
|
28
29
|
type: row.author_type,
|
|
29
30
|
id: String(row.author_id),
|
|
30
31
|
name: row.author_name == null ? null : String(row.author_name),
|
|
32
|
+
kind: row.author_type !== "bot" ? null : row.author_kind === "rule" || row.author_name === "rule" ? "rule" : "agent",
|
|
31
33
|
});
|
|
32
34
|
/** Writes `evidence` links from a row to each reference, refusing one the store does not hold. */
|
|
33
35
|
export const linkEvidence = (context, from, references, author) => {
|
|
@@ -153,6 +155,10 @@ export const memoriesStoreOver = (context) => {
|
|
|
153
155
|
throw new CliError("not_found", `${reference} is a fact now: store.facts holds it`);
|
|
154
156
|
return row;
|
|
155
157
|
};
|
|
158
|
+
const decided = (reference, by) => {
|
|
159
|
+
const row = rowOf(reference);
|
|
160
|
+
assertMayDecide({ id: `memory:${row.id}`, proposedBy: proposerOf(database, row.author_type, row.author_id) }, by, context.decisions);
|
|
161
|
+
};
|
|
156
162
|
const setStatus = (reference, status, verified) => {
|
|
157
163
|
const row = rowOf(reference);
|
|
158
164
|
const at = now();
|
|
@@ -179,6 +185,10 @@ export const memoriesStoreOver = (context) => {
|
|
|
179
185
|
if (input.confidence !== undefined && !(input.confidence >= 0 && input.confidence <= 1))
|
|
180
186
|
throw new CliError("validation_error", "confidence takes 0–1");
|
|
181
187
|
const status = oneOf(input.status ?? "proposed", ["proposed", "confirmed"], "a new memory's status");
|
|
188
|
+
if (status === "confirmed" && input.author !== "owner")
|
|
189
|
+
throw new CliError("permission_error", "an agent's memory waits as proposed; only the owner confirms it", {
|
|
190
|
+
reason: "proposal.owner-only",
|
|
191
|
+
});
|
|
182
192
|
const id = write(database, () => {
|
|
183
193
|
const { actor, model } = authorActor(context, input.author);
|
|
184
194
|
const subject = input.subject === undefined ? undefined : requiredThing(database, input.subject, "the subject");
|
|
@@ -201,11 +211,12 @@ export const memoriesStoreOver = (context) => {
|
|
|
201
211
|
list: async (options = {}) => {
|
|
202
212
|
const subject = options.subject === undefined ? undefined : requiredThing(database, options.subject);
|
|
203
213
|
const project = options.project === undefined ? null : projectId(database, options.project);
|
|
214
|
+
const by = proposedBySql("m.author_type", "m.author_id", options.proposedBy);
|
|
204
215
|
return database
|
|
205
216
|
.prepare(`${NAMED} WHERE m.deleted_at IS NULL AND (? IS NULL OR (m.subject_type = ? AND m.subject_id = ?)) ` +
|
|
206
217
|
"AND (? IS NULL OR m.project_id = ?) AND (? IS NULL OR m.kind = ?) AND (? IS NULL OR m.status = ?) AND (? IS NULL OR m.scope = ?) " +
|
|
207
|
-
|
|
208
|
-
.all(subject?.type ?? null, subject?.type ?? null, subject?.id ?? null, project, project, options.kind ?? null, options.kind ?? null, options.status ?? null, options.status ?? null, options.scope ?? null, options.scope ?? null, Math.min(Math.max(options.limit ?? 100, 1), 500))
|
|
218
|
+
`AND ${by.sql} ORDER BY m.created_at DESC, m.id DESC LIMIT ?`)
|
|
219
|
+
.all(subject?.type ?? null, subject?.type ?? null, subject?.id ?? null, project, project, options.kind ?? null, options.kind ?? null, options.status ?? null, options.status ?? null, options.scope ?? null, options.scope ?? null, ...by.params, Math.min(Math.max(options.limit ?? 100, 1), 500))
|
|
209
220
|
.map(memoryOf);
|
|
210
221
|
},
|
|
211
222
|
update: async (reference, change) => write(database, () => {
|
|
@@ -214,10 +225,22 @@ export const memoriesStoreOver = (context) => {
|
|
|
214
225
|
database.prepare("UPDATE memories SET project_id = ?, updated_at = ? WHERE id = ?").run(project, now(), id);
|
|
215
226
|
return memoryOf(rowOf(`memory:${id}`));
|
|
216
227
|
}),
|
|
217
|
-
confirm: async (reference) =>
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
228
|
+
confirm: async (reference, by) => {
|
|
229
|
+
decided(reference, by);
|
|
230
|
+
return setStatus(reference, "confirmed", true);
|
|
231
|
+
},
|
|
232
|
+
markStale: async (reference, by) => {
|
|
233
|
+
decided(reference, by);
|
|
234
|
+
return setStatus(reference, "stale", false);
|
|
235
|
+
},
|
|
236
|
+
remove: async (reference, by) => {
|
|
237
|
+
const row = rowOf(reference);
|
|
238
|
+
assertMayWithdraw({
|
|
239
|
+
id: `memory:${row.id}`,
|
|
240
|
+
proposedBy: proposerOf(database, row.author_type, row.author_id),
|
|
241
|
+
proposed: row.status === "proposed",
|
|
242
|
+
}, by);
|
|
243
|
+
const id = Number(row.id);
|
|
221
244
|
const links = erase(database, () => {
|
|
222
245
|
keepHighestId(database, "memories", now());
|
|
223
246
|
database.prepare("UPDATE memories SET supersedes_id = NULL WHERE supersedes_id = ?").run(id);
|
|
@@ -238,7 +261,7 @@ export const memoriesStoreOver = (context) => {
|
|
|
238
261
|
throw new CliError("validation_error", "say what to find in the memories");
|
|
239
262
|
const match = `normalized_text : (${words.map((word) => `"${word.replaceAll('"', '""')}"*`).join(" AND ")})`;
|
|
240
263
|
return database
|
|
241
|
-
.prepare("SELECT m.*, b.name AS author_name FROM memory_words w JOIN memories m ON m.id = w.rowid " +
|
|
264
|
+
.prepare("SELECT m.*, b.name AS author_name, b.kind AS author_kind FROM memory_words w JOIN memories m ON m.id = w.rowid " +
|
|
242
265
|
"LEFT JOIN bots b ON m.author_type = 'bot' AND b.id = m.author_id WHERE memory_words MATCH ? " +
|
|
243
266
|
"AND m.deleted_at IS NULL AND (? IS NULL OR (m.subject_type = ? AND m.subject_id = ?)) " +
|
|
244
267
|
"AND (? IS NULL OR m.scope = ?) ORDER BY bm25(memory_words), m.id LIMIT ?")
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Section } from "../../readers/types.js";
|
|
2
|
+
import { type Decider } from "../decisions.js";
|
|
2
3
|
import type { CacheDatabase } from "../driver.js";
|
|
3
4
|
import { type DocumentMetadata } from "./document-metadata.js";
|
|
4
5
|
import { type ImportedDocuments } from "./imported-documents.js";
|
|
@@ -81,6 +82,8 @@ export interface LinkInput {
|
|
|
81
82
|
evidence?: string;
|
|
82
83
|
provenance?: string;
|
|
83
84
|
confirmed?: boolean;
|
|
85
|
+
/** Who writes it: a confirmed link needs the owner. */
|
|
86
|
+
by?: Decider;
|
|
84
87
|
}
|
|
85
88
|
export interface FileNoteInput {
|
|
86
89
|
folderId: string;
|
|
@@ -151,7 +154,8 @@ export interface NotesStore extends NoteSearch, ImportedDocuments {
|
|
|
151
154
|
}>;
|
|
152
155
|
addLink(input: LinkInput): Promise<Link>;
|
|
153
156
|
links(options?: LinkListFilter): Promise<Link[]>;
|
|
154
|
-
|
|
157
|
+
/** Only the owner removes a link: it records no actor, so who proposed it cannot be checked. */
|
|
158
|
+
removeLink(id: string, by: Decider): Promise<{
|
|
155
159
|
id: string;
|
|
156
160
|
removed: true;
|
|
157
161
|
}>;
|
|
@@ -4,6 +4,7 @@ import { googleDocumentLinks } from "../../domain/google-documents.js";
|
|
|
4
4
|
import { DOCUMENT_ACCOUNT_PROVIDERS, filesAccountSql, folderProviderOf, folderProviderSql, isDocumentAccountProvider, } from "../../domain/providers.js";
|
|
5
5
|
import { parseReference } from "../../domain/references.js";
|
|
6
6
|
import { normalizeTag } from "../../domain/tags.js";
|
|
7
|
+
import { assertMayWithdraw } from "../decisions.js";
|
|
7
8
|
import { fold } from "../normalize.js";
|
|
8
9
|
import { ulid } from "../ulid.js";
|
|
9
10
|
import { ownerPerson } from "./actors.js";
|
|
@@ -14,7 +15,7 @@ import { LINK_KINDS, LINK_ORIGINS, linkKind } from "./link-kinds.js";
|
|
|
14
15
|
import { reviewLink } from "./link-reviews.js";
|
|
15
16
|
import { holdingText } from "./note-index.js";
|
|
16
17
|
import { addTags, ensureTag, labelsOf } from "./tags.js";
|
|
17
|
-
import {
|
|
18
|
+
import { referenceOfThing, storedThing, thingOf } from "./things.js";
|
|
18
19
|
import { write } from "./transaction.js";
|
|
19
20
|
export const NOTE_FORMATS = ["obsidian", "markdown"];
|
|
20
21
|
export { LINK_ORIGINS } from "./link-kinds.js";
|
|
@@ -94,7 +95,7 @@ export const linkOf = (database, row) => {
|
|
|
94
95
|
};
|
|
95
96
|
/** The row a reference names, refused when the store holds none. */
|
|
96
97
|
export const requiredThing = (database, reference, what = "the target") => {
|
|
97
|
-
const thing = thingOf(database,
|
|
98
|
+
const thing = thingOf(database, reference);
|
|
98
99
|
if (!thing)
|
|
99
100
|
throw new CliError("not_found", `${what} ${reference} is not in the local store`);
|
|
100
101
|
return thing;
|
|
@@ -149,6 +150,20 @@ export const resolvePersonLinks = (database, names) => {
|
|
|
149
150
|
if (check.get(key))
|
|
150
151
|
resolveKey(database, key);
|
|
151
152
|
};
|
|
153
|
+
/** A confirmed link is the owner's statement, of any kind; an agent's link is written unconfirmed. */
|
|
154
|
+
const ownerLink = (input) => {
|
|
155
|
+
if (input.confirmed === false)
|
|
156
|
+
return input;
|
|
157
|
+
if (input.by === undefined)
|
|
158
|
+
throw new CliError("validation_error", "a confirmed link names who writes it: the owner", {
|
|
159
|
+
reason: "proposal.decider-required",
|
|
160
|
+
});
|
|
161
|
+
if (input.by !== "owner")
|
|
162
|
+
throw new CliError("permission_error", "an agent's link is written unconfirmed; only the owner confirms one", {
|
|
163
|
+
reason: "proposal.owner-only",
|
|
164
|
+
});
|
|
165
|
+
return input;
|
|
166
|
+
};
|
|
152
167
|
/** Writes one link; an identical resolved link is answered instead of doubled. */
|
|
153
168
|
export const insertLink = (context, input) => {
|
|
154
169
|
const { database, now } = context;
|
|
@@ -522,7 +537,7 @@ export const notesStoreOver = (context) => {
|
|
|
522
537
|
const { limit, offset } = bounded(options.limit, options.offset);
|
|
523
538
|
if (options.source !== undefined)
|
|
524
539
|
oneOf(options.source, ["file", "internal"], "a note's source");
|
|
525
|
-
const about = options.about === undefined ? undefined : thingOf(database,
|
|
540
|
+
const about = options.about === undefined ? undefined : thingOf(database, options.about);
|
|
526
541
|
if (options.about !== undefined && !about)
|
|
527
542
|
return { items: [], hasMore: false };
|
|
528
543
|
const folder = options.folderId === undefined ? null : Number(folderRow(options.folderId).id);
|
|
@@ -592,12 +607,12 @@ export const notesStoreOver = (context) => {
|
|
|
592
607
|
database.prepare("DELETE FROM notes WHERE id = ?").run(Number(bare));
|
|
593
608
|
return { id, removed: true };
|
|
594
609
|
}),
|
|
595
|
-
addLink: async (input) => write(database, () => insertLink(context, input)),
|
|
610
|
+
addLink: async (input) => write(database, () => insertLink(context, ownerLink(input))),
|
|
596
611
|
links: async (options = {}) => {
|
|
597
612
|
const pair = (reference) => {
|
|
598
613
|
if (reference === undefined)
|
|
599
614
|
return { known: true, type: null, id: null };
|
|
600
|
-
const thing = thingOf(database,
|
|
615
|
+
const thing = thingOf(database, reference);
|
|
601
616
|
return thing ? { known: true, type: thing.type, id: thing.id } : { known: false, type: null, id: null };
|
|
602
617
|
};
|
|
603
618
|
const from = pair(options.from);
|
|
@@ -617,7 +632,8 @@ export const notesStoreOver = (context) => {
|
|
|
617
632
|
.all(from.type, from.type, from.id, to.type, to.type, to.id, options.unresolved ? 1 : 0, ...filters.map(({ value }) => value))
|
|
618
633
|
.map((row) => linkOf(database, row));
|
|
619
634
|
},
|
|
620
|
-
removeLink: async (id) => write(database, () => {
|
|
635
|
+
removeLink: async (id, by) => write(database, () => {
|
|
636
|
+
assertMayWithdraw({ id: `link:${id}`, proposedBy: { type: "unknown" }, proposed: false }, by);
|
|
621
637
|
if (!/^\d+$/.test(id) || !database.prepare("DELETE FROM links WHERE id = ?").run(Number(id)).changes)
|
|
622
638
|
throw new CliError("not_found", `no link ${id} in the local store`);
|
|
623
639
|
return { id, removed: true };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type DecisionPolicy } from "../decisions.js";
|
|
1
2
|
import { type StoreContext } from "./open.js";
|
|
2
3
|
export interface StoreOptions {
|
|
3
4
|
/** Open an existing compatible store without maintenance; SQLite refuses writes. */
|
|
@@ -11,6 +12,8 @@ export interface StoreOptions {
|
|
|
11
12
|
version?: string;
|
|
12
13
|
/** Where the notice of an upgrade that locks other CLIs out goes; stderr, since stdout may carry JSON or MCP. */
|
|
13
14
|
warn?: (line: string) => void;
|
|
15
|
+
/** The host's decision policy (its `agent_decisions`); the default lets an agent decide with a reason. */
|
|
16
|
+
decisions?: DecisionPolicy;
|
|
14
17
|
}
|
|
15
18
|
export declare const BACKFILL_ON_OPEN = 5000;
|
|
16
|
-
export declare const openStoreContext: ({ path, env, now, command, version, warn, readOnly, }?: StoreOptions) => Promise<StoreContext>;
|
|
19
|
+
export declare const openStoreContext: ({ path, env, now, command, version, warn, readOnly, decisions, }?: StoreOptions) => Promise<StoreContext>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mkdirSync, statSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
3
|
import { CliError } from "@wirecat/cli-base";
|
|
4
|
+
import { DEFAULT_DECISION_POLICY } from "../decisions.js";
|
|
4
5
|
import { migrate } from "../migrations.js";
|
|
5
6
|
import { storeCapable } from "../open.js";
|
|
6
7
|
import { storePath } from "../path.js";
|
|
@@ -31,7 +32,7 @@ const createWritable = (file) => {
|
|
|
31
32
|
throw new CliError("permission_error", "the message store cannot be written from here; run where it is writable, or set MESSAGING_STORE", { reason: "store.not-writable" });
|
|
32
33
|
}
|
|
33
34
|
};
|
|
34
|
-
export const openStoreContext = async ({ path, env, now = Date.now, command, version, warn = (line) => process.stderr.write(`${line}\n`), readOnly = false, } = {}) => {
|
|
35
|
+
export const openStoreContext = async ({ path, env, now = Date.now, command, version, warn = (line) => process.stderr.write(`${line}\n`), readOnly = false, decisions = DEFAULT_DECISION_POLICY, } = {}) => {
|
|
35
36
|
if (typeof readOnly !== "boolean")
|
|
36
37
|
throw new CliError("validation_error", "readOnly must be a boolean");
|
|
37
38
|
await storeCapable();
|
|
@@ -68,7 +69,12 @@ export const openStoreContext = async ({ path, env, now = Date.now, command, ver
|
|
|
68
69
|
});
|
|
69
70
|
}
|
|
70
71
|
loadProviders(database);
|
|
71
|
-
return {
|
|
72
|
+
return {
|
|
73
|
+
...opened,
|
|
74
|
+
now,
|
|
75
|
+
sharedConfig: sharedConfigPath(file, env ?? process.env),
|
|
76
|
+
decisions,
|
|
77
|
+
};
|
|
72
78
|
}
|
|
73
79
|
catch (error) {
|
|
74
80
|
database.close();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DecisionPolicy } from "../decisions.js";
|
|
1
2
|
import { type CacheDatabase } from "../driver.js";
|
|
2
3
|
import type { SQLiteAsyncDatabase } from "./drizzle/core.js";
|
|
3
4
|
/** Drizzle over the store's connection. Synchronous inside: a transaction never awaits (plan D3). */
|
|
@@ -11,6 +12,8 @@ export interface StoreContext extends OpenedSqlite {
|
|
|
11
12
|
now: () => number;
|
|
12
13
|
/** The owner's shared config file, whose `task_categories` edits the store's list; none in a bare context. */
|
|
13
14
|
sharedConfig?: string;
|
|
15
|
+
/** Who may decide proposals, set once by the host at open; every decide method reads it. */
|
|
16
|
+
decisions?: DecisionPolicy;
|
|
14
17
|
}
|
|
15
18
|
/**
|
|
16
19
|
* One SQLite connection, seen two ways: the `CacheDatabase` seam the hand-written SQL and the
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { type Decider, type Proposer } from "../decisions.js";
|
|
1
2
|
import type { AccountKey } from "../types.js";
|
|
2
|
-
import type
|
|
3
|
+
import { type Actor, type ProposedByFilter } from "./actors.js";
|
|
3
4
|
import { type Author } from "./memories.js";
|
|
4
5
|
import type { StoreContext } from "./open.js";
|
|
5
6
|
export declare const PROPOSAL_STATUSES: readonly ["proposed", "approved", "rejected", "executed", "failed"];
|
|
@@ -18,11 +19,12 @@ export interface ProposedAction {
|
|
|
18
19
|
payload: unknown;
|
|
19
20
|
reason: string | null;
|
|
20
21
|
status: (typeof PROPOSAL_STATUSES)[number];
|
|
21
|
-
/** `name` is a bot's handle; `null` for a person. */
|
|
22
|
+
/** `name` is a bot's handle and `kind` whether it is an agent or a rule; both `null` for a person. */
|
|
22
23
|
proposedBy: {
|
|
23
24
|
type: Actor["type"];
|
|
24
25
|
id: string;
|
|
25
26
|
name: string | null;
|
|
27
|
+
kind: "agent" | "rule" | null;
|
|
26
28
|
};
|
|
27
29
|
decidedBy: {
|
|
28
30
|
type: Actor["type"];
|
|
@@ -50,14 +52,20 @@ export interface ProposedActionsStore {
|
|
|
50
52
|
pending(options?: {
|
|
51
53
|
limit?: number;
|
|
52
54
|
}): Promise<ProposedAction[]>;
|
|
53
|
-
/** Newest first; every status unless one is named. */
|
|
55
|
+
/** Newest first; every status unless one is named, every kind unless `kinds` names some or `except` leaves some out. */
|
|
54
56
|
list(options?: {
|
|
55
57
|
status?: ProposedAction["status"];
|
|
56
58
|
limit?: number;
|
|
59
|
+
kinds?: readonly string[];
|
|
60
|
+
except?: readonly string[];
|
|
61
|
+
proposedBy?: ProposedByFilter;
|
|
62
|
+
/** A `project:<id>` the action targets or its payload names as `to`, `from` or `project`. */
|
|
63
|
+
project?: string;
|
|
57
64
|
}): Promise<ProposedAction[]>;
|
|
58
|
-
/** `result` records what the approval made, such as the task it became. */
|
|
59
|
-
approve(id: string, result?: unknown): Promise<ProposedAction>;
|
|
60
|
-
|
|
65
|
+
/** `result` records what the approval made, such as the task it became. `by` must be allowed to decide it. */
|
|
66
|
+
approve(id: string, by: Decider, result?: unknown): Promise<ProposedAction>;
|
|
67
|
+
/** `reason` is kept as the result: why it was turned down. */
|
|
68
|
+
reject(id: string, by: Decider, reason?: string): Promise<ProposedAction>;
|
|
61
69
|
/** An approved action the adapter carried out, with what the provider answered. */
|
|
62
70
|
executed(id: string, result?: unknown): Promise<ProposedAction>;
|
|
63
71
|
failed(id: string, error: string): Promise<ProposedAction>;
|
|
@@ -65,10 +73,15 @@ export interface ProposedActionsStore {
|
|
|
65
73
|
}
|
|
66
74
|
type ProposalOperations = Omit<{
|
|
67
75
|
[K in keyof ProposedActionsStore]: ProposedActionsStore[K] extends (...args: infer A) => Promise<infer R> ? (...args: A) => R : never;
|
|
68
|
-
}, "approve"> & {
|
|
69
|
-
/** `by` records an agent deciding for the owner
|
|
76
|
+
}, "approve" | "reject"> & {
|
|
77
|
+
/** Records a decision already checked; `by` records an agent deciding for the owner. */
|
|
70
78
|
approve(id: string, result?: unknown, by?: Author): ProposedAction;
|
|
79
|
+
reject(id: string, by?: Author, result?: unknown): ProposedAction;
|
|
80
|
+
/** Who proposed it, for `mayDecide`. */
|
|
81
|
+
proposer(id: string): Proposer;
|
|
71
82
|
};
|
|
83
|
+
/** Who proposed an action, for `mayDecide`. */
|
|
84
|
+
export declare const proposerOfAction: ({ proposedBy }: ProposedAction) => Proposer;
|
|
72
85
|
export declare const proposalOperationsOver: (context: StoreContext) => ProposalOperations;
|
|
73
86
|
export declare const proposedActionsStoreOver: (context: StoreContext) => ProposedActionsStore;
|
|
74
87
|
export {};
|
|
@@ -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
|
+
}
|