@unson/brainbase-mcp 0.31.0 → 0.32.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unson/brainbase-mcp",
3
- "version": "0.31.0",
3
+ "version": "0.32.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",
@@ -455,5 +455,5 @@
455
455
  "engines": {
456
456
  "node": ">=20"
457
457
  },
458
- "gitHead": "aeb8467e65eda4223dd092cc43d69b4ebd040f42"
458
+ "gitHead": "3f91401cb7221305fe1dfafc6126482bc8b55170"
459
459
  }
@@ -146,6 +146,14 @@
146
146
  font-weight: 700;
147
147
  }
148
148
  .bb-world-canvas-ui .bb-world-canvas-quest-text { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
149
+ /* A quest with a way down (候補の確認) is clickable even though the quest list lets the map take the pointer. */
150
+ .bb-world-canvas-ui .bb-world-canvas-quest a {
151
+ flex: none;
152
+ color: #b5f2ab;
153
+ text-decoration: underline;
154
+ text-underline-offset: 2px;
155
+ pointer-events: auto;
156
+ }
149
157
 
150
158
  .bb-world-canvas-ui .bb-world-canvas-edge {
151
159
  position: absolute;
@@ -357,13 +357,14 @@ export function createWorldCanvasUI({ doc, stage, reducedMotion = false, onReset
357
357
  activate('help', help);
358
358
  focus(helpTitle);
359
359
  }
360
- function addNote({ label = '', text = '', tone = '', summary } = {}) {
360
+ function addNote({ label = '', text = '', tone = '', summary, link = null } = {}) {
361
361
  if (disposed) return;
362
362
  const note = element('section', 'bb-world-canvas-note');
363
363
  if (label) note.append(element('h3', '', label));
364
364
  if (text || summary) note.append(element('p', '', text || summary));
365
365
  if (label || text || summary) notes.append(note);
366
366
  if (typeof summary !== 'string' || !summary.trim()) return;
367
+ // A link (already checked by the caller) follows the summary so the quest is a way down, not only a sign.
367
368
  const quest = element('p', 'bb-world-canvas-quest');
368
369
  const icon = tone === 'attention' || tone === 'warning' ? '!' : 'i';
369
370
  quest.append(
@@ -373,6 +374,7 @@ export function createWorldCanvasUI({ doc, stage, reducedMotion = false, onReset
373
374
  // The accessible name contains the unabridged summary even when visual text
374
375
  // is ellipsized on a narrow canvas; the long explanation remains in help.
375
376
  quest.setAttribute('aria-label', summary.trim());
377
+ if (link?.href) quest.append(element('a', link.className ?? '', link.label ?? summary.trim(), { href: link.href }));
376
378
  quests.append(quest);
377
379
  }
378
380
  function clearNotes() {
@@ -40,6 +40,7 @@
40
40
  }
41
41
 
42
42
  .bb-world-hud-line.is-attention { border-left-color: var(--bb-color-danger); }
43
+ .bb-world-hud-line a { margin-left: 0.5em; color: inherit; text-decoration: underline; }
43
44
  .bb-world-hud-line.is-warning {
44
45
  border-left-color: var(--bb-color-warning);
45
46
  background: rgb(255 240 199 / 95%);
@@ -33,7 +33,7 @@ import { createWorldCanvasUI } from './world-canvas-ui.js';
33
33
  import { workCanvasNotes } from './world-canvas-notes.js';
34
34
  import { canvasTexture, drawFacade, FACADE_SIZES, FACADE_UNITS, gableRoof, hashUnit, muted, paintVertices, roundedPlate, valueNoise } from './world-scenery.js';
35
35
  import { WORK_STATES, workCityBlocks, workGrowthBlock, workPickBlock, workSign, workSiteBlocks } from './world-work-rail.js';
36
- import { exitLegend, exitsRailBlock, normalizeBusinessExits } from './world-exits.js';
36
+ import { exitHrefKind, exitLegend, exitsRailBlock, normalizeBusinessExits } from './world-exits.js';
37
37
  import {
38
38
  makeWorkspaceElement as el,
39
39
  workspacePageHeader,
@@ -1462,13 +1462,64 @@ function addressSelection() {
1462
1462
  * label?, as_of? } }] }`. `href` and `action.href` must be `https:` or a link inside the host (`/…`, `?…`);
1463
1463
  * others are dropped and counted. Without it the world is drawn exactly as before.
1464
1464
  * @typedef {{ status: string, read_at?: string, reason?: string, exits: object[] }} BusinessExitsAnswer
1465
+ * @param {() => (MemoryStatusAnswer | Promise<MemoryStatusAnswer>)} [options.memoryStatus] How the memory loop stands
1466
+ * (story-world-memory-status-v1, ledger P18): last night's organizing and the memory candidates that wait for the
1467
+ * owner. The answer is `{ status: 'available'|'partial'|'not_connected', night?: { status:
1468
+ * 'completed'|'partial'|'failed'|'missing', finished_at? } | null, candidates?: { count } | null, review_href? }`.
1469
+ * A part that is null was not read and is shown as unknown, never as zero. Without it the world says 未接続.
1470
+ * @typedef {{ status: string, night?: object | null, candidates?: object | null, review_href?: string }} MemoryStatusAnswer
1465
1471
  */
1472
+ async function readMemoryStatus(memoryStatus) {
1473
+ if (typeof memoryStatus !== 'function') return { status: 'not_connected' };
1474
+ try {
1475
+ const answer = await memoryStatus();
1476
+ return answer && typeof answer === 'object' ? answer : { status: 'failed', reason: 'invalid_answer' };
1477
+ } catch (error) {
1478
+ return { status: 'failed', reason: error instanceof Error ? error.message : 'request_failed' };
1479
+ }
1480
+ }
1481
+
1482
+ const NIGHT_STATES = {
1483
+ completed: { text: '済み', tone: 'info' },
1484
+ partial: { text: '一部だけ(確かめられていない範囲があります)', tone: 'warning' },
1485
+ failed: { text: 'できなかった(整理できなかった夜)', tone: 'attention' },
1486
+ missing: { text: '動いていない(整理できなかった夜)', tone: 'attention' },
1487
+ };
1488
+
1489
+ /** The memory loop as world notes (story-world-memory-status-v1). Unknown is never shown as zero. */
1490
+ function memoryLines(answer) {
1491
+ if (answer.status === 'not_connected') {
1492
+ return [{ text: '記憶の整理:未接続。この画面は夜の整理の結果と記憶の候補を受け取れません。候補の件数は0件とは確認できません。', tone: 'info', summary: '記憶の整理:未接続' }];
1493
+ }
1494
+ if (answer.status !== 'available' && answer.status !== 'partial') {
1495
+ return [{ text: `記憶の整理:読めない(${answer.reason ?? '理由不明'})。候補の件数は0件とは確認できません。`, tone: 'warning', summary: '記憶の整理:読めない' }];
1496
+ }
1497
+ const lines = [];
1498
+ const night = answer.night && NIGHT_STATES[answer.night.status];
1499
+ if (night) {
1500
+ const label = `昨夜の記憶の整理:${night.text}`;
1501
+ lines.push({ text: `${label}。`, tone: night.tone, summary: night.tone === 'attention' ? '整理できなかった夜' : label });
1502
+ } else {
1503
+ lines.push({ text: '昨夜の記憶の整理:読めない。', tone: 'warning', summary: '昨夜の記憶の整理:読めない' });
1504
+ }
1505
+ const count = answer.candidates?.count;
1506
+ if (Number.isInteger(count) && count >= 0) {
1507
+ const href = typeof answer.review_href === 'string' && exitHrefKind(answer.review_href) ? answer.review_href : null;
1508
+ const link = href && count > 0 ? { href, label: '候補の確認を開く', className: 'bb-world-memory-link' } : null;
1509
+ if (count > 0) lines.push({ text: `あなたが決める記憶の候補 ${count}件。昇格したものだけが正本になります。`, tone: 'attention', summary: `あなたが決める記憶の候補 ${count}件`, link });
1510
+ else lines.push({ text: 'あなたが決める記憶の候補はありません。', tone: 'info', summary: null });
1511
+ } else {
1512
+ lines.push({ text: '記憶の候補:読めない。件数は0件とは確認できません。', tone: 'warning', summary: '記憶の候補:読めない' });
1513
+ }
1514
+ return lines;
1515
+ }
1516
+
1466
1517
  /** 判断の履歴 in the local web (it was 今日, `#today`, until 2026-10-11; the shell still opens old links). */
1467
1518
  function defaultJudgmentHref(decisionAttemptId) {
1468
1519
  return decisionAttemptId ? `#value-proofs?decision=${encodeURIComponent(decisionAttemptId)}` : '#value-proofs';
1469
1520
  }
1470
1521
 
1471
- export function createWorldView({ root, rail, page, presentation = 'standard', document: explicitDocument, fetcher, projectHref = (project) => `#projects?project=${encodeURIComponent(project.id)}`, taskHref = null, judgmentHref = defaultJudgmentHref, selection = addressSelection(), businessExits = null }) {
1522
+ export function createWorldView({ root, rail, page, presentation = 'standard', document: explicitDocument, fetcher, projectHref = (project) => `#projects?project=${encodeURIComponent(project.id)}`, taskHref = null, judgmentHref = defaultJudgmentHref, selection = addressSelection(), businessExits = null, memoryStatus = null }) {
1472
1523
  const exitsEnabled = typeof businessExits === 'function';
1473
1524
  const doc = explicitDocument ?? globalThis.document;
1474
1525
  const reducedMotion = Boolean(globalThis.matchMedia?.('(prefers-reduced-motion: reduce)').matches);
@@ -2311,6 +2362,7 @@ export function createWorldView({ root, rail, page, presentation = 'standard', d
2311
2362
  }
2312
2363
 
2313
2364
  async function load() {
2365
+ const memoryRead = readMemoryStatus(memoryStatus);
2314
2366
  const businessesResult = await readJson(fetcher, '/api/extensions/world/businesses');
2315
2367
  if (disposed) return;
2316
2368
  // A world drawn from an organization's Graph shows only that organization's judgments: the host keeps
@@ -2333,13 +2385,14 @@ export function createWorldView({ root, rail, page, presentation = 'standard', d
2333
2385
  }
2334
2386
  if (disposed) return;
2335
2387
  notices.replaceChildren();
2336
- const note = (label, textValue, tone = 'info', summary = null) => {
2388
+ const note = (label, textValue, tone = 'info', summary = null, link = null) => {
2337
2389
  if (canvasUI) {
2338
- setCanvasNote(`${label}:${summary ?? 'detail'}`, { label, text: textValue, tone, summary });
2390
+ setCanvasNote(`${label}:${summary ?? 'detail'}`, { label, text: textValue, tone, summary, link });
2339
2391
  return;
2340
2392
  }
2341
2393
  const line = el(doc, 'p', { className: `bb-world-hud-line is-${tone}`, attrs: tone === 'info' ? {} : { role: 'alert' } });
2342
2394
  line.append(el(doc, 'strong', { text: label }), el(doc, 'span', { text: textValue }));
2395
+ if (link) line.append(el(doc, 'a', { className: link.className, text: link.label, attrs: { href: link.href } }));
2343
2396
  notices.append(line);
2344
2397
  };
2345
2398
  const businessPayload = businessesResult.ok ? businessesResult.data : null;
@@ -2387,6 +2440,9 @@ export function createWorldView({ root, rail, page, presentation = 'standard', d
2387
2440
  }
2388
2441
  }
2389
2442
  const rowItems = new Map(rows.map((row) => [row.key, Array.isArray(row.items) ? row.items : []]));
2443
+ const memory = await memoryRead;
2444
+ if (disposed) return;
2445
+ for (const line of memoryLines(memory)) note('記憶', line.text, line.tone, line.summary, line.link);
2390
2446
 
2391
2447
  knownBusinesses = businesses;
2392
2448
  canvasUI?.setCities(businesses, current.code);