@unson/brainbase-mcp 0.21.1 → 0.22.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.
Files changed (37) hide show
  1. package/dist/local-web-fetch-bridge.js +1 -1
  2. package/dist/local-web-host.d.ts +59 -0
  3. package/dist/local-web-host.js +171 -4
  4. package/dist/meeting-minutes-http.d.ts +22 -0
  5. package/dist/meeting-minutes-http.js +252 -0
  6. package/dist/meeting-minutes-lineage-adapters.d.ts +157 -0
  7. package/dist/meeting-minutes-lineage-adapters.js +410 -0
  8. package/dist/meeting-minutes-lineage-http.d.ts +20 -0
  9. package/dist/meeting-minutes-lineage-http.js +221 -0
  10. package/dist/meeting-minutes-lineage-lifecycle.d.ts +58 -0
  11. package/dist/meeting-minutes-lineage-lifecycle.js +146 -0
  12. package/dist/meeting-minutes-lineage.d.ts +307 -0
  13. package/dist/meeting-minutes-lineage.js +1030 -0
  14. package/dist/meeting-minutes-storage-controller.d.ts +182 -0
  15. package/dist/meeting-minutes-storage-controller.js +366 -0
  16. package/dist/meeting-minutes-storage-http.d.ts +25 -0
  17. package/dist/meeting-minutes-storage-http.js +312 -0
  18. package/dist/meeting-minutes-storage.d.ts +169 -0
  19. package/dist/meeting-minutes-storage.js +402 -0
  20. package/dist/meeting-minutes.d.ts +223 -0
  21. package/dist/meeting-minutes.js +1172 -0
  22. package/dist/world-extension.js +1 -1
  23. package/package.json +46 -3
  24. package/ui/brainbase-tokens.css +35 -35
  25. package/ui/local-web-shell.css +20 -17
  26. package/ui/local-web-shell.js +34 -1
  27. package/ui/meeting-minutes-lineage-http.js +247 -0
  28. package/ui/meeting-minutes-lineage.css +177 -0
  29. package/ui/meeting-minutes-lineage.js +485 -0
  30. package/ui/meeting-minutes-storage.css +170 -0
  31. package/ui/meeting-minutes-storage.js +388 -0
  32. package/ui/meeting-minutes.css +215 -0
  33. package/ui/meeting-minutes.js +622 -0
  34. package/ui/world/world-district.js +306 -87
  35. package/ui/world/world-scenery.js +208 -0
  36. package/ui/world/world-view.css +54 -0
  37. package/ui/world/world-view.js +65 -205
@@ -347,7 +347,7 @@ export function createWorldExtension(options = {}) {
347
347
  return {
348
348
  id: WORLD_EXTENSION_ID,
349
349
  uiDir: fileURLToPath(new URL('../ui/world/', import.meta.url)),
350
- uiFiles: ['world-view.js', 'world-view.css', 'world-vendor.js', 'world-placement.js', 'world-work-rail.js', 'world-district.js'],
350
+ uiFiles: ['world-view.js', 'world-view.css', 'world-vendor.js', 'world-placement.js', 'world-work-rail.js', 'world-district.js', 'world-scenery.js'],
351
351
  screenEntry: 'world-view.js',
352
352
  // P17: the world sits above the home and opens first.
353
353
  navPosition: 'first',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unson/brainbase-mcp",
3
- "version": "0.21.1",
3
+ "version": "0.22.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",
@@ -115,6 +115,22 @@
115
115
  "types": "./dist/company-os-learning-adoption.d.ts",
116
116
  "import": "./dist/company-os-learning-adoption.js"
117
117
  },
118
+ "./meeting-minutes-lineage": {
119
+ "types": "./dist/meeting-minutes-lineage.d.ts",
120
+ "import": "./dist/meeting-minutes-lineage.js"
121
+ },
122
+ "./meeting-minutes-lineage-adapters": {
123
+ "types": "./dist/meeting-minutes-lineage-adapters.d.ts",
124
+ "import": "./dist/meeting-minutes-lineage-adapters.js"
125
+ },
126
+ "./meeting-minutes-lineage-http": {
127
+ "types": "./dist/meeting-minutes-lineage-http.d.ts",
128
+ "import": "./dist/meeting-minutes-lineage-http.js"
129
+ },
130
+ "./meeting-minutes-lineage-lifecycle": {
131
+ "types": "./dist/meeting-minutes-lineage-lifecycle.d.ts",
132
+ "import": "./dist/meeting-minutes-lineage-lifecycle.js"
133
+ },
118
134
  "./constraint-resolution": {
119
135
  "types": "./dist/constraint-resolution.d.ts",
120
136
  "import": "./dist/constraint-resolution.js"
@@ -211,6 +227,22 @@
211
227
  "types": "./dist/streamable-http.d.ts",
212
228
  "import": "./dist/streamable-http.js"
213
229
  },
230
+ "./meeting-minutes": {
231
+ "types": "./dist/meeting-minutes.d.ts",
232
+ "import": "./dist/meeting-minutes.js"
233
+ },
234
+ "./meeting-minutes-http": {
235
+ "types": "./dist/meeting-minutes-http.d.ts",
236
+ "import": "./dist/meeting-minutes-http.js"
237
+ },
238
+ "./meeting-minutes-storage": {
239
+ "types": "./dist/meeting-minutes-storage.d.ts",
240
+ "import": "./dist/meeting-minutes-storage.js"
241
+ },
242
+ "./meeting-minutes-storage-controller": {
243
+ "types": "./dist/meeting-minutes-storage-controller.d.ts",
244
+ "import": "./dist/meeting-minutes-storage-controller.js"
245
+ },
214
246
  "./ui/outcome-knowledge": "./ui/outcome-knowledge.js",
215
247
  "./ui/outcome-knowledge.css": "./ui/outcome-knowledge.css",
216
248
  "./ui/judgment-view": "./ui/judgment-view.js",
@@ -221,6 +253,8 @@
221
253
  "./ui/value-proof-review.css": "./ui/value-proof-review.css",
222
254
  "./ui/judgment-history": "./ui/judgment-history.js",
223
255
  "./ui/judgment-history.css": "./ui/judgment-history.css",
256
+ "./ui/meeting-minutes": "./ui/meeting-minutes.js",
257
+ "./ui/meeting-minutes.css": "./ui/meeting-minutes.css",
224
258
  "./ui/objective-editor-http-port": "./ui/objective-editor-http-port.js",
225
259
  "./ui/world-model-view": "./ui/world-model-view.js",
226
260
  "./ui/world-model-view.css": "./ui/world-model-view.css",
@@ -238,7 +272,12 @@
238
272
  "./ui/graph-projects-view.css": "./ui/graph-projects-view.css",
239
273
  "./ui/graph-registry-view": "./ui/graph-registry-view.js",
240
274
  "./ui/graph-registry-view.css": "./ui/graph-registry-view.css",
275
+ "./ui/meeting-minutes-storage": "./ui/meeting-minutes-storage.js",
276
+ "./ui/meeting-minutes-storage.css": "./ui/meeting-minutes-storage.css",
241
277
  "./ui/brainbase-tokens.css": "./ui/brainbase-tokens.css",
278
+ "./ui/meeting-minutes-lineage": "./ui/meeting-minutes-lineage.js",
279
+ "./ui/meeting-minutes-lineage.css": "./ui/meeting-minutes-lineage.css",
280
+ "./ui/meeting-minutes-lineage-http": "./ui/meeting-minutes-lineage-http.js",
242
281
  "./dist/*": "./dist/*",
243
282
  "./contracts/judgment-dag/schema.json": "./contracts/judgment-dag/schema.json",
244
283
  "./contracts/judgment-dag/fixture.json": "./contracts/judgment-dag/fixture.json",
@@ -292,7 +331,11 @@
292
331
  "./ui/project-overview": "./ui/project-overview.js",
293
332
  "./ui/project-graph": "./ui/project-graph.js",
294
333
  "./ui/project-workspace.css": "./ui/project-workspace.css",
295
- "./ui/project-graph-vendor.js": "./ui/project-graph-vendor.js"
334
+ "./ui/project-graph-vendor.js": "./ui/project-graph-vendor.js",
335
+ "./meeting-minutes-storage-http": {
336
+ "types": "./dist/meeting-minutes-storage-http.d.ts",
337
+ "import": "./dist/meeting-minutes-storage-http.js"
338
+ }
296
339
  },
297
340
  "bin": {
298
341
  "brainbase-mcp": "dist/index.js",
@@ -364,5 +407,5 @@
364
407
  "engines": {
365
408
  "node": ">=20"
366
409
  },
367
- "gitHead": "5bbdd5ef6feb5b037f1f1d79c38818db2c3e23a6"
410
+ "gitHead": "4933fdd273d050d9fa76f46b5d3696c1657613eb"
368
411
  }
@@ -7,47 +7,47 @@
7
7
  * truth for every Brainbase Web surface.
8
8
  */
9
9
  :root {
10
- --bb-color-ink: #1e2822;
11
- --bb-color-muted: #69746d;
12
- --bb-color-quiet: #929b95;
13
- --bb-color-border: #dce3dd;
14
- --bb-color-border-strong: #b7c3ba;
15
- --bb-color-control-border: #cbd8d0;
16
- --bb-color-paper: #fbfcfa;
17
- --bb-color-canvas: #f1f3f0;
10
+ --bb-color-ink: #202124;
11
+ --bb-color-muted: #6b6b73;
12
+ --bb-color-quiet: #76767f;
13
+ --bb-color-border: #e5e5e9;
14
+ --bb-color-border-strong: #c9c9d1;
15
+ --bb-color-control-border: #d7d7de;
16
+ --bb-color-paper: #ffffff;
17
+ --bb-color-canvas: #f6f6f8;
18
18
  --bb-color-panel: #ffffff;
19
- --bb-color-surface: #f5f8f5;
20
- --bb-color-header-start: #f7faf8;
21
- --bb-color-header-end: #eef5f0;
22
- --bb-color-eyebrow: #47715a;
23
- --bb-color-accent: #087d62;
24
- --bb-color-accent-soft: #e6f2ec;
25
- --bb-color-accent-strong: #176448;
26
- --bb-color-action: #35684c;
27
- --bb-color-action-hover: #2a5540;
19
+ --bb-color-surface: #f6f6f8;
20
+ --bb-color-header-start: #ffffff;
21
+ --bb-color-header-end: #f6f6f8;
22
+ --bb-color-eyebrow: #5b4bd8;
23
+ --bb-color-accent: #5b4bd8;
24
+ --bb-color-accent-soft: #efedff;
25
+ --bb-color-accent-strong: #4939b8;
26
+ --bb-color-action: #5b4bd8;
27
+ --bb-color-action-hover: #4939b8;
28
28
  --bb-color-action-text: #ffffff;
29
- --bb-color-focus: #20a37e;
30
- --bb-color-project-ink: #172d4f;
31
- --bb-color-project-muted: #566b87;
32
- --bb-color-project-border: #dce4ee;
33
- --bb-color-project-border-strong: #c8d4e2;
34
- --bb-color-project-accent: #1261ad;
35
- --bb-color-project-accent-strong: #194f8a;
36
- --bb-color-success: #087d62;
37
- --bb-color-success-soft: #e6f2ec;
29
+ --bb-color-focus: #5b4bd8;
30
+ --bb-color-project-ink: #202124;
31
+ --bb-color-project-muted: #6b6b73;
32
+ --bb-color-project-border: #e5e5e9;
33
+ --bb-color-project-border-strong: #c9c9d1;
34
+ --bb-color-project-accent: #5b4bd8;
35
+ --bb-color-project-accent-strong: #4939b8;
36
+ --bb-color-success: #18794e;
37
+ --bb-color-success-soft: #e4f5eb;
38
38
  --bb-color-warning: #8a631d;
39
39
  --bb-color-warning-soft: #fff0c7;
40
40
  --bb-color-danger: #d92335;
41
41
  --bb-color-danger-soft: #fde9eb;
42
- --bb-color-rail: #171c19;
43
- --bb-color-rail-border: #2c332e;
44
- --bb-color-rail-text: #d5ded8;
45
- --bb-color-rail-muted: #89938c;
46
- --bb-color-rail-hover: #232c26;
47
- --bb-color-rail-active: #203e33;
48
- --bb-color-rail-strong: #ffffff;
49
- --bb-color-overlay: rgb(8 15 11 / 40%);
50
- --bb-shadow: 0 16px 42px rgb(13 24 17 / 12%);
42
+ --bb-color-rail: #ffffff;
43
+ --bb-color-rail-border: #e5e5e9;
44
+ --bb-color-rail-text: #202124;
45
+ --bb-color-rail-muted: #6b6b73;
46
+ --bb-color-rail-hover: #f6f6f8;
47
+ --bb-color-rail-active: #efedff;
48
+ --bb-color-rail-strong: #202124;
49
+ --bb-color-overlay: rgb(32 33 36 / 40%);
50
+ --bb-shadow: 0 16px 42px rgb(32 33 36 / 10%);
51
51
  --bb-font-sans: Geist, "Noto Sans JP", "Hiragino Sans", system-ui, sans-serif;
52
52
  --bb-font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;
53
53
  --bb-radius-sm: 7px;
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Shell of the local Web host, in the organization edition's layout: a dark
2
+ * Shell of the local Web host, in the organization edition's layout: a white
3
3
  * left rail (brand, navigation, source) and a paper workspace. Uses only the
4
4
  * shared tokens in brainbase-tokens.css.
5
5
  */
@@ -51,15 +51,15 @@ body {
51
51
  flex-direction: column;
52
52
  overflow: auto;
53
53
  border-right: 1px solid var(--bb-color-rail-border);
54
- padding: 28px 12px 12px;
54
+ padding: 18px 12px 14px;
55
55
  background: var(--bb-color-rail);
56
56
  color: var(--bb-color-rail-text);
57
57
  }
58
58
 
59
59
  .bb-shell-brand {
60
- padding: 0 12px 28px;
60
+ padding: 0 10px 22px;
61
61
  color: var(--bb-color-rail-strong);
62
- font-size: 24px;
62
+ font-size: 19px;
63
63
  font-weight: 780;
64
64
  line-height: 1;
65
65
  letter-spacing: -.035em;
@@ -67,17 +67,17 @@ body {
67
67
 
68
68
  .bb-shell-nav {
69
69
  display: grid;
70
- gap: 4px;
70
+ gap: 2px;
71
71
  }
72
72
 
73
73
  .bb-shell-nav-link {
74
- min-height: 42px;
74
+ min-height: 38px;
75
75
  display: flex;
76
76
  align-items: center;
77
- gap: 12px;
78
- border-radius: 8px;
79
- padding: 0 12px;
80
- color: var(--bb-color-rail-text);
77
+ gap: 11px;
78
+ border-radius: 7px;
79
+ padding: 0 10px;
80
+ color: var(--bb-color-rail-muted);
81
81
  font-size: 13px;
82
82
  font-weight: 620;
83
83
  text-decoration: none;
@@ -90,8 +90,8 @@ body {
90
90
 
91
91
  .bb-shell-nav-link[aria-current="page"] {
92
92
  background: var(--bb-color-rail-active);
93
- color: var(--bb-color-rail-strong);
94
- box-shadow: inset 3px 0 var(--bb-color-accent);
93
+ color: var(--bb-color-accent);
94
+ box-shadow: none;
95
95
  }
96
96
 
97
97
  .bb-shell-icon {
@@ -137,9 +137,12 @@ body {
137
137
  font-size: 11px;
138
138
  }
139
139
 
140
- .bb-shell-fact.is-warning .bb-shell-fact-value,
140
+ .bb-shell-fact.is-warning .bb-shell-fact-value {
141
+ color: var(--bb-color-warning);
142
+ }
143
+
141
144
  .bb-shell-source .is-danger {
142
- color: var(--bb-color-warning-soft);
145
+ color: var(--bb-color-danger);
143
146
  }
144
147
 
145
148
  .bb-shell-source .bb-shell-link-button {
@@ -150,7 +153,7 @@ body {
150
153
 
151
154
  /* Right detail rail (the organization edition's decision rail) */
152
155
  .bb-shell.has-rail {
153
- grid-template-columns: 240px minmax(0, 1fr) 380px;
156
+ grid-template-columns: 240px minmax(0, 1fr) 360px;
154
157
  }
155
158
 
156
159
  .bb-shell-rail {
@@ -160,7 +163,7 @@ body {
160
163
  min-width: 0;
161
164
  overflow: auto;
162
165
  border-left: 1px solid var(--bb-color-border);
163
- padding: 44px 30px 48px;
166
+ padding: 28px 22px 36px;
164
167
  background: var(--bb-color-surface);
165
168
  }
166
169
 
@@ -194,7 +197,7 @@ body {
194
197
  max-width: 1440px;
195
198
  min-width: 0;
196
199
  margin: 0 auto;
197
- padding: 40px 48px 72px;
200
+ padding: 20px 24px 52px;
198
201
  }
199
202
 
200
203
  .bb-shell-objectives,
@@ -20,6 +20,9 @@ import { createGraphRegistryView } from './graph-registry-view.js';
20
20
  import { createObjectiveEditorController } from './objective-editor.js';
21
21
  import { createObjectiveEditorHttpPort } from './objective-editor-http-port.js';
22
22
  import { createJudgmentHistoryUI } from './judgment-history.js';
23
+ import { createMeetingMinutesUI } from './meeting-minutes.js';
24
+ import { createMeetingMinutesStorageExtension } from './meeting-minutes-storage.js';
25
+ import { createMeetingMinutesLineageExtension } from './meeting-minutes-lineage-http.js';
23
26
  import { createWorldModelView } from './world-model-view.js';
24
27
  import { createGraphClient } from './graph-view-shared.js';
25
28
 
@@ -43,6 +46,7 @@ const NAV_ICON_PATHS = Object.freeze({
43
46
  objectives: ['M12 4 3 20h18z', 'M12 9v5m0 3h.01'],
44
47
  projects: ['M3 6.5h7l2 2h9v10H3z', 'M3 6.5v-2h7l2 2'],
45
48
  graph: ['M10 13a5 5 0 0 0 7.5.5l2-2a5 5 0 0 0-7-7l-1.2 1.2', 'M14 11a5 5 0 0 0-7.5-.5l-2 2a5 5 0 0 0 7 7l1.2-1.2'],
49
+ minutes: ['M6 3.5h9l3 3V20.5H6z', 'M15 3.5v4h3', 'M9 12h6m-6 3h6'],
46
50
  // World view (extension id `world`, ledger P17).
47
51
  world: ['M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18z', 'M3 12h18', 'M12 3c2.5 2.6 3.8 5.6 3.8 9s-1.3 6.4-3.8 9c-2.5-2.6-3.8-5.6-3.8-9S9.5 5.6 12 3z'],
48
52
  });
@@ -156,6 +160,32 @@ function mountToday(container, context) {
156
160
  });
157
161
  }
158
162
 
163
+ function mountMeetingMinutes(container, context) {
164
+ const versionActionExtensions = [];
165
+ if (context.meetingMinutesStorage) versionActionExtensions.push(createMeetingMinutesStorageExtension({
166
+ fetcher: context.fetcher,
167
+ token: context.token,
168
+ basePath: '/api/meeting-minutes',
169
+ defaultExternal: context.meetingMinutesStorage.defaultExternal,
170
+ }));
171
+ if (context.meetingMinutesLineage) versionActionExtensions.push(createMeetingMinutesLineageExtension({
172
+ fetcher: context.fetcher,
173
+ token: context.token,
174
+ basePath: '/api/meeting-minutes-lineage',
175
+ candidateDefaults: context.meetingMinutesLineage.candidateDefaults,
176
+ }));
177
+ return createMeetingMinutesUI({
178
+ root: makePage(container, context),
179
+ rail: context.rail,
180
+ page: context.page,
181
+ document: context.document,
182
+ fetcher: context.fetcher,
183
+ token: context.token,
184
+ basePath: '/api/meeting-minutes',
185
+ versionActionExtensions,
186
+ });
187
+ }
188
+
159
189
  function renderUnreadableObjectives(doc, slot, payload) {
160
190
  slot.replaceChildren();
161
191
  const count = Number.isInteger(payload?.unreadable?.count) ? payload.unreadable.count : 0;
@@ -221,6 +251,7 @@ function mountGraphScreen(screenId, createView) {
221
251
 
222
252
  export const LOCAL_WEB_SCREENS = Object.freeze([
223
253
  Object.freeze({ id: 'today', label: '今日', usesGraph: false, rail: true, source: '判断の記録', mount: mountToday }),
254
+ Object.freeze({ id: 'minutes', label: '議事録', usesGraph: false, rail: true, source: '正本の議事録', mount: mountMeetingMinutes }),
224
255
  Object.freeze({ id: 'objectives', label: '目的と現状', usesGraph: true, rail: true, source: '手元のGraph', mount: mountObjectives }),
225
256
  Object.freeze({ id: 'projects', label: 'プロジェクトと関係者', usesGraph: true, rail: true, source: '手元のGraph', mount: mountGraphScreen('projects', createGraphProjectsView) }),
226
257
  Object.freeze({ id: 'graph', label: '情報と関係', usesGraph: true, rail: true, source: '手元のGraph', mount: mountGraphScreen('graph', createGraphRegistryView) }),
@@ -305,6 +336,8 @@ export function createLocalWebShell({
305
336
  statusPath = '/api/local/status',
306
337
  screens = LOCAL_WEB_SCREENS,
307
338
  initialScreen,
339
+ meetingMinutesStorage = null,
340
+ meetingMinutesLineage = null,
308
341
  } = {}) {
309
342
  if (!root) throw new TypeError('root is required');
310
343
  const doc = explicitDocument ?? (typeof document === 'undefined' ? null : document);
@@ -312,7 +345,7 @@ export function createLocalWebShell({
312
345
  const request = typeof fetcher === 'function'
313
346
  ? fetcher
314
347
  : typeof globalThis.fetch === 'function' ? (path, init) => globalThis.fetch(path, init) : null;
315
- const context = Object.freeze({ document: doc, fetcher: request, token });
348
+ const context = Object.freeze({ document: doc, fetcher: request, token, meetingMinutesStorage, meetingMinutesLineage });
316
349
 
317
350
  const status = { phase: 'loading', data: null, error: null };
318
351
  const mounted = new Map();
@@ -0,0 +1,247 @@
1
+ import { createMeetingMinutesLineageView } from './meeting-minutes-lineage.js';
2
+
3
+ /** Browser-side transport and host action-slot bridge for the lineage screen. */
4
+ export const MEETING_MINUTES_LINEAGE_HTTP_UI_CONTRACT_VERSION = 'brainbase.meeting-minutes-lineage-http-ui.v1';
5
+
6
+ export function createMeetingMinutesLineageHttpActions({
7
+ reference,
8
+ basePath = '/api/meeting-minutes-lineage',
9
+ fetcher = globalThis.fetch,
10
+ token,
11
+ actorFor,
12
+ idFor,
13
+ idempotencyKeyFor,
14
+ } = {}) {
15
+ if (!reference || typeof reference !== 'object') throw new TypeError('reference is required');
16
+ if (typeof fetcher !== 'function') throw new TypeError('fetcher is required');
17
+ const auditActorFor = typeof actorFor === 'function' ? actorFor : () => ({ type: 'person', id: 'browser-ui' });
18
+ const operationIds = new Map();
19
+ const operationId = (candidate, action) => {
20
+ const stable = candidate && typeof candidate === 'object'
21
+ ? (candidate.draftId ?? candidate.id ?? candidate.candidateId ?? candidate.candidate?.id)
22
+ : null;
23
+ const key = `${action}:${typeof stable === 'string' && stable ? stable : 'operation'}`;
24
+ let id = operationIds.get(key);
25
+ if (!id) {
26
+ id = typeof globalThis.crypto?.randomUUID === 'function'
27
+ ? globalThis.crypto.randomUUID()
28
+ : `lineage-${Date.now()}-${Math.random().toString(36).slice(2)}`;
29
+ operationIds.set(key, id);
30
+ }
31
+ return id;
32
+ };
33
+ const operationIdFor = typeof idFor === 'function'
34
+ ? ({ candidate, action }) => {
35
+ const key = `${action}:${operationId(candidate, action)}`;
36
+ if (!operationIds.has(`id:${key}`)) operationIds.set(`id:${key}`, idFor({ candidate, action }));
37
+ return operationIds.get(`id:${key}`);
38
+ }
39
+ : ({ candidate, action }) => operationId(candidate, action);
40
+ const operationKeyFor = typeof idempotencyKeyFor === 'function'
41
+ ? ({ candidate, action }) => {
42
+ const key = `${action}:${operationId(candidate, action)}`;
43
+ if (!operationIds.has(`key:${key}`)) operationIds.set(`key:${key}`, idempotencyKeyFor({ candidate, action }));
44
+ return operationIds.get(`key:${key}`);
45
+ }
46
+ : ({ candidate, action }) => `meeting-minutes-lineage:${operationId(candidate, action)}`;
47
+ const prefix = String(basePath).replace(/\/+$/u, '');
48
+ const requestJson = async (path, body) => {
49
+ const headers = { 'Content-Type': 'application/json', Accept: 'application/json' };
50
+ if (token) headers['X-Brainbase-Review-Token'] = token;
51
+ const response = await fetcher(`${prefix}${path}`, {
52
+ method: 'POST',
53
+ headers,
54
+ body: JSON.stringify(body),
55
+ });
56
+ // The local shell's request helper returns parsed JSON, while a direct
57
+ // fetcher returns a Response. Support both so the extension remains a
58
+ // usable host action slot outside the default shell.
59
+ if (!response || typeof response.json !== 'function') {
60
+ if (response?.ok === false) {
61
+ const failure = new Error(`Lineage request failed (${response.status ?? 'unknown'})`);
62
+ failure.code = 'lineage_unavailable';
63
+ failure.status = response.status;
64
+ throw failure;
65
+ }
66
+ return response;
67
+ }
68
+ let payload = null;
69
+ try {
70
+ payload = await response.json();
71
+ } catch {
72
+ payload = null;
73
+ }
74
+ if (!response.ok) {
75
+ const error = payload?.error;
76
+ const failure = new Error(typeof error?.message === 'string' ? error.message : `Lineage request failed (${response.status})`);
77
+ failure.code = typeof error?.code === 'string' ? error.code : 'lineage_unavailable';
78
+ failure.status = response.status;
79
+ throw failure;
80
+ }
81
+ return payload;
82
+ };
83
+ const requestMutation = (candidate, action, path, extra = {}) => requestJson(path, {
84
+ id: operationIdFor({ candidate, action }),
85
+ idempotencyKey: operationKeyFor({ candidate, action }),
86
+ // This is retained as an audit hint only. The host resolves the actor
87
+ // from its verified context and ignores this field for authority.
88
+ actor: auditActorFor({ candidate, action }),
89
+ ...extra,
90
+ });
91
+ return {
92
+ load: () => requestJson('/by-version', { reference }),
93
+ actions: {
94
+ createCandidate: (input) => {
95
+ if (!input || (input.kind !== 'judgment' && input.kind !== 'task')) {
96
+ return Promise.reject(Object.assign(new Error('候補の種類を選んでください。'), { code: 'invalid_input' }));
97
+ }
98
+ const candidate = { ...input, draftId: input.draftId ?? operationId(input, 'create') };
99
+ return requestMutation(candidate, 'create', '/candidates', {
100
+ evidence: reference,
101
+ kind: candidate.kind,
102
+ proposal: candidate.proposal,
103
+ epistemicStatus: candidate.epistemicStatus ?? 'inferred',
104
+ });
105
+ },
106
+ confirmCandidate: (candidate) => requestMutation(candidate, 'confirm', `/candidates/${encodeURIComponent(candidate.id)}/confirm`, {
107
+ evidenceDigest: candidate.evidenceDigest,
108
+ }),
109
+ adoptJudgment: (candidate) => requestMutation(candidate, 'adopt_judgment', `/candidates/${encodeURIComponent(candidate.id)}/adopt/judgment`),
110
+ adoptTask: (candidate) => requestMutation(candidate, 'adopt_task', `/candidates/${encodeURIComponent(candidate.id)}/adopt/task`),
111
+ // Corrections are intentionally host-owned because they require an
112
+ // explicit replacement version and a human supplied reason.
113
+ },
114
+ };
115
+ }
116
+
117
+ /**
118
+ * Connect a host-owned shared UI action slot to the real HTTP loader/actions.
119
+ * The slot must mount the returned view; an absent slot is a wiring error,
120
+ * never a successful no-op.
121
+ */
122
+ export function connectMeetingMinutesLineageActionSlot({
123
+ slot,
124
+ root,
125
+ reference,
126
+ basePath,
127
+ fetcher,
128
+ token,
129
+ actorFor,
130
+ idFor,
131
+ idempotencyKeyFor,
132
+ candidateDefaults,
133
+ documentRef = globalThis.document,
134
+ } = {}) {
135
+ if (!slot || typeof slot.mount !== 'function') throw new TypeError('a host action slot with mount(view) is required');
136
+ const transport = createMeetingMinutesLineageHttpActions({
137
+ reference,
138
+ basePath,
139
+ fetcher,
140
+ token,
141
+ actorFor,
142
+ idFor,
143
+ idempotencyKeyFor,
144
+ });
145
+ const view = createMeetingMinutesLineageView({
146
+ root,
147
+ load: transport.load,
148
+ actions: transport.actions,
149
+ candidateDefaults,
150
+ documentRef,
151
+ });
152
+ slot.mount(view);
153
+ return view;
154
+ }
155
+
156
+ function text(value) {
157
+ return typeof value === 'string' && value.trim() ? value.trim() : null;
158
+ }
159
+
160
+ function exactVersionReference(version, detail, documentRecord) {
161
+ if (!version || !detail?.meeting?.meeting_id || !documentRecord?.minutes_id || !version.version_id) return null;
162
+ const source = version.source_ref;
163
+ if (source && text(source.provider) && text(source.locator) && text(source.revision) && text(source.digest)) {
164
+ return {
165
+ meetingId: detail.meeting.meeting_id,
166
+ minutesId: documentRecord.minutes_id,
167
+ versionId: version.version_id,
168
+ contentDigest: source.digest,
169
+ locator: source.locator,
170
+ provenance: {
171
+ providerKind: source.provider,
172
+ providerId: source.locator,
173
+ revision: source.revision,
174
+ digest: source.digest,
175
+ },
176
+ };
177
+ }
178
+ return {
179
+ meetingId: detail.meeting.meeting_id,
180
+ minutesId: documentRecord.minutes_id,
181
+ versionId: version.version_id,
182
+ contentDigest: text(version.body_digest),
183
+ locator: `${detail.meeting.meeting_id}/${documentRecord.minutes_id}/${version.version_id}`,
184
+ provenance: {
185
+ providerKind: 'brainbase.native',
186
+ providerId: `${detail.meeting.meeting_id}/${documentRecord.minutes_id}`,
187
+ revision: version.version_id,
188
+ ...(text(version.body_digest) ? { digest: text(version.body_digest) } : {}),
189
+ },
190
+ };
191
+ }
192
+
193
+ /** Add the real lineage transport/view to the same selected-version action slot. */
194
+ export function createMeetingMinutesLineageExtension({
195
+ basePath = '/api/meeting-minutes-lineage',
196
+ fetcher,
197
+ token,
198
+ actorFor,
199
+ idFor,
200
+ idempotencyKeyFor,
201
+ candidateDefaults = null,
202
+ } = {}) {
203
+ return Object.freeze({
204
+ id: 'meeting-minutes-lineage',
205
+ mount({ root, document: documentRef, detail, documentRecord, version, request, setStatus }) {
206
+ const reference = exactVersionReference(version, detail, documentRecord);
207
+ if (!reference) {
208
+ root.replaceChildren();
209
+ const notice = documentRef.createElement('p');
210
+ notice.className = 'bb-mml-notice';
211
+ notice.textContent = '選択した議事録版の根拠を確認できないため、判断・Taskを操作できません。';
212
+ root.appendChild(notice);
213
+ return null;
214
+ }
215
+ let view;
216
+ const slot = { mount(next) { view = next; } };
217
+ connectMeetingMinutesLineageActionSlot({
218
+ slot,
219
+ root,
220
+ reference,
221
+ basePath,
222
+ fetcher: fetcher ?? request,
223
+ token,
224
+ actorFor,
225
+ idFor,
226
+ idempotencyKeyFor,
227
+ documentRef,
228
+ candidateDefaults,
229
+ });
230
+ // `connect...` owns the view, while this extension owns the initial
231
+ // refresh and shares the core's status line for transport failures.
232
+ if (view) {
233
+ const originalRefresh = view.refresh;
234
+ view.refresh = async (...args) => {
235
+ try {
236
+ return await originalRefresh(...args);
237
+ } catch (error) {
238
+ setStatus?.(error instanceof Error ? error.message : '判断履歴を読み取れません。', 'error');
239
+ throw error;
240
+ }
241
+ };
242
+ void view.refresh();
243
+ }
244
+ return view;
245
+ },
246
+ });
247
+ }