@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.
@@ -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. */
@@ -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.41.13",
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": "fcfea3958913012579029adf0bf785c45643c77b"
497
+ "gitHead": "35fc142733aac0f385f2d21e6d0651e1a9d8d7db"
491
498
  }
@@ -1148,12 +1148,45 @@ function appendJudgmentBasis(doc, references, labels) {
1148
1148
  return wrap;
1149
1149
  }
1150
1150
 
1151
- function appendOwnerJudgment(doc, references) {
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
- wrap.append(makeWorkspaceElement(doc, 'p', { text: 'この判断には、あなたの考えが入っています。個人KGの中身はここには出しません。' }));
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) {