@unson/brainbase-mcp 0.41.13 → 0.42.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/dist/local-web-host.d.ts +6 -0
- package/dist/local-web-host.js +35 -1
- package/dist/personal-memory.d.ts +101 -0
- package/dist/personal-memory.js +204 -0
- package/dist/ssot.d.ts +3 -1
- package/dist/ssot.js +4 -0
- package/package.json +9 -2
- package/ui/judgment-history.js +74 -3
- package/ui/local-web-shell.js +56 -2
- package/ui/personal-memory-http-port.js +72 -0
- package/ui/personal-memory.css +254 -0
- package/ui/personal-memory.js +677 -0
package/dist/local-web-host.d.ts
CHANGED
|
@@ -195,6 +195,12 @@ export declare function createWorldModelReadModule(context: LocalWebModuleContex
|
|
|
195
195
|
* correction is never saved through a path that skipped the host check.
|
|
196
196
|
*/
|
|
197
197
|
export declare function createGraphWebModule(context: LocalWebModuleContext): LocalWebModule;
|
|
198
|
+
/**
|
|
199
|
+
* 「あなたの記憶」: the owner's `personal-kg.jsonl`, read only. The memory
|
|
200
|
+
* bodies are private, so these reads require the launch token and the same
|
|
201
|
+
* origin like every write on this host.
|
|
202
|
+
*/
|
|
203
|
+
export declare function createPersonalMemoryModule(context: LocalWebModuleContext): LocalWebModule;
|
|
198
204
|
/** 今日 (value proofs), 目的と現状 (objectives + world model), プロジェクトと関係者 and 情報と関係 (Graph). */
|
|
199
205
|
export declare function defaultLocalWebModules(context: LocalWebModuleContext): LocalWebModule[];
|
|
200
206
|
/** Local single-owner host. The caller owns `listen()` and must bind to a loopback address. */
|
package/dist/local-web-host.js
CHANGED
|
@@ -12,6 +12,7 @@ import { createJudgmentHistoryHttpHandler, JUDGMENT_HISTORY_HTTP_PREFIX } from '
|
|
|
12
12
|
import { createJudgmentHistoryReader, createLocalJudgmentHistoryFeedbackWriter, createLocalJudgmentHistorySource } from './judgment-history.js';
|
|
13
13
|
import { defaultJudgmentJournalRoot, JudgmentValueProofJournalCache } from './judgment-value-proof-review.js';
|
|
14
14
|
import { createMeetingMinutesHttpHandler } from './meeting-minutes-http.js';
|
|
15
|
+
import { createPersonalMemoryHttpHandler, createPersonalMemoryJudgmentIndex, readLocalPersonalMemories } from './personal-memory.js';
|
|
15
16
|
import { createMeetingMinutesLineageHttpHandler } from './meeting-minutes-lineage-http.js';
|
|
16
17
|
import { createMeetingMinutesLineageCorrectionHook, } from './meeting-minutes-lineage-lifecycle.js';
|
|
17
18
|
import { createMeetingMinutesStore } from './meeting-minutes.js';
|
|
@@ -891,6 +892,38 @@ export function createGraphWebModule(context) {
|
|
|
891
892
|
handle: handler
|
|
892
893
|
};
|
|
893
894
|
}
|
|
895
|
+
/**
|
|
896
|
+
* 「あなたの記憶」: the owner's `personal-kg.jsonl`, read only. The memory
|
|
897
|
+
* bodies are private, so these reads require the launch token and the same
|
|
898
|
+
* origin like every write on this host.
|
|
899
|
+
*/
|
|
900
|
+
export function createPersonalMemoryModule(context) {
|
|
901
|
+
const readMemories = () => readLocalPersonalMemories(context.dataDir);
|
|
902
|
+
const index = createPersonalMemoryJudgmentIndex({
|
|
903
|
+
source: createLocalJudgmentHistorySource({
|
|
904
|
+
root: context.journalRoot,
|
|
905
|
+
entrypoint: context.judgmentHistoryEntrypoint ?? 'codex'
|
|
906
|
+
}),
|
|
907
|
+
memoryIds: async () => {
|
|
908
|
+
const list = await readMemories();
|
|
909
|
+
return list.status === 'ok' ? list.items.map((item) => item.id) : null;
|
|
910
|
+
}
|
|
911
|
+
});
|
|
912
|
+
return {
|
|
913
|
+
id: 'personal-memory',
|
|
914
|
+
uiFiles: ['personal-memory.js', 'personal-memory-http-port.js', 'personal-memory.css'],
|
|
915
|
+
handle: createPersonalMemoryHttpHandler({
|
|
916
|
+
readMemories,
|
|
917
|
+
index,
|
|
918
|
+
authorize(request) {
|
|
919
|
+
if (isTrustedLocalWrite(request, context.token))
|
|
920
|
+
return;
|
|
921
|
+
throw rejectUntrustedWrite(request, context.token)
|
|
922
|
+
?? new LocalWebHttpError(403, 'web_token_required', 'A valid launch token is required');
|
|
923
|
+
}
|
|
924
|
+
})
|
|
925
|
+
};
|
|
926
|
+
}
|
|
894
927
|
/** 今日 (value proofs), 目的と現状 (objectives + world model), プロジェクトと関係者 and 情報と関係 (Graph). */
|
|
895
928
|
export function defaultLocalWebModules(context) {
|
|
896
929
|
return [
|
|
@@ -900,7 +933,8 @@ export function defaultLocalWebModules(context) {
|
|
|
900
933
|
createMeetingMinutesModule(context),
|
|
901
934
|
createObjectiveFoundationModule(context),
|
|
902
935
|
createWorldModelReadModule(context),
|
|
903
|
-
createGraphWebModule(context)
|
|
936
|
+
createGraphWebModule(context),
|
|
937
|
+
createPersonalMemoryModule(context)
|
|
904
938
|
];
|
|
905
939
|
}
|
|
906
940
|
function escapeAttribute(value) {
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import type { JudgmentHistorySource } from './judgment-history.js';
|
|
3
|
+
import type { PersonalKgEntry } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* 「あなたの記憶」 (docs/specs/personal-memory-viewer.md): the owner's own
|
|
6
|
+
* memories read from the local `personal-kg.jsonl`, and the judgments that
|
|
7
|
+
* used each one, found back from the judgment journal's `personal_knowledge`
|
|
8
|
+
* references. Read only. A memory body travels in the response body only,
|
|
9
|
+
* never in a URL or a log line.
|
|
10
|
+
*/
|
|
11
|
+
export declare const PERSONAL_MEMORY_HTTP_PREFIX = "/api/personal-memory";
|
|
12
|
+
export type PersonalMemoryState = 'active' | 'superseded' | 'retracted' | 'unrecorded';
|
|
13
|
+
export interface PersonalMemoryItem {
|
|
14
|
+
readonly id: string;
|
|
15
|
+
readonly kind: string | null;
|
|
16
|
+
readonly text: string;
|
|
17
|
+
readonly tags: readonly string[];
|
|
18
|
+
readonly state: PersonalMemoryState;
|
|
19
|
+
readonly supersededBy: string | null;
|
|
20
|
+
readonly origin: {
|
|
21
|
+
readonly organizationLabel: string | null;
|
|
22
|
+
readonly projectLabel: string | null;
|
|
23
|
+
readonly workplace: string | null;
|
|
24
|
+
readonly inferred: boolean;
|
|
25
|
+
};
|
|
26
|
+
readonly occurredAt: string | null;
|
|
27
|
+
readonly updatedAt: string | null;
|
|
28
|
+
readonly recallScope: 'organization' | 'general' | 'unknown';
|
|
29
|
+
readonly history: readonly {
|
|
30
|
+
readonly state: PersonalMemoryState;
|
|
31
|
+
readonly at: string;
|
|
32
|
+
readonly reason: string | null;
|
|
33
|
+
}[];
|
|
34
|
+
}
|
|
35
|
+
export type PersonalMemoryList = {
|
|
36
|
+
readonly status: 'ok';
|
|
37
|
+
readonly items: readonly PersonalMemoryItem[];
|
|
38
|
+
} | {
|
|
39
|
+
readonly status: 'unavailable';
|
|
40
|
+
readonly reason: string;
|
|
41
|
+
};
|
|
42
|
+
export interface PersonalMemoryJudgment {
|
|
43
|
+
readonly decisionId: string;
|
|
44
|
+
readonly title: string | null;
|
|
45
|
+
readonly at: string;
|
|
46
|
+
readonly kind: string | null;
|
|
47
|
+
}
|
|
48
|
+
export type PersonalMemoryJudgments = {
|
|
49
|
+
readonly status: 'ok';
|
|
50
|
+
readonly items: readonly PersonalMemoryJudgment[];
|
|
51
|
+
readonly total: number;
|
|
52
|
+
} | {
|
|
53
|
+
readonly status: 'unavailable';
|
|
54
|
+
readonly reason: string;
|
|
55
|
+
};
|
|
56
|
+
export type PersonalMemoryJudgmentCounts = {
|
|
57
|
+
readonly status: 'ok';
|
|
58
|
+
readonly counts: Readonly<Record<string, number>>;
|
|
59
|
+
} | {
|
|
60
|
+
readonly status: 'unavailable';
|
|
61
|
+
readonly reason: string;
|
|
62
|
+
};
|
|
63
|
+
/** The local writer keeps no state, origin organization or recall scope: the spec's fixed values. */
|
|
64
|
+
export declare function personalMemoryFromEntry(entry: PersonalKgEntry): PersonalMemoryItem;
|
|
65
|
+
/** An empty file is zero memories; a missing or malformed one is unavailable, never zero. */
|
|
66
|
+
export declare function readLocalPersonalMemories(dataDir: string): Promise<PersonalMemoryList>;
|
|
67
|
+
export interface PersonalMemoryJudgmentIndex {
|
|
68
|
+
judgmentsUsing(id: string, limit: number): Promise<PersonalMemoryJudgments>;
|
|
69
|
+
counts(): Promise<PersonalMemoryJudgmentCounts>;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Finds judgments back from their `personal_knowledge` references. A
|
|
73
|
+
* reference names a memory by its id, so the journal is bound to these
|
|
74
|
+
* memories only when at least one recorded reference is one of their ids.
|
|
75
|
+
* When every reference names a memory kept elsewhere (for example the
|
|
76
|
+
* organization's personal KG), "not used" cannot be told from "used under
|
|
77
|
+
* another id", and the answer is unavailable instead of zero.
|
|
78
|
+
*/
|
|
79
|
+
export declare function createPersonalMemoryJudgmentIndex(options: {
|
|
80
|
+
readonly source: JudgmentHistorySource;
|
|
81
|
+
/** The memory ids, or null when the memories cannot be read. */
|
|
82
|
+
readonly memoryIds: () => Promise<readonly string[] | null>;
|
|
83
|
+
/**
|
|
84
|
+
* How long one journal read answers later requests. A large journal takes
|
|
85
|
+
* tens of seconds to scan, and one opened memory asks for its judgments and
|
|
86
|
+
* the list's counts at the same time.
|
|
87
|
+
*/
|
|
88
|
+
readonly reuseMs?: number;
|
|
89
|
+
readonly now?: () => number;
|
|
90
|
+
}): PersonalMemoryJudgmentIndex;
|
|
91
|
+
/**
|
|
92
|
+
* GET routes under `/api/personal-memory`. `authorize` runs before any read
|
|
93
|
+
* and throws to refuse the request (the local host requires its launch token
|
|
94
|
+
* on these reads, not only on writes).
|
|
95
|
+
*/
|
|
96
|
+
export declare function createPersonalMemoryHttpHandler(options: {
|
|
97
|
+
readonly readMemories: () => Promise<PersonalMemoryList>;
|
|
98
|
+
readonly index: PersonalMemoryJudgmentIndex;
|
|
99
|
+
readonly authorize: (request: IncomingMessage) => void;
|
|
100
|
+
readonly basePath?: string;
|
|
101
|
+
}): (request: IncomingMessage, response: ServerResponse) => Promise<boolean>;
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { stat } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { requestUrl, writeJson } from './local-web-security.js';
|
|
4
|
+
import { readPersonalKg } from './ssot.js';
|
|
5
|
+
/**
|
|
6
|
+
* 「あなたの記憶」 (docs/specs/personal-memory-viewer.md): the owner's own
|
|
7
|
+
* memories read from the local `personal-kg.jsonl`, and the judgments that
|
|
8
|
+
* used each one, found back from the judgment journal's `personal_knowledge`
|
|
9
|
+
* references. Read only. A memory body travels in the response body only,
|
|
10
|
+
* never in a URL or a log line.
|
|
11
|
+
*/
|
|
12
|
+
export const PERSONAL_MEMORY_HTTP_PREFIX = '/api/personal-memory';
|
|
13
|
+
/** The local writer keeps no state, origin organization or recall scope: the spec's fixed values. */
|
|
14
|
+
export function personalMemoryFromEntry(entry) {
|
|
15
|
+
return {
|
|
16
|
+
id: entry.id,
|
|
17
|
+
kind: entry.type,
|
|
18
|
+
text: entry.text,
|
|
19
|
+
tags: [...entry.tags ?? []],
|
|
20
|
+
state: 'unrecorded',
|
|
21
|
+
supersededBy: null,
|
|
22
|
+
origin: { organizationLabel: '手元', projectLabel: null, workplace: null, inferred: false },
|
|
23
|
+
occurredAt: null,
|
|
24
|
+
updatedAt: entry.updatedAt ?? null,
|
|
25
|
+
recallScope: 'general',
|
|
26
|
+
history: []
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function newestFirst(left, right) {
|
|
30
|
+
if (left === right)
|
|
31
|
+
return 0;
|
|
32
|
+
if (left === null)
|
|
33
|
+
return 1;
|
|
34
|
+
if (right === null)
|
|
35
|
+
return -1;
|
|
36
|
+
return left < right ? 1 : -1;
|
|
37
|
+
}
|
|
38
|
+
/** An empty file is zero memories; a missing or malformed one is unavailable, never zero. */
|
|
39
|
+
export async function readLocalPersonalMemories(dataDir) {
|
|
40
|
+
try {
|
|
41
|
+
await stat(join(dataDir, 'personal-kg.jsonl'));
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return { status: 'unavailable', reason: 'personal_kg_missing' };
|
|
45
|
+
}
|
|
46
|
+
let entries;
|
|
47
|
+
try {
|
|
48
|
+
entries = await readPersonalKg(dataDir);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return { status: 'unavailable', reason: 'personal_kg_invalid' };
|
|
52
|
+
}
|
|
53
|
+
const items = entries.map(personalMemoryFromEntry);
|
|
54
|
+
items.sort((left, right) => newestFirst(left.updatedAt, right.updatedAt));
|
|
55
|
+
return { status: 'ok', items };
|
|
56
|
+
}
|
|
57
|
+
function personalReferences(record) {
|
|
58
|
+
const refs = new Set();
|
|
59
|
+
for (const reference of record.judgment.selected_references ?? []) {
|
|
60
|
+
if (reference.kind === 'personal_knowledge' && reference.ref)
|
|
61
|
+
refs.add(reference.ref);
|
|
62
|
+
}
|
|
63
|
+
return refs;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Finds judgments back from their `personal_knowledge` references. A
|
|
67
|
+
* reference names a memory by its id, so the journal is bound to these
|
|
68
|
+
* memories only when at least one recorded reference is one of their ids.
|
|
69
|
+
* When every reference names a memory kept elsewhere (for example the
|
|
70
|
+
* organization's personal KG), "not used" cannot be told from "used under
|
|
71
|
+
* another id", and the answer is unavailable instead of zero.
|
|
72
|
+
*/
|
|
73
|
+
export function createPersonalMemoryJudgmentIndex(options) {
|
|
74
|
+
async function build() {
|
|
75
|
+
const [snapshot, ids] = await Promise.all([options.source.read(), options.memoryIds()]);
|
|
76
|
+
if (ids === null)
|
|
77
|
+
return { status: 'unavailable', reason: 'personal_kg_unavailable' };
|
|
78
|
+
if (snapshot.status !== 'available' || !snapshot.records) {
|
|
79
|
+
return { status: 'unavailable', reason: `journal_${snapshot.status === 'partial' ? 'partial' : 'unavailable'}` };
|
|
80
|
+
}
|
|
81
|
+
const known = new Set(ids);
|
|
82
|
+
const byMemory = new Map(ids.map((id) => [id, []]));
|
|
83
|
+
let referenced = 0;
|
|
84
|
+
let bound = 0;
|
|
85
|
+
for (const record of snapshot.records) {
|
|
86
|
+
const refs = personalReferences(record);
|
|
87
|
+
for (const ref of refs) {
|
|
88
|
+
referenced += 1;
|
|
89
|
+
if (!known.has(ref))
|
|
90
|
+
continue;
|
|
91
|
+
bound += 1;
|
|
92
|
+
byMemory.get(ref)?.push({ decisionId: record.record_id, title: record.judgment.summary, at: record.recorded_at, kind: null });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (referenced > 0 && bound === 0)
|
|
96
|
+
return { status: 'unavailable', reason: 'reference_source_mismatch' };
|
|
97
|
+
for (const judgments of byMemory.values())
|
|
98
|
+
judgments.sort((left, right) => newestFirst(left.at, right.at));
|
|
99
|
+
return { status: 'ok', byMemory };
|
|
100
|
+
}
|
|
101
|
+
const reuseMs = options.reuseMs ?? 30_000;
|
|
102
|
+
const now = options.now ?? (() => Date.now());
|
|
103
|
+
let current = null;
|
|
104
|
+
function shared() {
|
|
105
|
+
if (current && now() - current.startedAt < reuseMs)
|
|
106
|
+
return current.promise;
|
|
107
|
+
const entry = { promise: build(), startedAt: now() };
|
|
108
|
+
current = entry;
|
|
109
|
+
// A failed read, a missing journal and unreadable memories are not reused: the owner may fix them now.
|
|
110
|
+
const forget = () => { if (current === entry)
|
|
111
|
+
current = null; };
|
|
112
|
+
entry.promise.then((index) => {
|
|
113
|
+
if (index.status === 'unavailable' && (index.reason === 'journal_unavailable' || index.reason === 'personal_kg_unavailable'))
|
|
114
|
+
forget();
|
|
115
|
+
}, forget);
|
|
116
|
+
return entry.promise;
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
async judgmentsUsing(id, limit) {
|
|
120
|
+
const index = await shared();
|
|
121
|
+
if (index.status !== 'ok')
|
|
122
|
+
return index;
|
|
123
|
+
const judgments = index.byMemory.get(id) ?? [];
|
|
124
|
+
return { status: 'ok', items: judgments.slice(0, Math.max(0, limit)), total: judgments.length };
|
|
125
|
+
},
|
|
126
|
+
async counts() {
|
|
127
|
+
const index = await shared();
|
|
128
|
+
if (index.status !== 'ok')
|
|
129
|
+
return index;
|
|
130
|
+
return { status: 'ok', counts: Object.fromEntries([...index.byMemory].map(([id, judgments]) => [id, judgments.length])) };
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
const DEFAULT_JUDGMENT_LIMIT = 5;
|
|
135
|
+
const MAX_JUDGMENT_LIMIT = 500;
|
|
136
|
+
/**
|
|
137
|
+
* GET routes under `/api/personal-memory`. `authorize` runs before any read
|
|
138
|
+
* and throws to refuse the request (the local host requires its launch token
|
|
139
|
+
* on these reads, not only on writes).
|
|
140
|
+
*/
|
|
141
|
+
export function createPersonalMemoryHttpHandler(options) {
|
|
142
|
+
const basePath = (options.basePath ?? PERSONAL_MEMORY_HTTP_PREFIX).replace(/\/+$/u, '');
|
|
143
|
+
return async (request, response) => {
|
|
144
|
+
const url = requestUrl(request);
|
|
145
|
+
if (url.pathname !== basePath && !url.pathname.startsWith(`${basePath}/`))
|
|
146
|
+
return false;
|
|
147
|
+
if (request.method !== 'GET') {
|
|
148
|
+
writeJson(response, 405, { error: { code: 'method_not_allowed', message: 'Use GET' } });
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
options.authorize(request);
|
|
152
|
+
const unavailable = (result) => writeJson(response, 503, { status: 'unavailable', reason: result.reason });
|
|
153
|
+
if (url.pathname === basePath) {
|
|
154
|
+
if (url.searchParams.get('view') === 'judgment-counts') {
|
|
155
|
+
const counts = await options.index.counts();
|
|
156
|
+
if (counts.status === 'ok')
|
|
157
|
+
writeJson(response, 200, counts);
|
|
158
|
+
else
|
|
159
|
+
unavailable(counts);
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
const list = await options.readMemories();
|
|
163
|
+
if (list.status === 'ok')
|
|
164
|
+
writeJson(response, 200, { status: 'ok', items: list.items, next_cursor: null });
|
|
165
|
+
else
|
|
166
|
+
unavailable(list);
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
const rest = url.pathname.slice(basePath.length + 1).split('/');
|
|
170
|
+
let id;
|
|
171
|
+
try {
|
|
172
|
+
id = decodeURIComponent(rest[0] ?? '');
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
writeJson(response, 400, { error: { code: 'invalid_id', message: 'The memory id is not valid' } });
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
if (!id || rest.length > 2 || (rest.length === 2 && rest[1] !== 'judgments')) {
|
|
179
|
+
writeJson(response, 404, { error: { code: 'not_found', message: 'Not found' } });
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
if (rest.length === 2) {
|
|
183
|
+
const requested = Number.parseInt(url.searchParams.get('limit') ?? '', 10);
|
|
184
|
+
const limit = Number.isInteger(requested) && requested > 0 ? Math.min(requested, MAX_JUDGMENT_LIMIT) : DEFAULT_JUDGMENT_LIMIT;
|
|
185
|
+
const judgments = await options.index.judgmentsUsing(id, limit);
|
|
186
|
+
if (judgments.status === 'ok')
|
|
187
|
+
writeJson(response, 200, judgments);
|
|
188
|
+
else
|
|
189
|
+
unavailable(judgments);
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
const list = await options.readMemories();
|
|
193
|
+
if (list.status !== 'ok') {
|
|
194
|
+
unavailable(list);
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
const item = list.items.find((candidate) => candidate.id === id);
|
|
198
|
+
if (item)
|
|
199
|
+
writeJson(response, 200, { status: 'ok', item });
|
|
200
|
+
else
|
|
201
|
+
writeJson(response, 404, { status: 'not_found' });
|
|
202
|
+
return true;
|
|
203
|
+
};
|
|
204
|
+
}
|
package/dist/ssot.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { type CanonicalGraphMigrationPlan } from './ontology-migration.js';
|
|
2
|
-
import type { PersonalOs } from './types.js';
|
|
2
|
+
import type { PersonalKgEntry, PersonalOs } from './types.js';
|
|
3
3
|
export declare function initializePersonalOs(dataDir: string): Promise<void>;
|
|
4
4
|
export declare function loadPersonalOs(dataDir: string): Promise<PersonalOs>;
|
|
5
|
+
/** Only `personal-kg.jsonl`, validated line by line as `loadPersonalOs` does. */
|
|
6
|
+
export declare function readPersonalKg(dataDir: string): Promise<PersonalKgEntry[]>;
|
|
5
7
|
export declare function mutatePersonalOs(dataDir: string, mutator: (current: PersonalOs) => PersonalOs | Promise<PersonalOs>): Promise<PersonalOs>;
|
|
6
8
|
export declare function mutatePersonalOsWithSidecar<T>(dataDir: string, sidecarPath: string, mutator: (current: PersonalOs, sidecarContent?: string) => {
|
|
7
9
|
next: PersonalOs;
|
package/dist/ssot.js
CHANGED
|
@@ -74,6 +74,10 @@ export async function loadPersonalOs(dataDir) {
|
|
|
74
74
|
return loadPersonalOsUnlocked(dataDir);
|
|
75
75
|
});
|
|
76
76
|
}
|
|
77
|
+
/** Only `personal-kg.jsonl`, validated line by line as `loadPersonalOs` does. */
|
|
78
|
+
export async function readPersonalKg(dataDir) {
|
|
79
|
+
return readJsonl(join(dataDir, 'personal-kg.jsonl'), personalKgSchema, 'personal-kg.jsonl');
|
|
80
|
+
}
|
|
77
81
|
export async function mutatePersonalOs(dataDir, mutator) {
|
|
78
82
|
return withSsotLock(dataDir, async () => {
|
|
79
83
|
await recoverTransactions(dataDir);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unson/brainbase-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.42.0",
|
|
4
4
|
"description": "Local-first Brainbase judgment OS that lets supported AI agents reuse a user's approved goals, priorities, decisions, and judgment criteria.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -295,6 +295,10 @@
|
|
|
295
295
|
"types": "./dist/streamable-http.d.ts",
|
|
296
296
|
"import": "./dist/streamable-http.js"
|
|
297
297
|
},
|
|
298
|
+
"./personal-memory": {
|
|
299
|
+
"types": "./dist/personal-memory.d.ts",
|
|
300
|
+
"import": "./dist/personal-memory.js"
|
|
301
|
+
},
|
|
298
302
|
"./meeting-minutes": {
|
|
299
303
|
"types": "./dist/meeting-minutes.d.ts",
|
|
300
304
|
"import": "./dist/meeting-minutes.js"
|
|
@@ -328,6 +332,9 @@
|
|
|
328
332
|
"./ui/world-model-view.css": "./ui/world-model-view.css",
|
|
329
333
|
"./ui/people-and-companies-view": "./ui/people-and-companies-view.js",
|
|
330
334
|
"./ui/people-and-companies-view.css": "./ui/people-and-companies-view.css",
|
|
335
|
+
"./ui/personal-memory": "./ui/personal-memory.js",
|
|
336
|
+
"./ui/personal-memory.css": "./ui/personal-memory.css",
|
|
337
|
+
"./ui/personal-memory-http-port": "./ui/personal-memory-http-port.js",
|
|
331
338
|
"./ui/area-nav": "./ui/area-nav.js",
|
|
332
339
|
"./ui/area-nav.css": "./ui/area-nav.css",
|
|
333
340
|
"./ui/local-web-shell": "./ui/local-web-shell.js",
|
|
@@ -487,5 +494,5 @@
|
|
|
487
494
|
"engines": {
|
|
488
495
|
"node": ">=20"
|
|
489
496
|
},
|
|
490
|
-
"gitHead": "
|
|
497
|
+
"gitHead": "35fc142733aac0f385f2d21e6d0651e1a9d8d7db"
|
|
491
498
|
}
|
package/ui/judgment-history.js
CHANGED
|
@@ -1148,12 +1148,45 @@ function appendJudgmentBasis(doc, references, labels) {
|
|
|
1148
1148
|
return wrap;
|
|
1149
1149
|
}
|
|
1150
1150
|
|
|
1151
|
-
function
|
|
1151
|
+
function personalMemoryExcerpt(value) {
|
|
1152
|
+
const body = String(value ?? '').replace(/\s+/gu, ' ').trim();
|
|
1153
|
+
return body.length > 60 ? `${body.slice(0, 60)}…` : body;
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
/**
|
|
1157
|
+
* The used memories the host resolved for this viewer: an opening and a link
|
|
1158
|
+
* to あなたの記憶. Only the owner's host resolves them; without that, or when
|
|
1159
|
+
* the lookup failed, this stays the count only.
|
|
1160
|
+
*/
|
|
1161
|
+
function ownerMemoryLinks(doc, references, memories) {
|
|
1162
|
+
if (!(memories instanceof Map)) return null;
|
|
1163
|
+
const found = [];
|
|
1164
|
+
for (const reference of Array.isArray(references) ? references : []) {
|
|
1165
|
+
if (judgmentReferenceKind(reference) !== PERSONAL_KNOWLEDGE_KIND) continue;
|
|
1166
|
+
const entry = memories.get(text(reference?.ref));
|
|
1167
|
+
if (entry?.status === 'found') found.push(entry);
|
|
1168
|
+
}
|
|
1169
|
+
if (found.length === 0) return null;
|
|
1170
|
+
const list = makeWorkspaceElement(doc, 'ul', { className: 'bb-jh-owner-memories', attrs: { 'aria-label': '使った記憶' } });
|
|
1171
|
+
for (const entry of found) {
|
|
1172
|
+
const item = makeWorkspaceElement(doc, 'li');
|
|
1173
|
+
item.append(makeWorkspaceElement(doc, 'a', { text: entry.excerpt, attrs: { href: entry.href } }));
|
|
1174
|
+
list.append(item);
|
|
1175
|
+
}
|
|
1176
|
+
return list;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
function appendOwnerJudgment(doc, references, memories = null) {
|
|
1152
1180
|
const { owner } = describeJudgmentBasis(references);
|
|
1153
1181
|
const wrap = makeWorkspaceElement(doc, 'div', { className: `bb-jh-owner is-${owner.state}` });
|
|
1154
1182
|
if (owner.state === 'applied') {
|
|
1155
1183
|
wrap.append(makeWorkspaceElement(doc, 'strong', { text: `あなたの判断の材料(個人KG)を${owner.count}件使いました。` }));
|
|
1156
|
-
|
|
1184
|
+
const links = ownerMemoryLinks(doc, references, memories);
|
|
1185
|
+
if (links) {
|
|
1186
|
+
wrap.append(makeWorkspaceElement(doc, 'p', { text: 'この判断には、あなたの考えが入っています。使った記憶はあなたにだけ見えています。' }), links);
|
|
1187
|
+
} else {
|
|
1188
|
+
wrap.append(makeWorkspaceElement(doc, 'p', { text: 'この判断には、あなたの考えが入っています。個人KGの中身はここには出しません。' }));
|
|
1189
|
+
}
|
|
1157
1190
|
} else if (owner.state === 'not_applied') {
|
|
1158
1191
|
wrap.append(makeWorkspaceElement(doc, 'strong', { text: 'あなたの判断の材料(個人KG)は使っていません。' }));
|
|
1159
1192
|
wrap.append(makeWorkspaceElement(doc, 'p', { text: 'Graphの哲学・目的・世界モデルと、AIの判断で進めました。' }));
|
|
@@ -1467,7 +1500,7 @@ function renderNormalRail(doc, rail, row, detail, onClose, options = {}) {
|
|
|
1467
1500
|
}));
|
|
1468
1501
|
rail.append(workspaceRailBlock(doc, {
|
|
1469
1502
|
title: '本人の考え',
|
|
1470
|
-
content: appendOwnerJudgment(doc, record.selectedReferences),
|
|
1503
|
+
content: appendOwnerJudgment(doc, record.selectedReferences, options.personalMemories),
|
|
1471
1504
|
}));
|
|
1472
1505
|
rail.append(workspaceRailBlock(doc, {
|
|
1473
1506
|
title: '判断の理由',
|
|
@@ -1907,6 +1940,10 @@ export function createJudgmentHistoryUI({
|
|
|
1907
1940
|
// Optional host port: (references: {ref, kind}[]) => Promise<Map|object of ref -> name>.
|
|
1908
1941
|
// The host resolves names under the viewer's current permission.
|
|
1909
1942
|
resolveReferenceLabels = null,
|
|
1943
|
+
// Optional owner-only port: (refs: string[]) => Promise<Map|object of ref -> { excerpt, href }>.
|
|
1944
|
+
// A host passes it only to the owner; the 本人の考え block then shows the
|
|
1945
|
+
// opening of each used memory with a link to あなたの記憶.
|
|
1946
|
+
resolvePersonalMemory = null,
|
|
1910
1947
|
} = {}) {
|
|
1911
1948
|
if (!root) throw new TypeError('root is required');
|
|
1912
1949
|
const doc = safeDocument(explicitDocument);
|
|
@@ -1946,6 +1983,7 @@ export function createJudgmentHistoryUI({
|
|
|
1946
1983
|
skeletonTimer: null,
|
|
1947
1984
|
slowTimer: null,
|
|
1948
1985
|
labels: new Map(),
|
|
1986
|
+
personalMemories: new Map(),
|
|
1949
1987
|
};
|
|
1950
1988
|
|
|
1951
1989
|
let filterPopover = null;
|
|
@@ -2254,6 +2292,37 @@ export function createJudgmentHistoryUI({
|
|
|
2254
2292
|
void load({ append: false });
|
|
2255
2293
|
}
|
|
2256
2294
|
|
|
2295
|
+
function requestPersonalMemories(rows) {
|
|
2296
|
+
if (typeof resolvePersonalMemory !== 'function') return;
|
|
2297
|
+
const pending = [];
|
|
2298
|
+
for (const row of rows) {
|
|
2299
|
+
if (row.mode !== 'normal' || !Array.isArray(row.references)) continue;
|
|
2300
|
+
for (const reference of row.references) {
|
|
2301
|
+
const ref = text(reference?.ref);
|
|
2302
|
+
if (!ref || judgmentReferenceKind(reference) !== PERSONAL_KNOWLEDGE_KIND || state.personalMemories.has(ref)) continue;
|
|
2303
|
+
state.personalMemories.set(ref, { status: 'loading' });
|
|
2304
|
+
pending.push(ref);
|
|
2305
|
+
}
|
|
2306
|
+
}
|
|
2307
|
+
if (!pending.length) return;
|
|
2308
|
+
const settle = (resolved) => {
|
|
2309
|
+
if (state.disposed) return;
|
|
2310
|
+
const lookup = resolved instanceof Map
|
|
2311
|
+
? (ref) => resolved.get(ref)
|
|
2312
|
+
: isRecord(resolved) ? (ref) => resolved[ref] : () => null;
|
|
2313
|
+
for (const ref of pending) {
|
|
2314
|
+
const entry = lookup(ref);
|
|
2315
|
+
const excerpt = isRecord(entry) ? personalMemoryExcerpt(entry.excerpt) : '';
|
|
2316
|
+
const href = isRecord(entry) ? text(entry.href) : '';
|
|
2317
|
+
state.personalMemories.set(ref, excerpt && href ? { status: 'found', excerpt, href } : { status: 'missing' });
|
|
2318
|
+
}
|
|
2319
|
+
render();
|
|
2320
|
+
};
|
|
2321
|
+
Promise.resolve()
|
|
2322
|
+
.then(() => resolvePersonalMemory([...pending]))
|
|
2323
|
+
.then(settle, () => settle(null));
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2257
2326
|
function requestReferenceLabels(rows) {
|
|
2258
2327
|
const pending = [];
|
|
2259
2328
|
for (const row of rows) {
|
|
@@ -2440,6 +2509,7 @@ export function createJudgmentHistoryUI({
|
|
|
2440
2509
|
? 'この期間に取得できた判断履歴はありません。全件数が未確認のため0件とは確認できません。'
|
|
2441
2510
|
: 'この期間の判断履歴はありません。';
|
|
2442
2511
|
requestReferenceLabels(aggregate.rows);
|
|
2512
|
+
requestPersonalMemories(aggregate.rows);
|
|
2443
2513
|
const dataRows = makeRows(doc, aggregate, state.selectedKey, (key) => {
|
|
2444
2514
|
state.lastFocusedKey = key;
|
|
2445
2515
|
state.selectedKey = key;
|
|
@@ -2507,6 +2577,7 @@ export function createJudgmentHistoryUI({
|
|
|
2507
2577
|
onSubmitFeedback: (controls) => void submitFeedback(selected, controls),
|
|
2508
2578
|
onCopyReference: (payload) => void copyReference(payload),
|
|
2509
2579
|
labels: state.labels,
|
|
2580
|
+
personalMemories: state.personalMemories,
|
|
2510
2581
|
}
|
|
2511
2582
|
: {};
|
|
2512
2583
|
if (nativeDrawer) {
|