@unson/brainbase-mcp 0.30.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/dist/judgment-host.d.ts +2 -2
- package/dist/judgment-host.js +3 -29
- package/dist/judgment-resolution-contract.d.ts +14 -0
- package/dist/judgment-resolution-contract.js +53 -0
- package/package.json +6 -2
- package/ui/world/world-canvas-ui.css +8 -0
- package/ui/world/world-canvas-ui.js +3 -1
- package/ui/world/world-view.css +1 -0
- package/ui/world/world-view.js +60 -4
package/dist/judgment-host.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type JudgmentAutonomyEvaluation, type JudgmentAutonomyResolver } from './judgment-autonomy.js';
|
|
2
|
+
import { canonicalJson } from './judgment-resolution-contract.js';
|
|
2
3
|
type Environment = Record<string, string | undefined>;
|
|
3
4
|
export interface ConversationMessage {
|
|
4
5
|
sequence?: number;
|
|
@@ -204,7 +205,7 @@ export interface JudgmentStopBlock {
|
|
|
204
205
|
block_kind?: 'autonomy_continue' | 'audit_repair';
|
|
205
206
|
reason: string;
|
|
206
207
|
}
|
|
207
|
-
export
|
|
208
|
+
export { canonicalJson };
|
|
208
209
|
export declare function buildJudgmentRequest(payload: JudgmentHookPayload, { env, trustedConversationMessages }?: {
|
|
209
210
|
env?: Environment;
|
|
210
211
|
trustedConversationMessages?: ConversationMessage[];
|
|
@@ -223,4 +224,3 @@ export declare function blockedJudgmentOutput(reason: string): {
|
|
|
223
224
|
suppressOutput: false;
|
|
224
225
|
stopReason: string;
|
|
225
226
|
};
|
|
226
|
-
export {};
|
package/dist/judgment-host.js
CHANGED
|
@@ -3,6 +3,7 @@ import { closeSync, constants as fsConstants, fsyncSync, linkSync, mkdirSync, op
|
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
4
|
import { basename, delimiter, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
5
5
|
import { evaluateJudgmentAutonomy, renderJudgmentAutonomyContinuation } from './judgment-autonomy.js';
|
|
6
|
+
import { canonicalJson } from './judgment-resolution-contract.js';
|
|
6
7
|
const RUNTIME_VERSION = 'portable-judgment-runtime-1.0.0';
|
|
7
8
|
const ZERO_CALL_DISPLAY_LINE = '📚 Brainbase未参照: 必須参照なし・実呼び出し0回 ✓';
|
|
8
9
|
const PORTABLE_KNOWLEDGE_TOOLS = {
|
|
@@ -62,35 +63,8 @@ const DOMAIN_TERMS = [
|
|
|
62
63
|
];
|
|
63
64
|
const FOLLOW_UP_TERMS = ['that', 'it', 'continue', 'do that', 'それ', 'その形', 'これ', 'こちら', '続けて', 'もう一度', 'では', 'それでいい', 'そうして'];
|
|
64
65
|
const AUTHORITY_INTENTS = new Set(['implement', 'operate']);
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return JSON.stringify(value);
|
|
68
|
-
if (typeof value === 'number') {
|
|
69
|
-
if (!Number.isFinite(value))
|
|
70
|
-
throw new TypeError('canonical JSON only supports finite numbers');
|
|
71
|
-
return JSON.stringify(value);
|
|
72
|
-
}
|
|
73
|
-
if (Array.isArray(value))
|
|
74
|
-
return `[${value.map((entry) => canonicalJson(entry)).join(',')}]`;
|
|
75
|
-
if (value && typeof value === 'object') {
|
|
76
|
-
return `{${Object.keys(value).sort(compareCodePoints).map((key) => {
|
|
77
|
-
const entry = value[key];
|
|
78
|
-
if (entry === undefined)
|
|
79
|
-
throw new TypeError('canonical JSON does not support undefined');
|
|
80
|
-
return `${JSON.stringify(key)}:${canonicalJson(entry)}`;
|
|
81
|
-
}).join(',')}}`;
|
|
82
|
-
}
|
|
83
|
-
throw new TypeError(`canonical JSON does not support ${typeof value}`);
|
|
84
|
-
}
|
|
85
|
-
function compareCodePoints(left, right) {
|
|
86
|
-
const a = Array.from(left, (value) => value.codePointAt(0));
|
|
87
|
-
const b = Array.from(right, (value) => value.codePointAt(0));
|
|
88
|
-
for (let index = 0; index < Math.min(a.length, b.length); index += 1) {
|
|
89
|
-
if (a[index] !== b[index])
|
|
90
|
-
return a[index] - b[index];
|
|
91
|
-
}
|
|
92
|
-
return a.length - b.length;
|
|
93
|
-
}
|
|
66
|
+
// canonicalJson is the shared judgment resolution contract; re-exported so existing importers keep working.
|
|
67
|
+
export { canonicalJson };
|
|
94
68
|
function sha256(value) {
|
|
95
69
|
return createHash('sha256').update(value).digest('hex');
|
|
96
70
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare function canonicalJson(value: unknown): string;
|
|
2
|
+
export declare function sha256Hex(value: string): string;
|
|
3
|
+
export declare function computeRequestDigest(rawInput: unknown): string;
|
|
4
|
+
export declare class JudgmentResolutionError extends Error {
|
|
5
|
+
readonly code: string;
|
|
6
|
+
readonly status: number;
|
|
7
|
+
constructor(code: string, message: string, status?: number);
|
|
8
|
+
toJSON(): {
|
|
9
|
+
name: string;
|
|
10
|
+
code: string;
|
|
11
|
+
status: number;
|
|
12
|
+
message: string;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
// Shared contract between the judgment Host and the judgment resolver.
|
|
3
|
+
// The resolver's HTTP mount and its service must throw and recognize the
|
|
4
|
+
// same error class, and both sides must produce identical canonical bytes,
|
|
5
|
+
// so these live in one public module instead of being copied per caller.
|
|
6
|
+
function compareCodePoints(left, right) {
|
|
7
|
+
const a = Array.from(left, (value) => value.codePointAt(0));
|
|
8
|
+
const b = Array.from(right, (value) => value.codePointAt(0));
|
|
9
|
+
for (let index = 0; index < Math.min(a.length, b.length); index += 1) {
|
|
10
|
+
if (a[index] !== b[index])
|
|
11
|
+
return a[index] - b[index];
|
|
12
|
+
}
|
|
13
|
+
return a.length - b.length;
|
|
14
|
+
}
|
|
15
|
+
export function canonicalJson(value) {
|
|
16
|
+
if (value === null || typeof value === 'boolean' || typeof value === 'string')
|
|
17
|
+
return JSON.stringify(value);
|
|
18
|
+
if (typeof value === 'number') {
|
|
19
|
+
if (!Number.isFinite(value))
|
|
20
|
+
throw new TypeError('canonical JSON only supports finite numbers');
|
|
21
|
+
return JSON.stringify(value);
|
|
22
|
+
}
|
|
23
|
+
if (Array.isArray(value))
|
|
24
|
+
return `[${value.map((entry) => canonicalJson(entry)).join(',')}]`;
|
|
25
|
+
if (value && typeof value === 'object') {
|
|
26
|
+
return `{${Object.keys(value).sort(compareCodePoints).map((key) => {
|
|
27
|
+
const entry = value[key];
|
|
28
|
+
if (entry === undefined)
|
|
29
|
+
throw new TypeError('canonical JSON does not support undefined');
|
|
30
|
+
return `${JSON.stringify(key)}:${canonicalJson(entry)}`;
|
|
31
|
+
}).join(',')}}`;
|
|
32
|
+
}
|
|
33
|
+
throw new TypeError(`canonical JSON does not support ${typeof value}`);
|
|
34
|
+
}
|
|
35
|
+
export function sha256Hex(value) {
|
|
36
|
+
return createHash('sha256').update(value).digest('hex');
|
|
37
|
+
}
|
|
38
|
+
export function computeRequestDigest(rawInput) {
|
|
39
|
+
return sha256Hex(canonicalJson(rawInput));
|
|
40
|
+
}
|
|
41
|
+
export class JudgmentResolutionError extends Error {
|
|
42
|
+
code;
|
|
43
|
+
status;
|
|
44
|
+
constructor(code, message, status = 400) {
|
|
45
|
+
super(message);
|
|
46
|
+
this.name = 'JudgmentResolutionError';
|
|
47
|
+
this.code = code;
|
|
48
|
+
this.status = status;
|
|
49
|
+
}
|
|
50
|
+
toJSON() {
|
|
51
|
+
return { name: this.name, code: this.code, status: this.status, message: this.message };
|
|
52
|
+
}
|
|
53
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unson/brainbase-mcp",
|
|
3
|
-
"version": "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",
|
|
@@ -183,6 +183,10 @@
|
|
|
183
183
|
"types": "./dist/knowledge-resolution-service.d.ts",
|
|
184
184
|
"import": "./dist/knowledge-resolution-service.js"
|
|
185
185
|
},
|
|
186
|
+
"./judgment-resolution-contract": {
|
|
187
|
+
"types": "./dist/judgment-resolution-contract.d.ts",
|
|
188
|
+
"import": "./dist/judgment-resolution-contract.js"
|
|
189
|
+
},
|
|
186
190
|
"./canonical-document-writer": {
|
|
187
191
|
"types": "./dist/canonical-document-writer.d.ts",
|
|
188
192
|
"import": "./dist/canonical-document-writer.js"
|
|
@@ -451,5 +455,5 @@
|
|
|
451
455
|
"engines": {
|
|
452
456
|
"node": ">=20"
|
|
453
457
|
},
|
|
454
|
-
"gitHead": "
|
|
458
|
+
"gitHead": "3f91401cb7221305fe1dfafc6126482bc8b55170"
|
|
455
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() {
|
package/ui/world/world-view.css
CHANGED
|
@@ -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%);
|
package/ui/world/world-view.js
CHANGED
|
@@ -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);
|