@wirecat/cli-core 0.548.0 → 0.549.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 +207 -0
- package/dist/calendar/cli/index.d.ts +10 -1
- package/dist/calendar/cli/index.js +9 -3
- package/dist/calendar/cli/search-all.d.ts +17 -0
- package/dist/calendar/cli/search-all.js +72 -0
- package/dist/cli/answer-entity.d.ts +4 -0
- package/dist/cli/answer-entity.js +5 -0
- package/dist/cli/bot/context.d.ts +2 -0
- package/dist/cli/compact.d.ts +41 -0
- package/dist/cli/compact.js +313 -0
- package/dist/cli/context.d.ts +11 -2
- package/dist/cli/context.js +15 -6
- package/dist/cli/envelope.d.ts +5 -1
- package/dist/cli/envelope.js +15 -4
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +2 -0
- package/dist/cli/messenger/search-command.d.ts +10 -0
- package/dist/cli/messenger/search-command.js +19 -13
- package/dist/cli/messenger/store-maintenance-command.d.ts +1 -1
- package/dist/cli/program.d.ts +3 -0
- package/dist/cli/program.js +7 -1
- package/dist/domain/providers.d.ts +3 -0
- package/dist/domain/providers.js +20 -4
- package/dist/domain/ref-arguments.js +3 -7
- package/dist/domain/ref-summaries.d.ts +56 -0
- package/dist/domain/ref-summaries.js +63 -0
- package/dist/entities/index.d.ts +11 -0
- package/dist/entities/relationship.d.ts +2 -0
- package/dist/entities/relationship.js +2 -0
- package/dist/entities/task-results.d.ts +10 -0
- package/dist/entities/task-results.js +1 -0
- package/dist/errors/reasons.js +1 -0
- package/dist/output-detail.js +9 -5
- package/dist/output.d.ts +5 -1
- package/dist/output.js +29 -4
- package/dist/services/proposals/kinds/relationship-update.d.ts +4 -0
- package/dist/services/proposals/kinds/relationship-update.js +53 -0
- package/dist/services/proposals/registry.js +2 -0
- package/dist/store/decisions.d.ts +3 -1
- package/dist/store/decisions.js +7 -1
- package/dist/store/index.d.ts +2 -1
- package/dist/store/sqlite/evidence-sources.d.ts +2 -2
- package/dist/store/sqlite/evidence-sources.js +41 -77
- package/dist/store/sqlite/knowledge.d.ts +17 -23
- package/dist/store/sqlite/knowledge.js +13 -13
- package/dist/store/sqlite/manifest.js +2 -0
- package/dist/store/sqlite/migrations.generated.js +12 -0
- package/dist/store/sqlite/notes.d.ts +5 -0
- package/dist/store/sqlite/notes.js +6 -2
- package/dist/store/sqlite/open-store.js +2 -0
- package/dist/store/sqlite/proposed-actions.d.ts +1 -1
- package/dist/store/sqlite/proposed-actions.js +10 -2
- package/dist/store/sqlite/ref-summaries.d.ts +17 -0
- package/dist/store/sqlite/ref-summaries.js +378 -0
- package/dist/store/sqlite/relation-proposals.d.ts +40 -0
- package/dist/store/sqlite/relation-proposals.js +88 -1
- package/dist/store/sqlite/schema.d.ts +16 -0
- package/dist/store/sqlite/schema.js +1 -0
- package/dist/store/sqlite/suggested-links.d.ts +10 -0
- package/dist/store/sqlite/suggested-links.js +68 -0
- package/dist/store/store.d.ts +3 -0
- package/dist/store/store.js +5 -0
- package/package.json +3 -3
- package/parity.json +4 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { proposerOfAction } from "../../../store/index.js";
|
|
2
|
+
import { actionStatus, kept, projectOf, pushed, storedStatus } from "./shared.js";
|
|
3
|
+
const KIND = "relationship_update";
|
|
4
|
+
const summaryOf = ({ changes }) => Object.entries(changes)
|
|
5
|
+
.map(([field, value]) => `${field} ${value ?? "cleared"}`)
|
|
6
|
+
.join(", ");
|
|
7
|
+
const rowOf = (action) => {
|
|
8
|
+
const payload = action.payload;
|
|
9
|
+
return {
|
|
10
|
+
id: action.id,
|
|
11
|
+
kind: KIND,
|
|
12
|
+
status: actionStatus(action.status),
|
|
13
|
+
project: projectOf(payload.to, payload.from),
|
|
14
|
+
subject: `relationship:${payload.relationship}`,
|
|
15
|
+
object: payload.to,
|
|
16
|
+
summary: summaryOf(payload),
|
|
17
|
+
proposedBy: proposerOfAction(action),
|
|
18
|
+
reason: action.reason,
|
|
19
|
+
evidence: [],
|
|
20
|
+
score: null,
|
|
21
|
+
signals: null,
|
|
22
|
+
parts: null,
|
|
23
|
+
createdAt: action.createdAt,
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
/** An agent's change to a relationship's role or purpose; the owner's applies at once and never waits here. */
|
|
27
|
+
export const relationshipUpdateKind = (store) => ({
|
|
28
|
+
kind: KIND,
|
|
29
|
+
stored: [KIND],
|
|
30
|
+
list: async (filter) => {
|
|
31
|
+
const status = storedStatus(filter);
|
|
32
|
+
const stored = await store.proposedActions.list({
|
|
33
|
+
kinds: [KIND],
|
|
34
|
+
...(status === undefined ? {} : { status }),
|
|
35
|
+
...pushed(filter),
|
|
36
|
+
});
|
|
37
|
+
return kept(stored.map(rowOf), filter);
|
|
38
|
+
},
|
|
39
|
+
get: async (id) => rowOf(await store.proposedActions.get(id)),
|
|
40
|
+
decide: async (id, { confirm, by, reason }) => {
|
|
41
|
+
const { relation } = await store.knowledge.decideRelationUpdate(id, {
|
|
42
|
+
confirm,
|
|
43
|
+
by,
|
|
44
|
+
...(reason === undefined ? {} : { reason }),
|
|
45
|
+
});
|
|
46
|
+
return {
|
|
47
|
+
id,
|
|
48
|
+
kind: KIND,
|
|
49
|
+
status: confirm ? "confirmed" : "rejected",
|
|
50
|
+
made: relation ? [`relationship:${relation.id}`] : [],
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
});
|
|
@@ -6,6 +6,7 @@ import { findingsKind } from "./kinds/findings.js";
|
|
|
6
6
|
import { keyDocumentKind } from "./kinds/key-document.js";
|
|
7
7
|
import { memoryKind } from "./kinds/memory.js";
|
|
8
8
|
import { relationshipKind } from "./kinds/relationship.js";
|
|
9
|
+
import { relationshipUpdateKind } from "./kinds/relationship-update.js";
|
|
9
10
|
const PREFIX = /^([a-z][a-z_]*):/;
|
|
10
11
|
const refusalOf = (id, error) => {
|
|
11
12
|
if (!(error instanceof CliError))
|
|
@@ -118,6 +119,7 @@ export const proposalRegistry = (store) => {
|
|
|
118
119
|
const registry = emptyProposalRegistry(store);
|
|
119
120
|
for (const kind of [
|
|
120
121
|
relationshipKind(store),
|
|
122
|
+
relationshipUpdateKind(store),
|
|
121
123
|
keyDocumentKind(store),
|
|
122
124
|
factKind(store),
|
|
123
125
|
memoryKind(store),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Who proposed something waiting for the owner. `unknown` is a suggestion that names no author, such as an
|
|
3
|
-
* unconfirmed link written without provenance: only the owner decides it.
|
|
3
|
+
* unconfirmed link written without provenance: only the owner decides it. `verified: false` is a bot named only by
|
|
4
|
+
* free text anyone could write, as an old suggested link's provenance: only the owner decides that one too.
|
|
4
5
|
*/
|
|
5
6
|
export type Proposer = {
|
|
6
7
|
type: "person";
|
|
@@ -10,6 +11,7 @@ export type Proposer = {
|
|
|
10
11
|
id: string;
|
|
11
12
|
name: string;
|
|
12
13
|
kind: "agent" | "rule";
|
|
14
|
+
verified?: false;
|
|
13
15
|
} | {
|
|
14
16
|
type: "unknown";
|
|
15
17
|
};
|
package/dist/store/decisions.js
CHANGED
|
@@ -20,6 +20,8 @@ export const mayDecide = (row, by, policy = DEFAULT_DECISION_POLICY) => {
|
|
|
20
20
|
const proposer = row.proposedBy;
|
|
21
21
|
if (proposer.type === "unknown")
|
|
22
22
|
return refuse("permission_error", "proposal.owner-only", "this proposal names no author; only the owner decides it");
|
|
23
|
+
if (proposer.type === "bot" && proposer.verified === false)
|
|
24
|
+
return refuse("permission_error", "proposal.owner-only", `${proposer.name} is named only by an old link's provenance; only the owner decides it`);
|
|
23
25
|
if (proposer.type === "bot" && proposer.kind === "rule" && policy.ruleProposalsOwnerOnly)
|
|
24
26
|
return refuse("permission_error", "proposal.owner-only", `proposed by ${proposer.name}: only the owner decides it`);
|
|
25
27
|
if (proposer.type === "bot" && proposer.kind === "agent" && botKey(proposer.name) === botKey(by.bot))
|
|
@@ -39,7 +41,11 @@ export const assertMayWithdraw = (row, by) => {
|
|
|
39
41
|
if (by === "owner")
|
|
40
42
|
return;
|
|
41
43
|
const proposer = row.proposedBy;
|
|
42
|
-
if (row.proposed &&
|
|
44
|
+
if (row.proposed &&
|
|
45
|
+
proposer.type === "bot" &&
|
|
46
|
+
proposer.kind === "agent" &&
|
|
47
|
+
proposer.verified !== false &&
|
|
48
|
+
botKey(proposer.name) === botKey(by.bot))
|
|
43
49
|
return;
|
|
44
50
|
throw new CliError("permission_error", `only the owner removes or reverses ${row.id}`, {
|
|
45
51
|
reason: "proposal.owner-only",
|
package/dist/store/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export type { ContextEvidenceItem, ContextEvidencePage, ContextEvidenceReader, ContextEvidenceRequest, ContextEvidenceSender, ContextSource, } from "../context/evidence.js";
|
|
2
2
|
export { FACT_STATUSES, FACT_TYPES, MEMORY_KINDS, MEMORY_SCOPES, MEMORY_STATUSES, TASK_TYPES, WORK_TASK_TYPES, } from "../domain/knowledge.js";
|
|
3
|
+
export type { RefState, RefSummary, RefSummaryKind, RefsStore } from "../domain/ref-summaries.js";
|
|
3
4
|
export type { TagConsolidationApplyOptions, TagConsolidationOptions, TagConsolidationPreview, TagConsolidationReceipt, TagConsolidationStore, } from "../domain/tag-consolidation.js";
|
|
4
5
|
export type { TagRedirectIndex, TagRedirectResolution, TagRedirectSnapshot } from "../domain/tag-redirects.js";
|
|
5
6
|
export { createTagRedirectIndex } from "../domain/tag-redirects.js";
|
|
@@ -51,7 +52,7 @@ export { type NearestNote, NOTE_FIELDS, type NoteChunkToEmbed, type NoteHit, typ
|
|
|
51
52
|
export type { FileNoteInput, Link, LinkInput, LinkListFilter, Note, NoteFolder, NotesStore, NoteTag, } from "./sqlite/notes.js";
|
|
52
53
|
export type { PrivateContact, PrivateContactNote } from "./sqlite/private-people.js";
|
|
53
54
|
export { PROPOSAL_STATUSES, type ProposalInput, type ProposedAction, type ProposedActionsStore, proposerOfAction, } from "./sqlite/proposed-actions.js";
|
|
54
|
-
export type { LinkDecision } from "./sqlite/relation-proposals.js";
|
|
55
|
+
export type { LinkDecision, RelationUpdate } from "./sqlite/relation-proposals.js";
|
|
55
56
|
export { fillSearchIndex, resetSearchIndex, type SearchIndexFill, type SearchIndexState, searchIndexState, } from "./sqlite/search-index.js";
|
|
56
57
|
export type { SyncStateSave } from "./sqlite/sync.js";
|
|
57
58
|
export type { TagCreationOptions, TagCreationReview } from "./sqlite/tag-creation.js";
|
|
@@ -13,6 +13,8 @@ export interface EvidenceSource {
|
|
|
13
13
|
/** Deleted at its source since it was cited, or no longer in the store. */
|
|
14
14
|
missing: boolean;
|
|
15
15
|
}
|
|
16
|
+
/** Each distinct reference read back, in order; one the store no longer resolves keeps its reference, `missing`. */
|
|
17
|
+
export declare const sourcesOfReferences: (database: CacheDatabase, references: readonly string[]) => EvidenceSource[];
|
|
16
18
|
/**
|
|
17
19
|
* Every `evidence` link from a memory or fact, in the order they were written, including one whose target was
|
|
18
20
|
* deleted since: `evidenceOf` drops those, so a reader would never learn the record lost a source.
|
|
@@ -21,5 +23,3 @@ export declare const evidenceSourcesOf: (database: CacheDatabase, from: {
|
|
|
21
23
|
type: string;
|
|
22
24
|
id: number;
|
|
23
25
|
}) => EvidenceSource[];
|
|
24
|
-
/** Each distinct reference read back, in order; one the store no longer resolves keeps its reference, `missing`. */
|
|
25
|
-
export declare const sourcesOfReferences: (database: CacheDatabase, references: readonly string[]) => EvidenceSource[];
|
|
@@ -1,83 +1,47 @@
|
|
|
1
1
|
import { linkKind } from "./link-kinds.js";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
email: "SELECT e.subject AS title, coalesce(e.from_name, e.from_address) AS sender, e.sent_at AS at, " +
|
|
19
|
-
"coalesce(e.body_text, e.snippet) AS body, 'email' AS tool FROM emails e WHERE e.id = ?",
|
|
20
|
-
note: "SELECT title, NULL AS sender, updated_at AS at, body, 'notes' AS tool FROM notes WHERE id = ?",
|
|
21
|
-
document: "SELECT d.title, NULL AS sender, coalesce(d.external_updated_at, d.updated_at) AS at, d.body, a.provider AS tool " +
|
|
22
|
-
"FROM documents d JOIN accounts a ON a.id = d.account_id WHERE d.id = ?",
|
|
23
|
-
meeting: "SELECT m.title, NULL AS sender, m.started_at AS at, m.description AS body, a.provider AS tool " +
|
|
24
|
-
"FROM meetings m JOIN accounts a ON a.id = m.account_id WHERE m.id = ?",
|
|
25
|
-
meeting_transcript: "SELECT m.title, NULL AS sender, m.started_at AS at, NULL AS body, a.provider AS tool FROM meeting_transcripts t " +
|
|
26
|
-
"JOIN meetings m ON m.id = t.meeting_id JOIN accounts a ON a.id = m.account_id WHERE t.id = ?",
|
|
27
|
-
meeting_transcript_row: "SELECT m.title, r.speaker_name AS sender, m.started_at + coalesce(r.start_ms, 0) AS at, r.text AS body, " +
|
|
28
|
-
"a.provider AS tool FROM meeting_transcript_rows r JOIN meeting_transcripts t ON t.id = r.meeting_transcript_id " +
|
|
29
|
-
"JOIN meetings m ON m.id = t.meeting_id JOIN accounts a ON a.id = m.account_id WHERE r.id = ?",
|
|
30
|
-
};
|
|
31
|
-
const KIND = {
|
|
32
|
-
meeting_transcript: "meeting",
|
|
33
|
-
meeting_transcript_row: "meeting",
|
|
34
|
-
};
|
|
35
|
-
const sourceOf = (database, thing) => {
|
|
36
|
-
const state = stateOfThing(database, thing);
|
|
37
|
-
const read = READS[thing.type];
|
|
38
|
-
const row = read === undefined || state === "unavailable" ? undefined : database.prepare(read).get(thing.id);
|
|
39
|
-
const gone = state !== "available";
|
|
40
|
-
return {
|
|
41
|
-
ref: referenceOfThing(database, thing) ?? null,
|
|
42
|
-
kind: KIND[thing.type] ?? thing.type,
|
|
43
|
-
title: textOf(row?.title),
|
|
44
|
-
sender: textOf(row?.sender),
|
|
45
|
-
at: iso(row?.at),
|
|
46
|
-
excerpt: gone ? null : excerptOf(row?.body),
|
|
47
|
-
tool: textOf(row?.tool),
|
|
48
|
-
missing: gone,
|
|
49
|
-
};
|
|
50
|
-
};
|
|
2
|
+
import { refReadsOf } from "./ref-summaries.js";
|
|
3
|
+
import { referenceOfThing, storedThing } from "./things.js";
|
|
4
|
+
/** A message is known by its chat here, not by the sender and time its summary title says. */
|
|
5
|
+
const sourceOf = (read, ref) => ({
|
|
6
|
+
ref,
|
|
7
|
+
kind: read.kind,
|
|
8
|
+
title: read.kind === "message" ? read.in : read.title,
|
|
9
|
+
sender: read.sender,
|
|
10
|
+
at: read.at,
|
|
11
|
+
excerpt: read.excerpt,
|
|
12
|
+
tool: read.provider,
|
|
13
|
+
missing: read.state !== "available",
|
|
14
|
+
});
|
|
15
|
+
/** Each distinct reference read back, in order; one the store no longer resolves keeps its reference, `missing`. */
|
|
16
|
+
export const sourcesOfReferences = (database, references) => [...refReadsOf(database, references, { excerpt: true })].map(([ref, read]) => sourceOf(read, ref));
|
|
17
|
+
const GONE_KIND = { meeting_transcript: "meeting", meeting_transcript_row: "meeting" };
|
|
51
18
|
/**
|
|
52
19
|
* Every `evidence` link from a memory or fact, in the order they were written, including one whose target was
|
|
53
20
|
* deleted since: `evidenceOf` drops those, so a reader would never learn the record lost a source.
|
|
54
21
|
*/
|
|
55
|
-
export const evidenceSourcesOf = (database, from) =>
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
22
|
+
export const evidenceSourcesOf = (database, from) => {
|
|
23
|
+
const cited = database
|
|
24
|
+
.prepare(`SELECT to_type, to_id FROM links WHERE from_type = ? AND from_id = ? AND kind = ${linkKind("evidence")} ` +
|
|
25
|
+
"AND to_id IS NOT NULL ORDER BY id")
|
|
26
|
+
.all(from.type, from.id)
|
|
27
|
+
.map((link) => {
|
|
28
|
+
const thing = storedThing(link.to_type, link.to_id);
|
|
29
|
+
return { type: thing.type, ref: referenceOfThing(database, thing) };
|
|
30
|
+
});
|
|
31
|
+
const reads = refReadsOf(database, cited.flatMap(({ ref }) => ref ?? []), { excerpt: true });
|
|
32
|
+
return cited.map(({ type, ref }) => {
|
|
33
|
+
const read = ref === undefined ? undefined : reads.get(ref);
|
|
34
|
+
if (ref !== undefined && read !== undefined)
|
|
35
|
+
return sourceOf(read, ref);
|
|
36
|
+
return {
|
|
37
|
+
ref: null,
|
|
38
|
+
kind: GONE_KIND[type] ?? type,
|
|
39
|
+
title: null,
|
|
40
|
+
sender: null,
|
|
41
|
+
at: null,
|
|
42
|
+
excerpt: null,
|
|
43
|
+
tool: null,
|
|
44
|
+
missing: true,
|
|
45
|
+
};
|
|
46
|
+
});
|
|
67
47
|
};
|
|
68
|
-
/** Each distinct reference read back, in order; one the store no longer resolves keeps its reference, `missing`. */
|
|
69
|
-
export const sourcesOfReferences = (database, references) => [...new Set(references)].map((reference) => {
|
|
70
|
-
const thing = thingNamed(database, reference);
|
|
71
|
-
if (thing)
|
|
72
|
-
return { ...sourceOf(database, thing), ref: reference };
|
|
73
|
-
return {
|
|
74
|
-
ref: reference,
|
|
75
|
-
kind: reference.startsWith("msg:") ? "message" : reference.split(":")[0] || "unknown",
|
|
76
|
-
title: null,
|
|
77
|
-
sender: null,
|
|
78
|
-
at: null,
|
|
79
|
-
excerpt: null,
|
|
80
|
-
tool: null,
|
|
81
|
-
missing: true,
|
|
82
|
-
};
|
|
83
|
-
});
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { type QueryAst } from "@wirecat/cli-base/query";
|
|
2
2
|
import { type Decider } from "../decisions.js";
|
|
3
3
|
import type { AccountKey } from "../types.js";
|
|
4
|
-
import { RELATION_KINDS } from "./link-kinds.js";
|
|
5
4
|
import { type MeetingProjectsStore } from "./meeting-projects.js";
|
|
6
5
|
import type { Author } from "./memories.js";
|
|
7
6
|
import { type Link } from "./notes.js";
|
|
8
7
|
import type { StoreContext } from "./open.js";
|
|
9
8
|
import type { ProposedAction } from "./proposed-actions.js";
|
|
10
|
-
import { type LinkDecision } from "./relation-proposals.js";
|
|
9
|
+
import { type KnowledgeRelation, type LinkDecision, type RelateInput, type RelationUpdate } from "./relation-proposals.js";
|
|
11
10
|
import type { TagCreationOptions } from "./tag-creation.js";
|
|
12
11
|
import { type Label, type TagKind, type TagRow } from "./tags.js";
|
|
13
12
|
import { type ThingState } from "./things.js";
|
|
14
13
|
import { type VocabularyStore } from "./vocabulary.js";
|
|
14
|
+
export type { KnowledgeRelation, RelateInput } from "./relation-proposals.js";
|
|
15
15
|
export type { Label } from "./tags.js";
|
|
16
16
|
export type KnowledgeTarget = {
|
|
17
17
|
type: "message";
|
|
@@ -90,18 +90,6 @@ export interface Project {
|
|
|
90
90
|
scope: (typeof SCOPES)[number];
|
|
91
91
|
createdAt: string;
|
|
92
92
|
}
|
|
93
|
-
export interface KnowledgeRelation {
|
|
94
|
-
id: string;
|
|
95
|
-
from: string;
|
|
96
|
-
to: string;
|
|
97
|
-
kind: (typeof RELATION_KINDS)[number];
|
|
98
|
-
role: string | null;
|
|
99
|
-
evidence: string | null;
|
|
100
|
-
confirmed: boolean;
|
|
101
|
-
provenance: string | null;
|
|
102
|
-
confidenceCategory: "owner-confirmed" | "weak";
|
|
103
|
-
createdAt: string;
|
|
104
|
-
}
|
|
105
93
|
export interface Reminder {
|
|
106
94
|
id: string;
|
|
107
95
|
task: string;
|
|
@@ -199,9 +187,24 @@ export interface KnowledgeStore extends VocabularyStore, MeetingProjectsStore {
|
|
|
199
187
|
to: string;
|
|
200
188
|
kind: KnowledgeRelation["kind"];
|
|
201
189
|
role?: string;
|
|
190
|
+
purpose?: string;
|
|
202
191
|
evidence?: string[];
|
|
203
192
|
reason?: string;
|
|
204
193
|
}, by: Exclude<Author, "owner">): Promise<ProposedAction>;
|
|
194
|
+
/** The owner sets a relationship's role and purpose; `null` clears one. */
|
|
195
|
+
updateRelation(key: KnowledgeScope, id: string, input: RelationUpdate & {
|
|
196
|
+
by: "owner";
|
|
197
|
+
}): Promise<KnowledgeRelation>;
|
|
198
|
+
/** An agent's change is a `relationship_update` proposal for the owner; the same pending change is answered again. */
|
|
199
|
+
updateRelation(key: KnowledgeScope, id: string, input: RelationUpdate & {
|
|
200
|
+
by: Exclude<Author, "owner">;
|
|
201
|
+
reason?: string;
|
|
202
|
+
}): Promise<ProposedAction>;
|
|
203
|
+
/** Decides a `relationship_update` proposal; a confirmed one changes the relationship in the same transaction. */
|
|
204
|
+
decideRelationUpdate(id: string, decision: LinkDecision): Promise<{
|
|
205
|
+
proposal: ProposedAction;
|
|
206
|
+
relation: KnowledgeRelation | null;
|
|
207
|
+
}>;
|
|
205
208
|
/** Decides a stored `relationship` or `key_document` proposal; the link and the decision land together. */
|
|
206
209
|
decideLinkProposal(id: string, decision: LinkDecision): Promise<{
|
|
207
210
|
proposal: ProposedAction;
|
|
@@ -229,13 +232,4 @@ export interface KnowledgeStore extends VocabularyStore, MeetingProjectsStore {
|
|
|
229
232
|
}): Promise<Reminder[]>;
|
|
230
233
|
acknowledgeReminder(key: AccountKey, id: string, receipt: string): Promise<Reminder>;
|
|
231
234
|
}
|
|
232
|
-
export interface RelateInput {
|
|
233
|
-
from: string;
|
|
234
|
-
to: string;
|
|
235
|
-
kind: KnowledgeRelation["kind"];
|
|
236
|
-
role?: string;
|
|
237
|
-
evidence?: string;
|
|
238
|
-
confirmed?: boolean;
|
|
239
|
-
provenance?: string;
|
|
240
|
-
}
|
|
241
235
|
export declare const knowledgeStoreOver: (context: StoreContext) => KnowledgeStore;
|
|
@@ -15,7 +15,7 @@ import { meetingProjectsStoreOver } from "./meeting-projects.js";
|
|
|
15
15
|
import { holdingText } from "./note-index.js";
|
|
16
16
|
import { insertLink, linkOf } from "./notes.js";
|
|
17
17
|
import { queryFilter } from "./query-filter.js";
|
|
18
|
-
import { relationProposalsOver } from "./relation-proposals.js";
|
|
18
|
+
import { relationOf, relationProposalsOver, } from "./relation-proposals.js";
|
|
19
19
|
import { addTags as addThingTags, canonicalTagNamed, createTag, labelsOf, liveTagging, removeTags as removeThingTags, setMainTopic, } from "./tags.js";
|
|
20
20
|
import { INBOX_KEY_LENGTH, TASK_KEY_PREFIXES } from "./task-keys.js";
|
|
21
21
|
import { inboxKey, taskAccountOf, taskIdOf, taskRowOf } from "./tasks.js";
|
|
@@ -221,18 +221,6 @@ export const knowledgeStoreOver = (context) => {
|
|
|
221
221
|
scope: row.scope,
|
|
222
222
|
createdAt: iso(row.created_at),
|
|
223
223
|
});
|
|
224
|
-
const relationOf = (link) => ({
|
|
225
|
-
id: link.id,
|
|
226
|
-
from: link.from,
|
|
227
|
-
to: String(link.to),
|
|
228
|
-
kind: link.kind,
|
|
229
|
-
role: link.role,
|
|
230
|
-
evidence: link.evidence,
|
|
231
|
-
confirmed: link.confirmed,
|
|
232
|
-
provenance: link.provenance,
|
|
233
|
-
confidenceCategory: link.confirmed ? "owner-confirmed" : "weak",
|
|
234
|
-
createdAt: link.createdAt,
|
|
235
|
-
});
|
|
236
224
|
const proposals = relationProposalsOver(context);
|
|
237
225
|
const RELATIONS = `SELECT * FROM links WHERE kind IN (${RELATION_KINDS.map(linkKind).join(",")}) AND to_id IS NOT NULL`;
|
|
238
226
|
const relation = (id) => {
|
|
@@ -561,6 +549,7 @@ export const knowledgeStoreOver = (context) => {
|
|
|
561
549
|
origin: confirmed ? "owner" : "suggested",
|
|
562
550
|
confirmed,
|
|
563
551
|
...(input.role === undefined ? {} : { role: input.role }),
|
|
552
|
+
...(input.purpose === undefined ? {} : { purpose: input.purpose }),
|
|
564
553
|
...(input.evidence === undefined ? {} : { evidence: input.evidence }),
|
|
565
554
|
...(input.provenance === undefined ? {} : { provenance: input.provenance }),
|
|
566
555
|
})));
|
|
@@ -573,6 +562,17 @@ export const knowledgeStoreOver = (context) => {
|
|
|
573
562
|
});
|
|
574
563
|
return proposals.propose(from, input, by);
|
|
575
564
|
},
|
|
565
|
+
updateRelation: (async (key, id, input) => {
|
|
566
|
+
checked(key);
|
|
567
|
+
const link = linkOf(database, relation(id));
|
|
568
|
+
if (input.by === "owner")
|
|
569
|
+
return relationOf(proposals.update(link, input));
|
|
570
|
+
return proposals.proposeUpdate(link, input, input.by);
|
|
571
|
+
}),
|
|
572
|
+
decideRelationUpdate: async (id, decision) => {
|
|
573
|
+
const { proposal, link } = proposals.decideUpdate(id, decision);
|
|
574
|
+
return { proposal, relation: link && relationOf(link) };
|
|
575
|
+
},
|
|
576
576
|
decideLinkProposal: async (id, decision) => proposals.decideProposal(id, decision),
|
|
577
577
|
decideSuggestedLink: async (id, decision) => proposals.decideLink(id, decision),
|
|
578
578
|
relations: async (key, reference) => {
|
|
@@ -57,6 +57,8 @@ export const MANIFEST = [
|
|
|
57
57
|
{ name: "20261031230945_version-41-task-fields", version: 41, minCompatible: 34 },
|
|
58
58
|
{ name: "20261101002523_version-42-task-revision", version: 42, minCompatible: 34 },
|
|
59
59
|
{ name: "20261101011859_version-43-memory-project", version: 43, minCompatible: 34 },
|
|
60
|
+
{ name: "20261102050533_version-44-relationship-purpose", version: 44, minCompatible: 34 },
|
|
61
|
+
{ name: "20261102050534_version-44-suggested-links-move", version: 44, minCompatible: 34 },
|
|
60
62
|
];
|
|
61
63
|
export const generatedMigrations = (manifest = MANIFEST, generated = GENERATED) => {
|
|
62
64
|
const migrations = [];
|
|
@@ -877,5 +877,17 @@ export const GENERATED = [
|
|
|
877
877
|
"ALTER TABLE `memories` ADD `project_id` integer REFERENCES projects(id);",
|
|
878
878
|
"CREATE INDEX `memories_by_project_id` ON `memories` (`project_id`);"
|
|
879
879
|
]
|
|
880
|
+
},
|
|
881
|
+
{
|
|
882
|
+
"name": "20261102050533_version-44-relationship-purpose",
|
|
883
|
+
"statements": [
|
|
884
|
+
"ALTER TABLE `links` ADD `purpose` text;"
|
|
885
|
+
]
|
|
886
|
+
},
|
|
887
|
+
{
|
|
888
|
+
"name": "20261102050534_version-44-suggested-links-move",
|
|
889
|
+
"statements": [
|
|
890
|
+
"-- A suggestion's references (chat:, msg:) need the store's own encoding, which SQL cannot write, so the next writable\n-- open moves the unconfirmed agent and rule links into `proposed_actions` and deletes this key (moveSuggestedLinks).\nINSERT OR REPLACE INTO store_settings (key, value, at) VALUES ('suggested_links_to_move', '1', unixepoch() * 1000);"
|
|
891
|
+
]
|
|
880
892
|
}
|
|
881
893
|
];
|
|
@@ -56,6 +56,8 @@ export interface Link {
|
|
|
56
56
|
origin: (typeof LINK_SOURCES)[number];
|
|
57
57
|
targetText: string | null;
|
|
58
58
|
role: string | null;
|
|
59
|
+
/** What a chat is for in a project, from the purposes memo's config lists; `null` means `work`. */
|
|
60
|
+
purpose: string | null;
|
|
59
61
|
evidence: string | null;
|
|
60
62
|
provenance: string | null;
|
|
61
63
|
confirmed: boolean;
|
|
@@ -79,6 +81,7 @@ export interface LinkInput {
|
|
|
79
81
|
anchor?: string;
|
|
80
82
|
origin?: (typeof LINK_ORIGINS)[number];
|
|
81
83
|
role?: string;
|
|
84
|
+
purpose?: string;
|
|
82
85
|
evidence?: string;
|
|
83
86
|
provenance?: string;
|
|
84
87
|
confirmed?: boolean;
|
|
@@ -194,6 +197,8 @@ export declare const requiredThing: (database: CacheDatabase, reference: string,
|
|
|
194
197
|
* and only a name some note is waiting for costs more.
|
|
195
198
|
*/
|
|
196
199
|
export declare const resolvePersonLinks: (database: CacheDatabase, names: (string | null | undefined)[]) => void;
|
|
200
|
+
/** The store keeps no list of purposes: memo's config holds it, and checks a purpose before it gets here. */
|
|
201
|
+
export declare const purposeOf: (purpose: string) => string;
|
|
197
202
|
/** Writes one link; an identical resolved link is answered instead of doubled. */
|
|
198
203
|
export declare const insertLink: (context: StoreContext, input: LinkInput) => Link;
|
|
199
204
|
export declare const notesStoreOver: (context: StoreContext) => Omit<NotesStore, keyof NoteSearch>;
|
|
@@ -87,6 +87,7 @@ export const linkOf = (database, row) => {
|
|
|
87
87
|
origin: row.source,
|
|
88
88
|
targetText: row.target_text == null ? null : String(row.target_text),
|
|
89
89
|
role: row.role == null ? null : String(row.role),
|
|
90
|
+
purpose: row.purpose == null ? null : String(row.purpose),
|
|
90
91
|
evidence: row.evidence == null ? null : String(row.evidence),
|
|
91
92
|
provenance: metadata.provenance ?? null,
|
|
92
93
|
confirmed: Number(row.confirmed) === 1,
|
|
@@ -164,6 +165,8 @@ const ownerLink = (input) => {
|
|
|
164
165
|
});
|
|
165
166
|
return input;
|
|
166
167
|
};
|
|
168
|
+
/** The store keeps no list of purposes: memo's config holds it, and checks a purpose before it gets here. */
|
|
169
|
+
export const purposeOf = (purpose) => textOf(purpose, "a purpose", 100);
|
|
167
170
|
/** Writes one link; an identical resolved link is answered instead of doubled. */
|
|
168
171
|
export const insertLink = (context, input) => {
|
|
169
172
|
const { database, now } = context;
|
|
@@ -187,8 +190,9 @@ export const insertLink = (context, input) => {
|
|
|
187
190
|
const provenance = input.provenance === undefined ? undefined : textOf(input.provenance, "provenance", 2000);
|
|
188
191
|
const row = database
|
|
189
192
|
.prepare("INSERT INTO links (from_type, from_id, to_type, to_id, kind, anchor, source, target_text, target_folded, role, evidence, " +
|
|
190
|
-
"metadata, confirmed, created_at, author, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
191
|
-
|
|
193
|
+
"metadata, confirmed, created_at, author, updated_at, purpose) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) " +
|
|
194
|
+
"RETURNING id")
|
|
195
|
+
.get(from.type, from.id, to?.type ?? null, to?.id ?? null, kind, input.anchor ?? null, origin, targetText, targetText === null ? null : targetKey(targetText), input.role === undefined ? null : textOf(input.role, "a role", 200), input.evidence === undefined ? null : textOf(input.evidence, "evidence", 2000), provenance === undefined ? null : JSON.stringify({ provenance }), input.confirmed === false ? 0 : 1, at, origin === "owner" ? "owner" : null, at, input.purpose === undefined ? null : purposeOf(input.purpose));
|
|
192
196
|
if (to === null && targetText !== null)
|
|
193
197
|
resolveKey(database, targetKey(targetText));
|
|
194
198
|
return linkOf(database, database.prepare("SELECT * FROM links WHERE id = ?").get(Number(row?.id)));
|
|
@@ -16,6 +16,7 @@ import { loadProviders } from "./providers.js";
|
|
|
16
16
|
import { assertReadCompatible } from "./read-compatibility.js";
|
|
17
17
|
import { stemsState } from "./stems.js";
|
|
18
18
|
import { fillStems, followDefaultStemmers } from "./stems-fill.js";
|
|
19
|
+
import { moveSuggestedLinks } from "./suggested-links.js";
|
|
19
20
|
import { fillVectorIndexes } from "./vector-index.js";
|
|
20
21
|
export const BACKFILL_ON_OPEN = 5_000;
|
|
21
22
|
const NOT_WRITABLE = new Set(["EACCES", "EPERM", "EROFS"]);
|
|
@@ -55,6 +56,7 @@ export const openStoreContext = async ({ path, env, now = Date.now, command, ver
|
|
|
55
56
|
fillFiles(database, now);
|
|
56
57
|
fillFolded(database);
|
|
57
58
|
fillJoinKeys(database);
|
|
59
|
+
moveSuggestedLinks(database, now);
|
|
58
60
|
const pending = pendingNormalization(database);
|
|
59
61
|
if (pending > 0 && pending <= BACKFILL_ON_OPEN)
|
|
60
62
|
backfillNormalized(database);
|
|
@@ -81,7 +81,7 @@ type ProposalOperations = Omit<{
|
|
|
81
81
|
proposer(id: string): Proposer;
|
|
82
82
|
};
|
|
83
83
|
/** Who proposed an action, for `mayDecide`. */
|
|
84
|
-
export declare const proposerOfAction: ({ proposedBy }: ProposedAction) => Proposer;
|
|
84
|
+
export declare const proposerOfAction: ({ proposedBy, payload }: ProposedAction) => Proposer;
|
|
85
85
|
export declare const proposalOperationsOver: (context: StoreContext) => ProposalOperations;
|
|
86
86
|
export declare const proposedActionsStoreOver: (context: StoreContext) => ProposedActionsStore;
|
|
87
87
|
export {};
|
|
@@ -20,11 +20,19 @@ const STATUS = `CASE WHEN p.status = 'approved' AND p.kind IN ('${MEETING_FINDIN
|
|
|
20
20
|
const NAMED = `SELECT p.*, ${STATUS} AS shown_status, b.name AS proposer_name, b.kind AS proposer_kind FROM proposed_actions p ` +
|
|
21
21
|
"LEFT JOIN bots b ON p.proposed_by_type = 'bot' AND b.id = p.proposed_by_id";
|
|
22
22
|
/** Who proposed an action, for `mayDecide`. */
|
|
23
|
-
export const proposerOfAction = ({ proposedBy }) => proposedBy.type === "person"
|
|
23
|
+
export const proposerOfAction = ({ proposedBy, payload }) => proposedBy.type === "person"
|
|
24
24
|
? { type: "person", id: proposedBy.id }
|
|
25
25
|
: proposedBy.name === null
|
|
26
26
|
? { type: "unknown" }
|
|
27
|
-
: {
|
|
27
|
+
: {
|
|
28
|
+
type: "bot",
|
|
29
|
+
id: proposedBy.id,
|
|
30
|
+
name: proposedBy.name,
|
|
31
|
+
kind: proposedBy.kind ?? "agent",
|
|
32
|
+
...(movedSuggestion(payload) ? { verified: false } : {}),
|
|
33
|
+
};
|
|
34
|
+
/** A suggestion moved from `links` names its bot only as the link's provenance text said it (store version 44). */
|
|
35
|
+
const movedSuggestion = (payload) => typeof payload === "object" && payload !== null && "suggestedLink" in payload;
|
|
28
36
|
export const proposalOperationsOver = (context) => {
|
|
29
37
|
const { database, now } = context;
|
|
30
38
|
const actionOf = (row) => {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type RefSummary } from "../../domain/ref-summaries.js";
|
|
2
|
+
import type { CacheDatabase } from "../driver.js";
|
|
3
|
+
/** A summary plus what an evidence reader shows beside it; `summaries` drops the extra fields. */
|
|
4
|
+
export interface RefRead extends RefSummary {
|
|
5
|
+
sender: string | null;
|
|
6
|
+
excerpt: string | null;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Every ref read back in one statement per kind present, keyed by the text passed and in its order. A bad ref is
|
|
10
|
+
* `unknown` and a ref the store lacks is `unavailable`: one bad row must not fail a page of them.
|
|
11
|
+
*/
|
|
12
|
+
export declare const refReadsOf: (database: CacheDatabase, texts: readonly string[], { excerpt }?: {
|
|
13
|
+
excerpt?: boolean;
|
|
14
|
+
}) => Map<string, RefRead>;
|
|
15
|
+
export declare const refSummariesOf: (database: CacheDatabase, texts: readonly string[], options?: {
|
|
16
|
+
excerpt?: boolean;
|
|
17
|
+
}) => Map<string, RefSummary>;
|