@unson/brainbase-mcp 0.10.10 → 0.10.11
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/README.md +2 -0
- package/contracts/judgment-dag/digest.json +2 -2
- package/dist/local-web-host.d.ts +15 -3
- package/dist/local-web-host.js +68 -15
- package/package.json +2 -2
- package/ui/local-web-shell.js +2 -3
package/README.md
CHANGED
|
@@ -57,6 +57,8 @@ Brainbaseの価値は、情報を多く保存することではありません
|
|
|
57
57
|
|
|
58
58
|
共通UIの最初の公開単位として、知識UIを `@unson/brainbase-mcp/ui/*` から利用できます。Mana委任UIはUnsonの実行基盤(Mana)に結びつくため組織版の画面とし、0.8.0の後に共通UIから外しました。
|
|
59
59
|
|
|
60
|
+
私有版の画面は `createLocalWebHost({ extensions })` へ登録します。ホストが共通ナビ、静的ファイルの許可リスト、loopback Host検査、書込時の同一Origin・起動トークン検査を担います。拡張のAPIは `/api/extensions/<id>`、画面資産は `/ui/extensions/<id>` に限られ、私有コードはこの公開packageへ含めません。契約と受入条件は[拡張Story](docs/stories/story-local-web-private-extension-v1.md)に記録しています。
|
|
61
|
+
|
|
60
62
|
実装済み・develop・計画中の境界は、[現在の状態](https://brainbase.pages.dev/guide/status)を参照してください。
|
|
61
63
|
|
|
62
64
|
## 10分で試す
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
"path": "README.md",
|
|
28
|
-
"sha256": "
|
|
28
|
+
"sha256": "c36fd404cbc6e9c2262e82b5d921ccbcef6617774f969dd1e0252cd9e7101af8"
|
|
29
29
|
}
|
|
30
30
|
],
|
|
31
|
-
"digest": "
|
|
31
|
+
"digest": "1beccdf5679b9b7f42375a498a76a661d87ba665f66657c93827dd8d6a938c28"
|
|
32
32
|
}
|
package/dist/local-web-host.d.ts
CHANGED
|
@@ -5,9 +5,9 @@ import { type ObjectiveDefinitionBuilder } from './foundation-http.js';
|
|
|
5
5
|
*
|
|
6
6
|
* One loopback server carries every local screen. The host owns the
|
|
7
7
|
* protections, the trusted principal and the static allowlist; each screen's
|
|
8
|
-
* server side is a `LocalWebModule`.
|
|
9
|
-
* `defaultLocalWebModules()` and
|
|
10
|
-
* `
|
|
8
|
+
* server side is a `LocalWebModule`. Public screens live in
|
|
9
|
+
* `defaultLocalWebModules()` and `LOCAL_WEB_SCREENS`; private products register
|
|
10
|
+
* a `LocalWebExtension` without copying either one.
|
|
11
11
|
*/
|
|
12
12
|
export declare const LOCAL_WEB_HOST_VERSION: "local-web-host.v1";
|
|
13
13
|
/** One per-launch token guards every write on this host, including value-proof feedback. */
|
|
@@ -50,6 +50,17 @@ export interface LocalWebModule {
|
|
|
50
50
|
readonly uiFiles: readonly string[];
|
|
51
51
|
handle(request: IncomingMessage, response: ServerResponse): Promise<boolean>;
|
|
52
52
|
}
|
|
53
|
+
/** Private products add one screen and one namespaced API without changing OSS routes. */
|
|
54
|
+
export interface LocalWebExtension {
|
|
55
|
+
/** Lowercase URL segment, unique among extensions. */
|
|
56
|
+
readonly id: string;
|
|
57
|
+
/** Packaged directory containing only the files listed in uiFiles. */
|
|
58
|
+
readonly uiDir: string;
|
|
59
|
+
readonly uiFiles: readonly string[];
|
|
60
|
+
/** ES module exporting a `screen` compatible with createLocalWebShell. */
|
|
61
|
+
readonly screenEntry: string;
|
|
62
|
+
createModule(context: LocalWebModuleContext): LocalWebModule;
|
|
63
|
+
}
|
|
53
64
|
export interface LocalWebHostOptions {
|
|
54
65
|
/** Personal OS data directory. Defaults to `BRAINBASE_PERSONAL_OS_DIR` or `~/.brainbase/personal-os`. */
|
|
55
66
|
readonly dataDir?: string;
|
|
@@ -59,6 +70,7 @@ export interface LocalWebHostOptions {
|
|
|
59
70
|
/** Directory containing the packaged `ui/` files. */
|
|
60
71
|
readonly uiDir?: string;
|
|
61
72
|
readonly now?: () => Date;
|
|
73
|
+
readonly extensions?: readonly LocalWebExtension[];
|
|
62
74
|
}
|
|
63
75
|
export interface LocalWebHost {
|
|
64
76
|
readonly server: Server;
|
package/dist/local-web-host.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto';
|
|
2
|
-
import { readFile, stat } from 'node:fs/promises';
|
|
2
|
+
import { lstat, readFile, stat } from 'node:fs/promises';
|
|
3
3
|
import { createServer } from 'node:http';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
@@ -20,9 +20,9 @@ import { createWorldModelStore, WORLD_MODEL_EVIDENCE_SIDECAR, WorldModelStoreErr
|
|
|
20
20
|
*
|
|
21
21
|
* One loopback server carries every local screen. The host owns the
|
|
22
22
|
* protections, the trusted principal and the static allowlist; each screen's
|
|
23
|
-
* server side is a `LocalWebModule`.
|
|
24
|
-
* `defaultLocalWebModules()` and
|
|
25
|
-
* `
|
|
23
|
+
* server side is a `LocalWebModule`. Public screens live in
|
|
24
|
+
* `defaultLocalWebModules()` and `LOCAL_WEB_SCREENS`; private products register
|
|
25
|
+
* a `LocalWebExtension` without copying either one.
|
|
26
26
|
*/
|
|
27
27
|
export const LOCAL_WEB_HOST_VERSION = 'local-web-host.v1';
|
|
28
28
|
/** One per-launch token guards every write on this host, including value-proof feedback. */
|
|
@@ -598,7 +598,7 @@ function shellHtml(token, stylesheets) {
|
|
|
598
598
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
599
599
|
<meta name="brainbase-web-token" content="${escapeAttribute(token)}">
|
|
600
600
|
<title>Brainbase</title>
|
|
601
|
-
${stylesheets.map((
|
|
601
|
+
${stylesheets.map((path) => `<link rel="stylesheet" href="${escapeAttribute(path)}">`).join('\n')}
|
|
602
602
|
</head>
|
|
603
603
|
<body>
|
|
604
604
|
<div id="brainbase-local-web"></div>
|
|
@@ -607,16 +607,31 @@ ${stylesheets.map((file) => `<link rel="stylesheet" href="/ui/${escapeAttribute(
|
|
|
607
607
|
</html>
|
|
608
608
|
`;
|
|
609
609
|
}
|
|
610
|
-
|
|
610
|
+
function bootstrapJs(extensions) {
|
|
611
|
+
const imports = extensions.map((extension, index) => `import { screen as extensionScreen${index} } from '/ui/extensions/${extension.id}/${extension.screenEntry}';`).join('\n');
|
|
612
|
+
const screens = extensions.map((_, index) => `extensionScreen${index}`).join(', ');
|
|
613
|
+
const ids = JSON.stringify(extensions.map((extension) => extension.id));
|
|
614
|
+
return `import { createLocalWebShell, LOCAL_WEB_SCREENS } from '/ui/local-web-shell.js';
|
|
615
|
+
${imports}
|
|
616
|
+
const extensionScreens = [${screens}];
|
|
617
|
+
const extensionIds = ${ids};
|
|
618
|
+
if (extensionScreens.some((screen, index) => screen?.id !== extensionIds[index] || LOCAL_WEB_SCREENS.some((base) => base.id === screen.id))) {
|
|
619
|
+
throw new Error('Invalid extension screen id');
|
|
620
|
+
}
|
|
611
621
|
const meta = (name) => document.querySelector(\`meta[name="\${name}"]\`)?.getAttribute('content') ?? '';
|
|
612
622
|
const screenFromHash = () => decodeURIComponent(location.hash.replace(/^#/, '')) || undefined;
|
|
613
623
|
const shell = createLocalWebShell({
|
|
614
624
|
root: document.getElementById('brainbase-local-web'),
|
|
615
625
|
token: meta('brainbase-web-token'),
|
|
616
|
-
initialScreen: screenFromHash()
|
|
626
|
+
initialScreen: screenFromHash(),
|
|
627
|
+
screens: [...LOCAL_WEB_SCREENS, ...extensionScreens]
|
|
617
628
|
});
|
|
618
629
|
addEventListener('hashchange', () => shell.show(screenFromHash()));
|
|
619
630
|
`;
|
|
631
|
+
}
|
|
632
|
+
function validExtensionFile(file) {
|
|
633
|
+
return /^[A-Za-z0-9][A-Za-z0-9._-]*\.(?:js|css)$/u.test(file) && !file.includes('..');
|
|
634
|
+
}
|
|
620
635
|
/** Local single-owner host. The caller owns `listen()` and must bind to a loopback address. */
|
|
621
636
|
export function createLocalWebHost(options = {}) {
|
|
622
637
|
const token = options.token ?? randomBytes(24).toString('base64url');
|
|
@@ -627,15 +642,40 @@ export function createLocalWebHost(options = {}) {
|
|
|
627
642
|
const uiDir = options.uiDir ?? fileURLToPath(new URL('../ui/', import.meta.url));
|
|
628
643
|
const context = { dataDir, journalRoot, token, now: options.now ?? (() => new Date()) };
|
|
629
644
|
const modules = defaultLocalWebModules(context);
|
|
645
|
+
const extensions = [...options.extensions ?? []];
|
|
646
|
+
const extensionModules = [];
|
|
647
|
+
const ids = new Set();
|
|
630
648
|
const uiFiles = new Map();
|
|
631
649
|
for (const file of [...BASE_UI_FILES, ...modules.flatMap((module) => module.uiFiles)]) {
|
|
632
650
|
const extension = file.slice(file.lastIndexOf('.'));
|
|
633
651
|
const type = CONTENT_TYPES[extension];
|
|
634
|
-
if (!type || file
|
|
652
|
+
if (!type || !validExtensionFile(file))
|
|
635
653
|
throw new TypeError(`Unsupported UI file ${file}`);
|
|
636
|
-
uiFiles.set(`/ui/${file}`, type);
|
|
654
|
+
uiFiles.set(`/ui/${file}`, { type, root: uiDir, file });
|
|
655
|
+
}
|
|
656
|
+
for (const extension of extensions) {
|
|
657
|
+
if (!/^[a-z][a-z0-9-]*$/u.test(extension.id) || ids.has(extension.id)) {
|
|
658
|
+
throw new TypeError(`Invalid or duplicate extension id ${extension.id}`);
|
|
659
|
+
}
|
|
660
|
+
ids.add(extension.id);
|
|
661
|
+
if (!extension.uiFiles.includes(extension.screenEntry) || !extension.screenEntry.endsWith('.js')) {
|
|
662
|
+
throw new TypeError(`Extension screen entry must be an allowlisted JS file: ${extension.id}`);
|
|
663
|
+
}
|
|
664
|
+
const module = extension.createModule(context);
|
|
665
|
+
if (module.id !== extension.id || module.uiFiles.length !== 0) {
|
|
666
|
+
throw new TypeError(`Extension module must use its id and declare UI files on the extension: ${extension.id}`);
|
|
667
|
+
}
|
|
668
|
+
extensionModules.push({ prefix: `/api/extensions/${extension.id}`, module });
|
|
669
|
+
for (const file of extension.uiFiles) {
|
|
670
|
+
if (!validExtensionFile(file))
|
|
671
|
+
throw new TypeError(`Unsupported extension UI file ${file}`);
|
|
672
|
+
const path = `/ui/extensions/${extension.id}/${file}`;
|
|
673
|
+
if (uiFiles.has(path))
|
|
674
|
+
throw new TypeError(`Duplicate extension UI file ${path}`);
|
|
675
|
+
uiFiles.set(path, { type: CONTENT_TYPES[file.slice(file.lastIndexOf('.'))], root: extension.uiDir, file });
|
|
676
|
+
}
|
|
637
677
|
}
|
|
638
|
-
const stylesheets = [...uiFiles.keys()].filter((path) => path.endsWith('.css'))
|
|
678
|
+
const stylesheets = [...uiFiles.keys()].filter((path) => path.endsWith('.css'));
|
|
639
679
|
async function handle(request, response) {
|
|
640
680
|
if (!isLoopbackHost(request)) {
|
|
641
681
|
writeJson(response, 403, { error: { code: 'host_rejected', message: 'Host must be a loopback address for this server' } });
|
|
@@ -653,6 +693,14 @@ export function createLocalWebHost(options = {}) {
|
|
|
653
693
|
return;
|
|
654
694
|
}
|
|
655
695
|
const path = requestUrl(request).pathname;
|
|
696
|
+
for (const extension of extensionModules) {
|
|
697
|
+
if (path === extension.prefix || path.startsWith(`${extension.prefix}/`)) {
|
|
698
|
+
if (await extension.module.handle(request, response))
|
|
699
|
+
return;
|
|
700
|
+
writeJson(response, 404, { error: { code: 'not_found', message: 'Not found' } });
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
656
704
|
if (request.method !== 'GET') {
|
|
657
705
|
writeJson(response, 405, { error: { code: 'method_not_allowed', message: 'Use GET' } });
|
|
658
706
|
return;
|
|
@@ -668,16 +716,21 @@ export function createLocalWebHost(options = {}) {
|
|
|
668
716
|
}
|
|
669
717
|
if (path === '/app.js') {
|
|
670
718
|
response.setHeader('Content-Type', 'text/javascript; charset=utf-8');
|
|
671
|
-
response.end(
|
|
719
|
+
response.end(bootstrapJs(extensions));
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
const asset = uiFiles.get(path);
|
|
723
|
+
if (!asset) {
|
|
724
|
+
writeJson(response, 404, { error: { code: 'not_found', message: 'Not found' } });
|
|
672
725
|
return;
|
|
673
726
|
}
|
|
674
|
-
const
|
|
675
|
-
if (!
|
|
727
|
+
const target = join(asset.root, asset.file);
|
|
728
|
+
if (!(await lstat(target)).isFile()) {
|
|
676
729
|
writeJson(response, 404, { error: { code: 'not_found', message: 'Not found' } });
|
|
677
730
|
return;
|
|
678
731
|
}
|
|
679
|
-
response.setHeader('Content-Type', type);
|
|
680
|
-
response.end(await readFile(
|
|
732
|
+
response.setHeader('Content-Type', asset.type);
|
|
733
|
+
response.end(await readFile(target));
|
|
681
734
|
}
|
|
682
735
|
const server = createServer((request, response) => {
|
|
683
736
|
void handle(request, response).catch((error) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unson/brainbase-mcp",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.11",
|
|
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",
|
|
@@ -336,5 +336,5 @@
|
|
|
336
336
|
"engines": {
|
|
337
337
|
"node": ">=20"
|
|
338
338
|
},
|
|
339
|
-
"gitHead": "
|
|
339
|
+
"gitHead": "6e626178bfa8b8b3acf99600ba79555309518046"
|
|
340
340
|
}
|
package/ui/local-web-shell.js
CHANGED
|
@@ -11,9 +11,8 @@
|
|
|
11
11
|
* mounted until the host reports Graph v2; a v1 Graph shows the migration
|
|
12
12
|
* commands instead of zero items, and the host never migrates by itself.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* `defaultLocalWebModules()` in `src/local-web-host.ts`.
|
|
14
|
+
* Public screens are listed in LOCAL_WEB_SCREENS. Private products pass an
|
|
15
|
+
* extended `screens` list through LocalWebHostOptions.extensions.
|
|
17
16
|
*/
|
|
18
17
|
|
|
19
18
|
import { createGraphProjectsView } from './graph-projects-view.js';
|