@unson/brainbase-mcp 0.10.10 → 0.10.12
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/knowledge-promotion.d.ts +410 -0
- package/dist/knowledge-promotion.js +843 -0
- package/dist/local-web-host.d.ts +15 -3
- package/dist/local-web-host.js +68 -15
- package/package.json +6 -2
- package/ui/local-web-shell.js +2 -3
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.12",
|
|
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",
|
|
@@ -155,6 +155,10 @@
|
|
|
155
155
|
"types": "./dist/personal-knowledge.d.ts",
|
|
156
156
|
"import": "./dist/personal-knowledge.js"
|
|
157
157
|
},
|
|
158
|
+
"./knowledge-promotion": {
|
|
159
|
+
"types": "./dist/knowledge-promotion.d.ts",
|
|
160
|
+
"import": "./dist/knowledge-promotion.js"
|
|
161
|
+
},
|
|
158
162
|
"./organization-connection": {
|
|
159
163
|
"types": "./dist/organization-connection.d.ts",
|
|
160
164
|
"import": "./dist/organization-connection.js"
|
|
@@ -336,5 +340,5 @@
|
|
|
336
340
|
"engines": {
|
|
337
341
|
"node": ">=20"
|
|
338
342
|
},
|
|
339
|
-
"gitHead": "
|
|
343
|
+
"gitHead": "10493d41000c832bf85c33fc30e920a4c194fe61"
|
|
340
344
|
}
|
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';
|