@unson/brainbase-mcp 0.15.6 → 0.16.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/cli.js +6 -23
- package/dist/graph-web-http.d.ts +0 -7
- package/dist/graph-web-http.js +0 -14
- package/dist/graph-web.d.ts +2 -32
- package/dist/graph-web.js +1 -41
- package/dist/local-web-host.d.ts +0 -23
- package/dist/local-web-host.js +3 -15
- package/dist/organization-graph-web.d.ts +5 -27
- package/dist/organization-graph-web.js +4 -128
- package/dist/world-extension.d.ts +4 -19
- package/dist/world-extension.js +15 -75
- package/package.json +2 -2
- package/ui/graph-view-shared.js +0 -39
- package/ui/local-web-shell.js +3 -53
- package/ui/world/world-view.js +471 -156
package/dist/world-extension.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import { fileURLToPath } from 'node:url';
|
|
3
|
-
import {
|
|
4
|
-
import { publicHome } from './value-proof-review-http.js';
|
|
3
|
+
import { readJudgmentValueProofJournal, } from './judgment-value-proof-review.js';
|
|
5
4
|
import { projectOrganizationGraph, projectVocabularyTerms } from './organization-graph-web.js';
|
|
6
5
|
import { readLocalGraphState } from './local-web-host.js';
|
|
7
6
|
import { loadPersonalOs } from './ssot.js';
|
|
@@ -224,37 +223,26 @@ export async function readWorldVocabulary(path) {
|
|
|
224
223
|
return { kinds, statuses };
|
|
225
224
|
}
|
|
226
225
|
/**
|
|
227
|
-
* The businesses from the same Graph the other screens read
|
|
228
|
-
*
|
|
229
|
-
* be read is a state with its reason, never zero businesses.
|
|
226
|
+
* The businesses from the same Graph the other screens read (this Mac's Graph).
|
|
227
|
+
* An organization host draws its own records with `projectOrganizationWorld`.
|
|
228
|
+
* A Graph that cannot be read is a state with its reason, never zero businesses.
|
|
230
229
|
*/
|
|
231
230
|
export async function readWorldBusinesses(context, options = {}) {
|
|
232
231
|
const now = (options.now ?? (() => new Date()))();
|
|
233
232
|
let graph;
|
|
234
233
|
let source;
|
|
235
|
-
let terms;
|
|
236
234
|
try {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
}
|
|
241
|
-
graph = await context.organizationGraph.readGraphFile();
|
|
242
|
-
source = { authority: 'organization_graph', server: context.organizationGraph.server };
|
|
243
|
-
terms = context.organizationGraph.readVocabularyTerms ? await context.organizationGraph.readVocabularyTerms().catch(() => null) : null;
|
|
244
|
-
}
|
|
245
|
-
else {
|
|
246
|
-
const state = await readLocalGraphState(context.dataDir);
|
|
247
|
-
if (state.status === 'not_initialized' || state.status === 'migration_required') {
|
|
248
|
-
return { status: state.status, version: WORLD_EXTENSION_VERSION, reason: state.status };
|
|
249
|
-
}
|
|
250
|
-
if (state.status !== 'ready')
|
|
251
|
-
return { status: 'unavailable', version: WORLD_EXTENSION_VERSION, reason: state.message ?? 'local_graph_unreadable' };
|
|
252
|
-
const os = await loadPersonalOs(context.dataDir);
|
|
253
|
-
if (os.graph.version !== 2)
|
|
254
|
-
return { status: 'migration_required', version: WORLD_EXTENSION_VERSION, reason: 'migration_required' };
|
|
255
|
-
graph = os.graph;
|
|
256
|
-
source = { authority: 'local', dataDir: context.dataDir };
|
|
235
|
+
const state = await readLocalGraphState(context.dataDir);
|
|
236
|
+
if (state.status === 'not_initialized' || state.status === 'migration_required') {
|
|
237
|
+
return { status: state.status, version: WORLD_EXTENSION_VERSION, reason: state.status };
|
|
257
238
|
}
|
|
239
|
+
if (state.status !== 'ready')
|
|
240
|
+
return { status: 'unavailable', version: WORLD_EXTENSION_VERSION, reason: state.message ?? 'local_graph_unreadable' };
|
|
241
|
+
const os = await loadPersonalOs(context.dataDir);
|
|
242
|
+
if (os.graph.version !== 2)
|
|
243
|
+
return { status: 'migration_required', version: WORLD_EXTENSION_VERSION, reason: 'migration_required' };
|
|
244
|
+
graph = os.graph;
|
|
245
|
+
source = { authority: 'local', dataDir: context.dataDir };
|
|
258
246
|
}
|
|
259
247
|
catch (error) {
|
|
260
248
|
return { status: 'unavailable', version: WORLD_EXTENSION_VERSION, reason: error instanceof Error ? error.message : 'graph_unreadable' };
|
|
@@ -265,7 +253,7 @@ export async function readWorldBusinesses(context, options = {}) {
|
|
|
265
253
|
version: WORLD_EXTENSION_VERSION,
|
|
266
254
|
source,
|
|
267
255
|
as_of: now.toISOString(),
|
|
268
|
-
vocabulary: resolveWorldVocabulary(options.vocabulary, projection.businesses
|
|
256
|
+
vocabulary: resolveWorldVocabulary(options.vocabulary, projection.businesses),
|
|
269
257
|
...projection,
|
|
270
258
|
};
|
|
271
259
|
}
|
|
@@ -315,44 +303,6 @@ export async function readJudgmentPlaces(journalRoot, options = {}) {
|
|
|
315
303
|
}));
|
|
316
304
|
return { status: 'available', places };
|
|
317
305
|
}
|
|
318
|
-
/**
|
|
319
|
-
* The judgments of one organization, for a world drawn from that organization's Graph. A judgment
|
|
320
|
-
* belongs to the organization only when the repository it was worked in is one of the organization's
|
|
321
|
-
* businesses (a registered repository or the business code, the same rule as the world's placement).
|
|
322
|
-
* Every other judgment (another company's work, or one whose repository was not recorded) is withheld:
|
|
323
|
-
* only their count leaves the host, never their questions or repositories.
|
|
324
|
-
*/
|
|
325
|
-
export async function readOrganizationJudgments(context, businesses, workspaces = new Map()) {
|
|
326
|
-
const journal = await readJudgmentValueProofJournal({ root: context.journalRoot, cache: context.journalCache });
|
|
327
|
-
if (journal.status !== 'available')
|
|
328
|
-
return { status: 'unavailable', scope: 'organization', withheld: 0, places: [], home: null, reason: 'journal_unreadable' };
|
|
329
|
-
const belongs = (workspace) => workspace !== null
|
|
330
|
-
&& businesses.some((business) => business.repositories.includes(workspace) || business.code === workspace);
|
|
331
|
-
const kept = [];
|
|
332
|
-
const places = [];
|
|
333
|
-
for (const entry of journal.entries) {
|
|
334
|
-
const workspace = await entryWorkspace(entry.file, workspaces);
|
|
335
|
-
if (!belongs(workspace))
|
|
336
|
-
continue;
|
|
337
|
-
kept.push(entry);
|
|
338
|
-
places.push({ decision_attempt_id: entry.proof.decision_attempt_id, workspace });
|
|
339
|
-
}
|
|
340
|
-
const [feedback, answers] = await Promise.all([
|
|
341
|
-
readJudgmentValueProofFeedback({ dataDir: context.dataDir }),
|
|
342
|
-
readJudgmentValueProofAnswers({ dataDir: context.dataDir }),
|
|
343
|
-
]);
|
|
344
|
-
// Rejected files are left out too: their names would say nothing about the organization they belong to.
|
|
345
|
-
const home = buildJudgmentValueProofReviewHome({ ...journal, entries: kept, rejected: [] }, feedback, { now: context.now(), answers });
|
|
346
|
-
return { status: 'available', scope: 'organization', withheld: journal.entries.length - kept.length, places, home: publicHome(home) };
|
|
347
|
-
}
|
|
348
|
-
async function entryWorkspace(valueProofFile, workspaces) {
|
|
349
|
-
const turnInputFile = `${valueProofFile.slice(0, -VALUE_PROOF_SUFFIX.length)}.turn-input.json`;
|
|
350
|
-
if (!workspaces.has(turnInputFile)) {
|
|
351
|
-
const turnInput = await readJsonFile(turnInputFile);
|
|
352
|
-
workspaces.set(turnInputFile, isRecord(turnInput) ? text(turnInput.project_code) : null);
|
|
353
|
-
}
|
|
354
|
-
return workspaces.get(turnInputFile) ?? null;
|
|
355
|
-
}
|
|
356
306
|
async function readJsonFile(path) {
|
|
357
307
|
try {
|
|
358
308
|
return JSON.parse(await readFile(path, 'utf8'));
|
|
@@ -375,16 +325,6 @@ function createWorldModule(context, read) {
|
|
|
375
325
|
writeJson(response, 200, await read());
|
|
376
326
|
return true;
|
|
377
327
|
}
|
|
378
|
-
if (path === `/api/extensions/${WORLD_EXTENSION_ID}/organization-judgments`) {
|
|
379
|
-
response.setHeader('Cache-Control', 'no-store');
|
|
380
|
-
const world = await read();
|
|
381
|
-
if (world.status !== 'ok' || world.source.authority !== 'organization_graph') {
|
|
382
|
-
writeJson(response, 200, { version: WORLD_EXTENSION_VERSION, status: 'unavailable', scope: 'organization', withheld: 0, places: [], home: null, reason: 'not_reading_organization_graph' });
|
|
383
|
-
return true;
|
|
384
|
-
}
|
|
385
|
-
writeJson(response, 200, { version: WORLD_EXTENSION_VERSION, ...(await readOrganizationJudgments(context, world.businesses, workspaces)) });
|
|
386
|
-
return true;
|
|
387
|
-
}
|
|
388
328
|
if (path === `/api/extensions/${WORLD_EXTENSION_ID}/judgment-places`) {
|
|
389
329
|
response.setHeader('Cache-Control', 'no-store');
|
|
390
330
|
writeJson(response, 200, { version: WORLD_EXTENSION_VERSION, ...(await readJudgmentPlaces(context.journalRoot, { cache: context.journalCache, workspaces })) });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unson/brainbase-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.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",
|
|
@@ -358,5 +358,5 @@
|
|
|
358
358
|
"engines": {
|
|
359
359
|
"node": ">=20"
|
|
360
360
|
},
|
|
361
|
-
"gitHead": "
|
|
361
|
+
"gitHead": "a0914d025914d4ada329b730aeddd6488f28bcfd"
|
|
362
362
|
}
|
package/ui/graph-view-shared.js
CHANGED
|
@@ -469,45 +469,6 @@ export function hostSourceNotice(sourceNotice, subject) {
|
|
|
469
469
|
return { ...sourceNotice, text: sourceNotice.text(subject ?? null) };
|
|
470
470
|
}
|
|
471
471
|
|
|
472
|
-
// ---------------------------------------------------------------------------
|
|
473
|
-
// The organization's web (Inspector UX-20261004-01)
|
|
474
|
-
|
|
475
|
-
const ORGANIZATION_RECORD_ID = /^[A-Za-z0-9_.:-]{1,200}$/u;
|
|
476
|
-
|
|
477
|
-
/** The organization web's origin the host was told about, or null. */
|
|
478
|
-
export function organizationWebOrigin(organizationGraph) {
|
|
479
|
-
if (organizationGraph?.status !== 'connected') return null;
|
|
480
|
-
const origin = organizationGraph.web_url;
|
|
481
|
-
return typeof origin === 'string' && /^https:\/\/[^/?#@\s]+$/u.test(origin) ? origin : null;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
/**
|
|
485
|
-
* Where a record opens in the organization web, `{ href, label }`, or null when it has no usable id. The
|
|
486
|
-
* organization web opens 情報と関係 by record id and プロジェクトと関係者 by project code, and keeps that record
|
|
487
|
-
* across sign-in. A project opens by its own code only: a sub-project's scope code is its parent's and
|
|
488
|
-
* would open the parent, so without its own code the record opens by id.
|
|
489
|
-
*/
|
|
490
|
-
export function organizationRecordLink(origin, screenId, record) {
|
|
491
|
-
const id = typeof record?.id === 'string' && ORGANIZATION_RECORD_ID.test(record.id) ? record.id : null;
|
|
492
|
-
const own = typeof record?.metadata?.code === 'string' ? record.metadata.code.trim() : '';
|
|
493
|
-
const code = own && ORGANIZATION_RECORD_ID.test(own) ? own : null;
|
|
494
|
-
if (screenId === 'projects' && record?.type === 'project' && code) {
|
|
495
|
-
return { href: `${origin}/?screen=projects&project=${encodeURIComponent(code)}`, label: '「プロジェクトと関係者」' };
|
|
496
|
-
}
|
|
497
|
-
return id ? { href: `${origin}/?screen=graph&entity_id=${encodeURIComponent(id)}`, label: '「情報と関係」' } : null;
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
export const ORGANIZATION_RECORD_LINK_TEXT = '組織版でこの記録を開く';
|
|
501
|
-
|
|
502
|
-
/** A link that opens `target` from organizationRecordLink in a new tab. */
|
|
503
|
-
export function organizationRecordAnchor(doc, target, className) {
|
|
504
|
-
return makeElement(doc, 'a', {
|
|
505
|
-
...(className ? { className } : {}),
|
|
506
|
-
text: ORGANIZATION_RECORD_LINK_TEXT,
|
|
507
|
-
attrs: { href: target.href, target: '_blank', rel: 'noopener noreferrer' },
|
|
508
|
-
});
|
|
509
|
-
}
|
|
510
|
-
|
|
511
472
|
/**
|
|
512
473
|
* A host's note that corrections are not available here, or null when it passed none. The host may
|
|
513
474
|
* pass a function of the selected record (`{ id, type, metadata }`) returning a string or an element,
|
package/ui/local-web-shell.js
CHANGED
|
@@ -21,7 +21,7 @@ import { createObjectiveEditorController } from './objective-editor.js';
|
|
|
21
21
|
import { createObjectiveEditorHttpPort } from './objective-editor-http-port.js';
|
|
22
22
|
import { createValueProofReviewUI } from './value-proof-review.js';
|
|
23
23
|
import { createWorldModelView } from './world-model-view.js';
|
|
24
|
-
import { createGraphClient
|
|
24
|
+
import { createGraphClient } from './graph-view-shared.js';
|
|
25
25
|
|
|
26
26
|
export const LOCAL_WEB_SHELL_CONTRACT_VERSION = 'brainbase.local-web-shell.v1';
|
|
27
27
|
|
|
@@ -203,58 +203,11 @@ function mountObjectives(container, context) {
|
|
|
203
203
|
return { editor, worldModel };
|
|
204
204
|
}
|
|
205
205
|
|
|
206
|
-
const ORGANIZATION_READ_ONLY_NOTE = '組織のGraphを読み取り専用で表示しています。登録の訂正は組織版で行ってください。';
|
|
207
|
-
// What the owner needs before leaving for the organization web (Inspector UX-20261004-01): a link to this
|
|
208
|
-
// very record there, who may correct it, and what happens after saving.
|
|
209
|
-
export { organizationRecordLink };
|
|
210
|
-
const organizationReadOnlyNoteLinked = (doc, origin, screenId) => (record) => {
|
|
211
|
-
const target = organizationRecordLink(origin, screenId, record);
|
|
212
|
-
if (!target) return ORGANIZATION_READ_ONLY_NOTE;
|
|
213
|
-
const note = makeElement(doc, 'span');
|
|
214
|
-
note.append(
|
|
215
|
-
makeElement(doc, 'span', { text: [
|
|
216
|
-
'組織のGraphを読み取り専用で表示しています。ここでは直せません。訂正の手順:',
|
|
217
|
-
`① 下のリンク(上の「出典」にも同じリンクがあります)から、組織版の${target.label}でこの記録を開きます。ログインしていなければSlackでログインすると、そのままこの記録に戻ります。`,
|
|
218
|
-
'② 直せるのは、組織版での役割がgmかceoで、このプロジェクトが属する事業(事業そのものなら、その事業)を許可されている人です。直せるのはプロジェクトの記録(名前・別名・要約・期間・目的・状態)だけで、人物の記録と、関係者の追加・終了は組織版でもまだ直せません。',
|
|
219
|
-
'③ 保存すると、理由といっしょに組織のGraphへ書き込まれ、組織版がその場で読み戻して表示します。この画面には、1分ほどたってから再読み込みすると反映されます。',
|
|
220
|
-
'',
|
|
221
|
-
].join('\n') }),
|
|
222
|
-
organizationRecordAnchor(doc, target),
|
|
223
|
-
);
|
|
224
|
-
return note;
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* The 出典 notice of a screen reading the organization Graph. Once a record is selected its link opens
|
|
229
|
-
* that record in the organization web; before that, the screen there.
|
|
230
|
-
*/
|
|
231
|
-
const organizationSourceText = (doc, origin, screenId, sourceText) => (record) => {
|
|
232
|
-
const target = record ? organizationRecordLink(origin, screenId, record) : null;
|
|
233
|
-
const link = target
|
|
234
|
-
? organizationRecordAnchor(doc, target)
|
|
235
|
-
: makeElement(doc, 'a', {
|
|
236
|
-
text: '組織版で開いて訂正する',
|
|
237
|
-
attrs: { href: `${origin}/?screen=${encodeURIComponent(screenId)}`, target: '_blank', rel: 'noopener noreferrer' },
|
|
238
|
-
});
|
|
239
|
-
const source = makeElement(doc, 'span');
|
|
240
|
-
source.append(makeElement(doc, 'span', { text: `${sourceText} ` }), link);
|
|
241
|
-
return source;
|
|
242
|
-
};
|
|
243
|
-
|
|
244
206
|
function mountGraphScreen(screenId, createView) {
|
|
245
207
|
return (container, context) => {
|
|
246
208
|
const viewRoot = makeElement(context.document, 'div');
|
|
247
209
|
makePage(container, context).append(viewRoot);
|
|
248
|
-
// Ledger C1: when the host reads the organization Graph, nothing here can be corrected.
|
|
249
|
-
const readOnly = context.organizationGraph?.status === 'connected';
|
|
250
|
-
const origin = readOnly ? organizationWebOrigin(context.organizationGraph) : null;
|
|
251
|
-
const sourceText = `組織のGraph(${context.organizationGraph?.server ?? '接続先不明'})を読み取り専用で表示しています。このMacのGraphではありません。`;
|
|
252
210
|
return createView({
|
|
253
|
-
...(readOnly ? {
|
|
254
|
-
canCorrect: false,
|
|
255
|
-
readOnlyNote: origin ? organizationReadOnlyNoteLinked(context.document, origin, screenId) : ORGANIZATION_READ_ONLY_NOTE,
|
|
256
|
-
sourceNotice: { label: '出典', text: origin ? organizationSourceText(context.document, origin, screenId, sourceText) : sourceText },
|
|
257
|
-
} : {}),
|
|
258
211
|
root: viewRoot,
|
|
259
212
|
rail: context.rail,
|
|
260
213
|
page: context.page,
|
|
@@ -334,7 +287,6 @@ function renderSource(doc, bar, status, onRecheck) {
|
|
|
334
287
|
bar.append(
|
|
335
288
|
fact('データ', data.data_dir),
|
|
336
289
|
fact('Graph', graphLabel, data.graph.status === 'ready' ? '' : 'is-warning'),
|
|
337
|
-
fact('組織のGraph', data.organization_graph?.status === 'connected' ? '読んでいます(読み取りのみ)' : '読んでいません'),
|
|
338
290
|
);
|
|
339
291
|
}
|
|
340
292
|
|
|
@@ -446,10 +398,8 @@ export function createLocalWebShell({
|
|
|
446
398
|
slot.replaceChildren();
|
|
447
399
|
const rail = rails.get(screen.id) ?? null;
|
|
448
400
|
rail?.replaceChildren();
|
|
449
|
-
const
|
|
450
|
-
const
|
|
451
|
-
const page = Object.freeze({ crumbs: Object.freeze(['あなたのBrainbase', screen.label]), source: readsOrganization ? '組織のGraph(読み取りのみ)' : screen.source ?? null });
|
|
452
|
-
const view = screen.mount(slot, Object.freeze({ ...context, rail, page, initialEntityId: entityId, organizationGraph })) ?? true;
|
|
401
|
+
const page = Object.freeze({ crumbs: Object.freeze(['あなたのBrainbase', screen.label]), source: screen.source ?? null });
|
|
402
|
+
const view = screen.mount(slot, Object.freeze({ ...context, rail, page, initialEntityId: entityId })) ?? true;
|
|
453
403
|
mounted.set(screen.id, view);
|
|
454
404
|
if (entityId && screen.id === 'today' && typeof view?.openDecision === 'function') view.openDecision(entityId);
|
|
455
405
|
if (entityId && screen.id === 'projects' && typeof view?.openProject === 'function') void view.openProject(entityId);
|