@mnemosyne_os/sdk 1.0.0 → 1.2.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/README.md +189 -98
- package/dist/index.cjs +586 -22
- package/dist/index.d.cts +741 -36
- package/dist/index.d.ts +741 -36
- package/dist/index.js +586 -25
- package/package.json +6 -4
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/client.ts
|
|
2
|
-
import
|
|
2
|
+
import WebSocket2 from "ws";
|
|
3
3
|
|
|
4
4
|
// src/manifest.ts
|
|
5
5
|
import { z } from "zod";
|
|
@@ -14,13 +14,30 @@ var VALID_SCOPES = [
|
|
|
14
14
|
"vault:write:PERSONAL",
|
|
15
15
|
"vault:read:FINANCE",
|
|
16
16
|
"vault:write:FINANCE",
|
|
17
|
-
"vault:read:
|
|
18
|
-
"vault:write:
|
|
17
|
+
"vault:read:RESEARCH",
|
|
18
|
+
"vault:write:RESEARCH",
|
|
19
|
+
/** Wildcard: grants r/w to any user-created custom vault. */
|
|
20
|
+
"vault:read:CUSTOM",
|
|
21
|
+
"vault:write:CUSTOM",
|
|
19
22
|
"share:request",
|
|
20
|
-
"share:grant"
|
|
23
|
+
"share:grant",
|
|
24
|
+
"monorepo:read",
|
|
25
|
+
"agents:read",
|
|
26
|
+
"nft:validate",
|
|
27
|
+
"neural:graph:read",
|
|
28
|
+
"llm:query",
|
|
29
|
+
// [PHASE-58] Read-only access to Perpetual Memory Bridges
|
|
30
|
+
"bridge:read"
|
|
21
31
|
];
|
|
22
|
-
var
|
|
23
|
-
|
|
32
|
+
var GRANT_REQUIRED_SCOPE_PREFIXES = [
|
|
33
|
+
"vault:read:",
|
|
34
|
+
"vault:write:",
|
|
35
|
+
"share:request",
|
|
36
|
+
"share:grant",
|
|
37
|
+
"llm:query"
|
|
38
|
+
];
|
|
39
|
+
var VALID_INTENTS = ["INGEST", "QUERY", "CORRELATE", "FORGET", "GIT_LOG", "LIST_AGENTS", "LIST_VAULTS", "BRIDGE_READ"];
|
|
40
|
+
var VALID_VAULTS = ["DEV", "SOCIAL", "PERSONAL", "FINANCE", "RESEARCH"];
|
|
24
41
|
var SEMVER_RE = /^\d+\.\d+\.\d+/;
|
|
25
42
|
var APP_ID_RE = /^[a-z0-9][a-z0-9_-]{1,63}$/;
|
|
26
43
|
var AppManifestSchema = z.object({
|
|
@@ -36,12 +53,22 @@ var AppManifestSchema = z.object({
|
|
|
36
53
|
scopes: z.array(z.enum(VALID_SCOPES)).min(1, {
|
|
37
54
|
message: "At least one scope required \u2014 apps with no scopes have no access"
|
|
38
55
|
}),
|
|
39
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Vaults the app can access.
|
|
58
|
+
* Core vaults must use the built-in identifiers (DEV, SOCIAL, etc.).
|
|
59
|
+
* Custom vault IDs are opaque uppercase strings — the manifest may list them
|
|
60
|
+
* here to pre-declare intent, and the OS validates them at runtime.
|
|
61
|
+
*/
|
|
62
|
+
vaults: z.array(z.union([z.enum(VALID_VAULTS), z.string().regex(/^[A-Z][A-Z0-9_]{0,63}$/, "Custom vault ID must be UPPER_SNAKE_CASE")])).min(1),
|
|
40
63
|
intents: z.array(z.enum(VALID_INTENTS)).min(1),
|
|
41
64
|
max_chronicle_size_kb: z.number().int().min(1).max(1024).optional().default(64),
|
|
65
|
+
/**
|
|
66
|
+
* @deprecated Remplacé par requiresOsGrant() — ce flag n'a aucun effet sur la sécurité.
|
|
67
|
+
* L'OS décide toujours en fonction des scopes déclarés (MN-004).
|
|
68
|
+
*/
|
|
42
69
|
requires_consent: z.boolean().optional().default(false),
|
|
43
70
|
description: z.string().max(256).optional()
|
|
44
|
-
})
|
|
71
|
+
});
|
|
45
72
|
function loadManifest(pathOrManifest) {
|
|
46
73
|
if (typeof pathOrManifest === "string") {
|
|
47
74
|
const absPath = resolve(pathOrManifest);
|
|
@@ -65,6 +92,12 @@ ${issues}`);
|
|
|
65
92
|
return result.data;
|
|
66
93
|
}
|
|
67
94
|
function assertScope(manifest, scope) {
|
|
95
|
+
if (scope.startsWith("vault:read:") && !["DEV", "SOCIAL", "PERSONAL", "FINANCE", "RESEARCH"].includes(scope.split(":")[2] ?? "")) {
|
|
96
|
+
if (manifest.scopes.includes("vault:read:CUSTOM")) return;
|
|
97
|
+
}
|
|
98
|
+
if (scope.startsWith("vault:write:") && !["DEV", "SOCIAL", "PERSONAL", "FINANCE", "RESEARCH"].includes(scope.split(":")[2] ?? "")) {
|
|
99
|
+
if (manifest.scopes.includes("vault:write:CUSTOM")) return;
|
|
100
|
+
}
|
|
68
101
|
if (!manifest.scopes.includes(scope)) {
|
|
69
102
|
throw new Error(
|
|
70
103
|
`[MnemoSDK] Scope "${scope}" not declared in manifest for app "${manifest.id}". Add it to manifest.scopes to request this permission.`
|
|
@@ -78,15 +111,37 @@ function assertVault(manifest, vault) {
|
|
|
78
111
|
);
|
|
79
112
|
}
|
|
80
113
|
}
|
|
114
|
+
function requiresOsGrant(manifest) {
|
|
115
|
+
return manifest.scopes.some(
|
|
116
|
+
(scope) => GRANT_REQUIRED_SCOPE_PREFIXES.some((prefix) => scope.startsWith(prefix))
|
|
117
|
+
);
|
|
118
|
+
}
|
|
81
119
|
|
|
82
120
|
// src/jwt.ts
|
|
83
121
|
import { createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
84
122
|
function b64url(input) {
|
|
85
|
-
|
|
86
|
-
|
|
123
|
+
if (typeof Buffer !== "undefined" && typeof Buffer.alloc(0).toString("base64url") === "string") {
|
|
124
|
+
try {
|
|
125
|
+
const buf = typeof input === "string" ? Buffer.from(input, "utf8") : Buffer.from(input);
|
|
126
|
+
return buf.toString("base64url");
|
|
127
|
+
} catch {
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const bytes = typeof input === "string" ? new TextEncoder().encode(input) : input;
|
|
131
|
+
let b64 = "";
|
|
132
|
+
if (typeof btoa !== "undefined") {
|
|
133
|
+
b64 = btoa(String.fromCharCode(...Array.from(bytes)));
|
|
134
|
+
} else {
|
|
135
|
+
b64 = Buffer.from(bytes).toString("base64");
|
|
136
|
+
}
|
|
137
|
+
return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
87
138
|
}
|
|
88
139
|
function parseB64url(input) {
|
|
89
|
-
|
|
140
|
+
const base64 = input.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - input.length % 4) % 4);
|
|
141
|
+
if (typeof atob !== "undefined") {
|
|
142
|
+
return decodeURIComponent(escape(atob(base64)));
|
|
143
|
+
}
|
|
144
|
+
return Buffer.from(base64, "base64").toString("utf8");
|
|
90
145
|
}
|
|
91
146
|
var HEADER = b64url(JSON.stringify({ alg: "HS256", typ: "JWT" }));
|
|
92
147
|
function signToken(payload, secret) {
|
|
@@ -199,8 +254,8 @@ var MnemoClient = class _MnemoClient {
|
|
|
199
254
|
}
|
|
200
255
|
_connectWs() {
|
|
201
256
|
return new Promise((resolve2, reject) => {
|
|
202
|
-
const url = `ws://
|
|
203
|
-
this.ws = new
|
|
257
|
+
const url = `ws://127.0.0.1:${this.options.wsPort}`;
|
|
258
|
+
this.ws = new WebSocket2(url);
|
|
204
259
|
const timeout = setTimeout(() => {
|
|
205
260
|
this.ws?.terminate();
|
|
206
261
|
reject(new Error(`[MnemoSDK] Connection timeout to ${url}`));
|
|
@@ -249,7 +304,7 @@ var MnemoClient = class _MnemoClient {
|
|
|
249
304
|
}
|
|
250
305
|
_rpcWs(method, params) {
|
|
251
306
|
return new Promise((resolve2, reject) => {
|
|
252
|
-
if (!this.ws || this.ws.readyState !==
|
|
307
|
+
if (!this.ws || this.ws.readyState !== WebSocket2.OPEN) {
|
|
253
308
|
return reject(new Error("[MnemoSDK] WebSocket not open"));
|
|
254
309
|
}
|
|
255
310
|
const id = Math.random().toString(36).slice(2);
|
|
@@ -349,7 +404,13 @@ var MnemoClient = class _MnemoClient {
|
|
|
349
404
|
text,
|
|
350
405
|
vault,
|
|
351
406
|
limit: options.limit ?? 10,
|
|
352
|
-
threshold: options.threshold ?? 0
|
|
407
|
+
threshold: options.threshold ?? 0,
|
|
408
|
+
// [SDK 1.2 — Semantic Bridge] Forward opt-in semantic params. Server
|
|
409
|
+
// ignores absent fields, so omitting these preserves legacy "recent"
|
|
410
|
+
// behaviour exactly.
|
|
411
|
+
...options.semantic !== void 0 ? { semantic: options.semantic } : {},
|
|
412
|
+
...options.scope !== void 0 ? { scope: options.scope } : {},
|
|
413
|
+
...options.spineTypeFilter !== void 0 ? { spineTypeFilter: options.spineTypeFilter } : {}
|
|
353
414
|
});
|
|
354
415
|
}
|
|
355
416
|
/**
|
|
@@ -407,25 +468,55 @@ var MnemoClient = class _MnemoClient {
|
|
|
407
468
|
get appManifest() {
|
|
408
469
|
return { ...this.manifest };
|
|
409
470
|
}
|
|
410
|
-
/**
|
|
471
|
+
/**
|
|
472
|
+
* Inspecte le token actuel (décodé, sans vérification cryptographique).
|
|
473
|
+
* Utilise un décodeur base64url universel (Node + Browser) — MN-005.
|
|
474
|
+
*/
|
|
411
475
|
get tokenInfo() {
|
|
412
476
|
if (!this.token) return null;
|
|
413
477
|
try {
|
|
414
478
|
const [, payloadB64] = this.token.split(".");
|
|
415
479
|
if (!payloadB64) return null;
|
|
416
|
-
|
|
480
|
+
const base64 = payloadB64.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - payloadB64.length % 4) % 4);
|
|
481
|
+
const json = typeof atob !== "undefined" ? decodeURIComponent(escape(atob(base64))) : Buffer.from(base64, "base64").toString("utf8");
|
|
482
|
+
return JSON.parse(json);
|
|
417
483
|
} catch {
|
|
418
484
|
return null;
|
|
419
485
|
}
|
|
420
486
|
}
|
|
487
|
+
/**
|
|
488
|
+
* [PHASE-50] Lit un fichier .md depuis le repo OS (docs/, packages/).
|
|
489
|
+
*/
|
|
490
|
+
async readFile(path) {
|
|
491
|
+
const res = await this._rpc("sdk.readFile", { path });
|
|
492
|
+
return res.content;
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Returns the most recent git commits from the OS monorepo.
|
|
496
|
+
* Calls the server-side `sdk.git.log` RPC (repo path is hardcoded server-side
|
|
497
|
+
* for Zero-Trust — the client cannot choose it).
|
|
498
|
+
* Requires scope `monorepo:read` and intent `GIT_LOG` in the manifest.
|
|
499
|
+
*/
|
|
500
|
+
async gitLog(options = {}) {
|
|
501
|
+
assertScope(this.manifest, "monorepo:read");
|
|
502
|
+
if (!this.manifest.intents.includes("GIT_LOG")) {
|
|
503
|
+
throw new Error('[MnemoSDK] Intent "GIT_LOG" not declared in manifest');
|
|
504
|
+
}
|
|
505
|
+
return this._rpc("sdk.git.log", {
|
|
506
|
+
appId: this.manifest.id,
|
|
507
|
+
limit: options.limit ?? 20,
|
|
508
|
+
since: options.since,
|
|
509
|
+
filter: options.filter
|
|
510
|
+
});
|
|
511
|
+
}
|
|
421
512
|
};
|
|
422
513
|
|
|
423
514
|
// src/constants.ts
|
|
424
515
|
var MNEMOSYNE_METHODS = {
|
|
425
|
-
// ── Auth
|
|
516
|
+
// ── Auth ───────────────────────────────────────────────────────────────
|
|
426
517
|
/** Enregistre l'app, valide le manifest, retourne un JWT 24h */
|
|
427
518
|
REGISTER: "sdk.register",
|
|
428
|
-
// ── Vault
|
|
519
|
+
// ── Vault ───────────────────────────────────────────────────────────────
|
|
429
520
|
/** Ingère du contenu dans le vault (vectorisation incluse) */
|
|
430
521
|
INGEST: "sdk.ingest",
|
|
431
522
|
/** Requête sémantique — résultats filtrés par source_app_id */
|
|
@@ -434,22 +525,84 @@ var MNEMOSYNE_METHODS = {
|
|
|
434
525
|
CORRELATE: "sdk.correlate",
|
|
435
526
|
/** Supprime un chronicle par ID (scope vault:write requis) */
|
|
436
527
|
FORGET: "sdk.forget",
|
|
437
|
-
// ──
|
|
528
|
+
// ── Resonances ───────────────────────────────────────────────────────────
|
|
529
|
+
/**
|
|
530
|
+
* Liste les Resonances actives (projets cognitifs) depuis le vault DEV.
|
|
531
|
+
* Requiert le scope `vault:read:DEV`.
|
|
532
|
+
*/
|
|
533
|
+
RESONANCES_LIST: "sdk.resonances.list",
|
|
534
|
+
/**
|
|
535
|
+
* Met à jour la position courante d'une Resonance (phase, description).
|
|
536
|
+
* Persisté comme chronicle DECISION dans le vault DEV.
|
|
537
|
+
* Requiert le scope `vault:write:DEV`.
|
|
538
|
+
*/
|
|
539
|
+
UPDATE_POSITION: "sdk.resonance.updatePosition",
|
|
540
|
+
// ── Monorepo ───────────────────────────────────────────────────────────
|
|
541
|
+
/**
|
|
542
|
+
* Lit le git log du monorepo Mnemosyne OS.
|
|
543
|
+
* Requiert le scope `monorepo:read` dans le manifest.
|
|
544
|
+
* Retourne les commits filtrés (hash, message, author, date).
|
|
545
|
+
* [SECURITY] Path hardcodé côté serveur — le client ne contrôle pas le repo.
|
|
546
|
+
*/
|
|
547
|
+
GIT_LOG: "sdk.git.log",
|
|
548
|
+
/**
|
|
549
|
+
* Lit un fichier .md depuis le repo Mnemosyne OS (docs/, packages/).
|
|
550
|
+
* Requiert le scope `monorepo:read`.
|
|
551
|
+
* [SECURITY] Seuls les .md sont lisibles, path sanitizé côté serveur.
|
|
552
|
+
*/
|
|
553
|
+
READ_FILE: "sdk.readFile",
|
|
554
|
+
// ── Agents ───────────────────────────────────────────────────────────────
|
|
555
|
+
/**
|
|
556
|
+
* Liste les agents actifs connectés au SDK WebSocket Server.
|
|
557
|
+
* Requiert le scope `agents:read` dans le manifest.
|
|
558
|
+
*/
|
|
559
|
+
LIST_AGENTS: "sdk.agents.list",
|
|
560
|
+
// ── Cross-App ──────────────────────────────────────────────────────────
|
|
438
561
|
/** Demande d'accès aux chronicles d'une autre app (popup consentement) */
|
|
439
562
|
SHARE: "sdk.share",
|
|
440
|
-
// ── NFT Licence
|
|
563
|
+
// ── NFT Licence ─────────────────────────────────────────────────────────
|
|
441
564
|
/**
|
|
442
565
|
* Vérifie qu'un wallet possède le NFT de licence de cette app.
|
|
443
566
|
* Requiert le scope `nft:validate` dans le manifest.
|
|
444
567
|
* Cache TTL 5 min côté OS pour ne pas spam la blockchain.
|
|
445
568
|
*/
|
|
446
569
|
NFT_VALIDATE: "sdk.nft.validate",
|
|
447
|
-
// ── NeuralGraph
|
|
570
|
+
// ── NeuralGraph ─────────────────────────────────────────────────────────
|
|
571
|
+
/**
|
|
572
|
+
* Queries the NeuralGraph — returns nodes and edges near the given text.
|
|
573
|
+
* Requires scope `neural:graph:read`.
|
|
574
|
+
*/
|
|
575
|
+
GRAPH_QUERY: "sdk.graph.query",
|
|
576
|
+
// ── Vault Discovery ─────────────────────────────────────────────────────
|
|
448
577
|
/**
|
|
449
|
-
*
|
|
450
|
-
*
|
|
578
|
+
* Lists all available vaults (core built-ins + user-created custom vaults).
|
|
579
|
+
* Requires intent `LIST_VAULTS`.
|
|
580
|
+
* Returns `VaultListResult` with `coreVaults` and `customVaults` arrays.
|
|
581
|
+
* Use the `id` field of each vault as the `vault` param in ingest/query.
|
|
451
582
|
*/
|
|
452
|
-
|
|
583
|
+
LIST_VAULTS: "sdk.vaults.list",
|
|
584
|
+
// ── Correlate ─────────────────────────────────────────────────────────
|
|
585
|
+
// (CORRELATE and FORGET are already declared in the Vault section above)
|
|
586
|
+
// ── Forget (GDPR) ────────────────────────────────────────────────────────
|
|
587
|
+
// (See FORGET above)
|
|
588
|
+
// ── Dynamic Spine Registry ────────────────────────────────────────────────
|
|
589
|
+
/**
|
|
590
|
+
* Allocates a new dynamic spine in the OS neural registry (Protocole du Guichet).
|
|
591
|
+
* Requires scope `vault:write:CUSTOM`.
|
|
592
|
+
* The allocated spine appears in the Mnemosyne OS neural graph.
|
|
593
|
+
*/
|
|
594
|
+
REGISTER_SPINE: "sdk.spine.register",
|
|
595
|
+
/**
|
|
596
|
+
* Releases a previously allocated dynamic spine.
|
|
597
|
+
* Only the owning app can release its own spines.
|
|
598
|
+
* Requires scope `vault:write:CUSTOM`.
|
|
599
|
+
*/
|
|
600
|
+
RELEASE_SPINE: "sdk.spine.release",
|
|
601
|
+
/**
|
|
602
|
+
* Lists all currently allocated dynamic spines across all registered apps.
|
|
603
|
+
* Returns an array of `DynamicSpineInfo` objects.
|
|
604
|
+
*/
|
|
605
|
+
LIST_SPINES: "sdk.spine.list"
|
|
453
606
|
};
|
|
454
607
|
var MNEMOSYNE_WS_PORT = 7799;
|
|
455
608
|
var MNEMOSYNE_WS_HOST = "127.0.0.1";
|
|
@@ -460,8 +613,414 @@ var MNEMOSYNE_CHAINS = {
|
|
|
460
613
|
POLYGON: { id: 137, name: "Polygon", rpc: "https://polygon-rpc.com" },
|
|
461
614
|
BASE_SEPOLIA: { id: 84532, name: "Base Sepolia (testnet)", rpc: "https://sepolia.base.org" }
|
|
462
615
|
};
|
|
616
|
+
|
|
617
|
+
// src/browser-client.ts
|
|
618
|
+
var MnemoClientBrowser = class _MnemoClientBrowser {
|
|
619
|
+
ws;
|
|
620
|
+
token = null;
|
|
621
|
+
pending = /* @__PURE__ */ new Map();
|
|
622
|
+
_onPush;
|
|
623
|
+
_onDisconnect;
|
|
624
|
+
timeoutMs;
|
|
625
|
+
constructor(ws, timeoutMs) {
|
|
626
|
+
this.ws = ws;
|
|
627
|
+
this.timeoutMs = timeoutMs;
|
|
628
|
+
this.ws.onmessage = (e) => this._handleMsg(e.data);
|
|
629
|
+
this.ws.onclose = () => {
|
|
630
|
+
this._onDisconnect?.();
|
|
631
|
+
for (const [, p] of this.pending) {
|
|
632
|
+
clearTimeout(p.timer);
|
|
633
|
+
p.reject(new Error("[MnemoClientBrowser] WebSocket disconnected"));
|
|
634
|
+
}
|
|
635
|
+
this.pending.clear();
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
// ── Static factory ───────────────────────────────────────────────────────────
|
|
639
|
+
/**
|
|
640
|
+
* Crée une connexion WebSocket native vers le SDK server de Mnemosyne OS.
|
|
641
|
+
*
|
|
642
|
+
* @param host - Hostname du SDK server (défaut: '127.0.0.1')
|
|
643
|
+
* @param port - Port du SDK server (défaut: 7799)
|
|
644
|
+
* @param timeoutMs - Timeout de chaque RPC en ms (défaut: 15000)
|
|
645
|
+
*/
|
|
646
|
+
static connect(host = MNEMOSYNE_WS_HOST, port = MNEMOSYNE_WS_PORT, timeoutMs = 15e3) {
|
|
647
|
+
return new Promise((resolve2, reject) => {
|
|
648
|
+
const url = `ws://${host}:${port}`;
|
|
649
|
+
const ws = new WebSocket(url);
|
|
650
|
+
const timeout = setTimeout(() => {
|
|
651
|
+
ws.close();
|
|
652
|
+
reject(new Error(`[MnemoClientBrowser] Connection timeout to ${url}`));
|
|
653
|
+
}, timeoutMs);
|
|
654
|
+
ws.onopen = () => {
|
|
655
|
+
clearTimeout(timeout);
|
|
656
|
+
resolve2(new _MnemoClientBrowser(ws, timeoutMs));
|
|
657
|
+
};
|
|
658
|
+
ws.onerror = () => {
|
|
659
|
+
clearTimeout(timeout);
|
|
660
|
+
reject(new Error(`[MnemoClientBrowser] Failed to connect to ${url} \u2014 is Mnemosyne OS running?`));
|
|
661
|
+
};
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
// ── Message router ───────────────────────────────────────────────────────────
|
|
665
|
+
_handleMsg(raw) {
|
|
666
|
+
try {
|
|
667
|
+
const msg = JSON.parse(raw);
|
|
668
|
+
if (msg.push && msg.event) {
|
|
669
|
+
this._onPush?.(msg.event);
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
const p = this.pending.get(msg.id);
|
|
673
|
+
if (!p) return;
|
|
674
|
+
clearTimeout(p.timer);
|
|
675
|
+
this.pending.delete(msg.id);
|
|
676
|
+
if (msg.error) p.reject(new Error(msg.error));
|
|
677
|
+
else p.resolve(msg.result);
|
|
678
|
+
} catch {
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
// ── Low-level RPC ────────────────────────────────────────────────────────────
|
|
682
|
+
call(method, params) {
|
|
683
|
+
return new Promise((resolve2, reject) => {
|
|
684
|
+
if (this.ws.readyState !== WebSocket.OPEN) {
|
|
685
|
+
return reject(new Error("[MnemoClientBrowser] WebSocket not open"));
|
|
686
|
+
}
|
|
687
|
+
const id = Math.random().toString(36).slice(2);
|
|
688
|
+
const req = {
|
|
689
|
+
id,
|
|
690
|
+
method,
|
|
691
|
+
params,
|
|
692
|
+
...this.token ? { token: this.token } : {}
|
|
693
|
+
};
|
|
694
|
+
const timer = setTimeout(() => {
|
|
695
|
+
this.pending.delete(id);
|
|
696
|
+
reject(new Error(`[MnemoClientBrowser] RPC timeout: ${method} (${this.timeoutMs}ms)`));
|
|
697
|
+
}, this.timeoutMs);
|
|
698
|
+
this.pending.set(id, {
|
|
699
|
+
resolve: (v) => resolve2(v),
|
|
700
|
+
reject,
|
|
701
|
+
timer
|
|
702
|
+
});
|
|
703
|
+
this.ws.send(JSON.stringify(req));
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
// ── Auth ─────────────────────────────────────────────────────────────────────
|
|
707
|
+
/**
|
|
708
|
+
* Enregistre l'app auprès de Mnemosyne OS et obtient un JWT 24h.
|
|
709
|
+
* Doit être appelé avant toute autre méthode.
|
|
710
|
+
*
|
|
711
|
+
* @throws si le manifest est invalide ou si l'OS refuse l'enregistrement
|
|
712
|
+
*/
|
|
713
|
+
async register(manifest) {
|
|
714
|
+
const res = await this.call("sdk.register", { manifest });
|
|
715
|
+
this.token = res.token;
|
|
716
|
+
return res;
|
|
717
|
+
}
|
|
718
|
+
// ── Vault ─────────────────────────────────────────────────────────────────────
|
|
719
|
+
/**
|
|
720
|
+
* Ingère du contenu dans le vault Mnemosyne.
|
|
721
|
+
* Le contenu est vectorisé par le runtime OS (pas dans le SDK).
|
|
722
|
+
* Requiert le scope `vault:write:<vault>` et l'intent `INGEST`.
|
|
723
|
+
*/
|
|
724
|
+
async ingest(content, spineType, vault = "DEV", metadata) {
|
|
725
|
+
const res = await this.call("sdk.ingest", {
|
|
726
|
+
appId: this.appId ?? "unknown",
|
|
727
|
+
content,
|
|
728
|
+
spineType,
|
|
729
|
+
vault,
|
|
730
|
+
...metadata ? { metadata } : {}
|
|
731
|
+
});
|
|
732
|
+
return res;
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Requête sémantique sur le vault — retourne les chronicles les plus proches.
|
|
736
|
+
* Requiert le scope `vault:read:<vault>` et l'intent `QUERY`.
|
|
737
|
+
*
|
|
738
|
+
* @param text - Texte de recherche
|
|
739
|
+
* @param vault - Vault cible (défaut: 'DEV')
|
|
740
|
+
* @param limit - Nombre max de résultats (défaut: 10)
|
|
741
|
+
* @param options - [SDK 1.2] Options sémantiques additionnelles (semantic,
|
|
742
|
+
* scope, spineTypeFilter). Voir {@link QueryOptions}.
|
|
743
|
+
*/
|
|
744
|
+
async query(text, vault = "DEV", limit = 10, options = {}) {
|
|
745
|
+
const res = await this.call("sdk.query", {
|
|
746
|
+
appId: this.appId ?? "unknown",
|
|
747
|
+
text,
|
|
748
|
+
vault,
|
|
749
|
+
limit,
|
|
750
|
+
// [SDK 1.2 — Semantic Bridge] Forward optional semantic params.
|
|
751
|
+
...options.semantic !== void 0 ? { semantic: options.semantic } : {},
|
|
752
|
+
...options.scope !== void 0 ? { scope: options.scope } : {},
|
|
753
|
+
...options.spineTypeFilter !== void 0 ? { spineTypeFilter: options.spineTypeFilter } : {}
|
|
754
|
+
});
|
|
755
|
+
if (!res.success) throw new Error(res.error ?? "query failed");
|
|
756
|
+
return res.chronicles ?? [];
|
|
757
|
+
}
|
|
758
|
+
// ── Resonances ────────────────────────────────────────────────────────────────
|
|
759
|
+
/**
|
|
760
|
+
* Returns the active Resonances (cognitive projects) from the DEV vault.
|
|
761
|
+
* Requires scope `vault:read:DEV`.
|
|
762
|
+
*/
|
|
763
|
+
async resonancesList() {
|
|
764
|
+
const res = await this.call(
|
|
765
|
+
"sdk.resonances.list",
|
|
766
|
+
{ appId: this.appId ?? "unknown" }
|
|
767
|
+
);
|
|
768
|
+
return res.success ? res.resonances ?? [] : [];
|
|
769
|
+
}
|
|
770
|
+
/**
|
|
771
|
+
* Persists the current position of a Resonance in the DEV vault.
|
|
772
|
+
* Requires scope `vault:write:DEV`.
|
|
773
|
+
*
|
|
774
|
+
* @param resonanceId - Resonance identifier
|
|
775
|
+
* @param position - Current position description (e.g. 'Phase 51.2 — auto-poll')
|
|
776
|
+
* @param phase - Current phase (e.g. 'Phase 51')
|
|
777
|
+
*/
|
|
778
|
+
async updatePosition(resonanceId, position, phase) {
|
|
779
|
+
return this.call("sdk.resonance.updatePosition", {
|
|
780
|
+
appId: this.appId ?? "unknown",
|
|
781
|
+
resonanceId,
|
|
782
|
+
position,
|
|
783
|
+
phase
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
// ── Monorepo ──────────────────────────────────────────────────────────────────
|
|
787
|
+
/**
|
|
788
|
+
* Retourne les commits récents du monorepo Mnemosyne OS.
|
|
789
|
+
* Requiert le scope `monorepo:read` et l'intent `GIT_LOG`.
|
|
790
|
+
* [SECURITY] Le chemin du repo est hardcodé côté serveur.
|
|
791
|
+
*
|
|
792
|
+
* @param limit - Nombre de commits max (défaut: 20)
|
|
793
|
+
* @param since - Date relative Git (ex: '30 days ago', '2024-01-01')
|
|
794
|
+
*/
|
|
795
|
+
async gitLog(limit = 20, since) {
|
|
796
|
+
const res = await this.call(
|
|
797
|
+
"sdk.git.log",
|
|
798
|
+
{ appId: this.appId ?? "unknown", limit, since }
|
|
799
|
+
);
|
|
800
|
+
return res.success ? res.commits ?? [] : [];
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* Lit un fichier `.md` depuis le repo Mnemosyne OS.
|
|
804
|
+
* Requiert le scope `monorepo:read`.
|
|
805
|
+
* [SECURITY] Seuls les fichiers `.md` sont accessibles, path sanitizé côté serveur.
|
|
806
|
+
*
|
|
807
|
+
* @param path - Chemin relatif au repo (ex: 'docs/ARCHITECTURE.md')
|
|
808
|
+
*/
|
|
809
|
+
async readFile(path) {
|
|
810
|
+
const res = await this.call("sdk.readFile", { path });
|
|
811
|
+
return res.content ?? "";
|
|
812
|
+
}
|
|
813
|
+
// ── Agents ──────────────────────────────────────────────────────────────────────
|
|
814
|
+
/**
|
|
815
|
+
* Lists Layer 2 apps currently connected to the SDK WebSocket Server.
|
|
816
|
+
* Requires scope `agents:read` and intent `LIST_AGENTS`.
|
|
817
|
+
*/
|
|
818
|
+
async agentsList() {
|
|
819
|
+
const res = await this.call(
|
|
820
|
+
"sdk.agents.list",
|
|
821
|
+
{ appId: this.appId ?? "unknown" }
|
|
822
|
+
);
|
|
823
|
+
return res.success ? res.agents ?? [] : [];
|
|
824
|
+
}
|
|
825
|
+
// ── Vault Discovery [v1.2.0] ────────────────────────────────────────────────────
|
|
826
|
+
/**
|
|
827
|
+
* Returns all available vaults (core built-ins + user-created custom vaults).
|
|
828
|
+
* Use the `id` field of each entry as the `vault` param in `ingest()` and `query()`.
|
|
829
|
+
* Requires intent `LIST_VAULTS`.
|
|
830
|
+
*/
|
|
831
|
+
async vaultsList() {
|
|
832
|
+
return this.call("sdk.vaults.list", { appId: this.appId ?? "unknown" });
|
|
833
|
+
}
|
|
834
|
+
// ── Correlate [v1.2.0] ──────────────────────────────────────────────────────────
|
|
835
|
+
/**
|
|
836
|
+
* Finds chronicles causally or semantically linked to a source chronicle.
|
|
837
|
+
* Requires intent `CORRELATE` and the appropriate vault read scope.
|
|
838
|
+
*
|
|
839
|
+
* @param options - `{ sourceId, vault?, limit?, threshold? }`
|
|
840
|
+
*/
|
|
841
|
+
async correlate(options) {
|
|
842
|
+
return this.call("sdk.correlate", {
|
|
843
|
+
appId: this.appId ?? "unknown",
|
|
844
|
+
...options
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
// ── Forget / GDPR [v1.2.0] ──────────────────────────────────────────────────────
|
|
848
|
+
/**
|
|
849
|
+
* Permanently deletes a chronicle by ID. Requires intent `FORGET`
|
|
850
|
+
* and the appropriate vault write scope.
|
|
851
|
+
*
|
|
852
|
+
* @param chronicleId - ID of the chronicle to delete.
|
|
853
|
+
* @param vault - Vault to delete from (default: 'DEV').
|
|
854
|
+
*/
|
|
855
|
+
async forget(chronicleId, vault = "DEV") {
|
|
856
|
+
return this.call("sdk.forget", {
|
|
857
|
+
appId: this.appId ?? "unknown",
|
|
858
|
+
chronicleId,
|
|
859
|
+
vault
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
// ── NeuralGraph [v1.2.0] ────────────────────────────────────────────────────────
|
|
863
|
+
/**
|
|
864
|
+
* Queries the NeuralGraph — returns nodes and edges near the given text.
|
|
865
|
+
* Requires scope `neural:graph:read`.
|
|
866
|
+
*
|
|
867
|
+
* @param text - Search text to embed and match against the graph.
|
|
868
|
+
* @param threshold - Minimum edge strength 0-1 (default: 0.5).
|
|
869
|
+
*/
|
|
870
|
+
async graphQuery(text, threshold = 0.5) {
|
|
871
|
+
return this.call("sdk.graph.query", {
|
|
872
|
+
appId: this.appId ?? "unknown",
|
|
873
|
+
text,
|
|
874
|
+
threshold
|
|
875
|
+
});
|
|
876
|
+
}
|
|
877
|
+
// ── Dynamic Spine Registry [v1.2.0] ──────────────────────────────────────────────
|
|
878
|
+
/**
|
|
879
|
+
* Allocates a new dynamic spine in the OS Protocole du Guichet registry.
|
|
880
|
+
* Requires scope `vault:write:CUSTOM`.
|
|
881
|
+
*
|
|
882
|
+
* @example
|
|
883
|
+
* ```ts
|
|
884
|
+
* await client.spineRegister({
|
|
885
|
+
* id: 'my_events', name: 'App Events', type: 'SESSION', icon: '📊'
|
|
886
|
+
* });
|
|
887
|
+
* ```
|
|
888
|
+
*/
|
|
889
|
+
async spineRegister(spine) {
|
|
890
|
+
return this.call("sdk.spine.register", {
|
|
891
|
+
appId: this.appId ?? "unknown",
|
|
892
|
+
spine
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* Releases a dynamic spine previously allocated by this app.
|
|
897
|
+
* Requires scope `vault:write:CUSTOM`.
|
|
898
|
+
*/
|
|
899
|
+
async spineRelease(spineId) {
|
|
900
|
+
return this.call("sdk.spine.release", {
|
|
901
|
+
appId: this.appId ?? "unknown",
|
|
902
|
+
spineId
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
/**
|
|
906
|
+
* Lists all currently allocated dynamic spines across all apps.
|
|
907
|
+
*/
|
|
908
|
+
async spineList() {
|
|
909
|
+
const res = await this.call(
|
|
910
|
+
"sdk.spine.list",
|
|
911
|
+
{ appId: this.appId ?? "unknown" }
|
|
912
|
+
);
|
|
913
|
+
return res.success ? res.spines ?? [] : [];
|
|
914
|
+
}
|
|
915
|
+
// ── [PHASE-58] Perpetual Memory Bridges [v2.0] ───────────────────────────────────────
|
|
916
|
+
/**
|
|
917
|
+
* Returns the persistent semantic bridges stored in the user's vault.
|
|
918
|
+
* Bridges are cognitive links discovered by Semantic Reflect scans between
|
|
919
|
+
* cross-vault chronicles (e.g. SOCIAL ⇔ DEV, PERSONAL ⇔ DEV).
|
|
920
|
+
*
|
|
921
|
+
* [PHASE-58][PLATFORM][READ-ONLY]
|
|
922
|
+
*
|
|
923
|
+
* Required manifest: `scopes: ['bridge:read']`, `intents: ['BRIDGE_READ']`
|
|
924
|
+
*
|
|
925
|
+
* @param options - Optional filters: `{ limit, since, minCosine, vaultFilter, onlyNew }`
|
|
926
|
+
*
|
|
927
|
+
* @example
|
|
928
|
+
* ```typescript
|
|
929
|
+
* const result = await client.getBridgeHistory({ minCosine: 0.80, limit: 50 });
|
|
930
|
+
* console.log(`${result.bridges.length} high-confidence bridges`);
|
|
931
|
+
* ```
|
|
932
|
+
*/
|
|
933
|
+
async getBridgeHistory(options) {
|
|
934
|
+
return this.call("sdk.bridges.history", {
|
|
935
|
+
appId: this.appId ?? "unknown",
|
|
936
|
+
...options
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* Computes a resonance score for a text against the user's bridge history.
|
|
941
|
+
* Use this to measure cognitive relevance before publishing a social post.
|
|
942
|
+
*
|
|
943
|
+
* [PHASE-58][PLATFORM][READ-ONLY]
|
|
944
|
+
*
|
|
945
|
+
* Required manifest: `scopes: ['bridge:read']`, `intents: ['BRIDGE_READ']`
|
|
946
|
+
*
|
|
947
|
+
* @param text - Text to score (e.g. LinkedIn/Reddit post draft).
|
|
948
|
+
* @param topK - Number of top matches to return (default: 5).
|
|
949
|
+
*
|
|
950
|
+
* @returns { score: 0-1, level: 'LOW'|'MEDIUM'|'HIGH_RESONANCE', topMatches }
|
|
951
|
+
*
|
|
952
|
+
* @example
|
|
953
|
+
* ```typescript
|
|
954
|
+
* const score = await client.computeResonance(postDraft, 5);
|
|
955
|
+
* if (score.level === 'HIGH_RESONANCE') {
|
|
956
|
+
* console.log('✨ This post aligns with your cognitive graph!');
|
|
957
|
+
* }
|
|
958
|
+
* ```
|
|
959
|
+
*/
|
|
960
|
+
async computeResonance(text, topK = 5) {
|
|
961
|
+
return this.call("sdk.bridges.resonance", {
|
|
962
|
+
appId: this.appId ?? "unknown",
|
|
963
|
+
text,
|
|
964
|
+
topK
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
// ── Events ────────────────────────────────────────────────────────────────────
|
|
968
|
+
/**
|
|
969
|
+
* Enregistre un handler pour les push events envoyés par l'OS.
|
|
970
|
+
* Déclenché par exemple quand un autre client ingère un chronicle (`chronicle:new`).
|
|
971
|
+
*
|
|
972
|
+
* @example
|
|
973
|
+
* ```typescript
|
|
974
|
+
* client.onPush((event) => {
|
|
975
|
+
* if (event.type === 'chronicle:new') {
|
|
976
|
+
* console.log('New chronicle from:', event.sourceApp);
|
|
977
|
+
* }
|
|
978
|
+
* });
|
|
979
|
+
* ```
|
|
980
|
+
*/
|
|
981
|
+
onPush(fn) {
|
|
982
|
+
this._onPush = fn;
|
|
983
|
+
}
|
|
984
|
+
/**
|
|
985
|
+
* Enregistre un callback appelé quand la connexion WS est fermée par l'OS.
|
|
986
|
+
*/
|
|
987
|
+
onDisconnect(fn) {
|
|
988
|
+
this._onDisconnect = fn;
|
|
989
|
+
}
|
|
990
|
+
// ── Lifecycle ─────────────────────────────────────────────────────────────────
|
|
991
|
+
/**
|
|
992
|
+
* Ferme la connexion WebSocket proprement.
|
|
993
|
+
*/
|
|
994
|
+
close() {
|
|
995
|
+
this.ws.close(1e3, "Client close");
|
|
996
|
+
}
|
|
997
|
+
// ── Getters ───────────────────────────────────────────────────────────────────
|
|
998
|
+
get isConnected() {
|
|
999
|
+
return this.ws.readyState === WebSocket.OPEN;
|
|
1000
|
+
}
|
|
1001
|
+
/** Token JWT actuel (null avant register()) */
|
|
1002
|
+
get currentToken() {
|
|
1003
|
+
return this.token;
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* AppId extrait du JWT actuel, ou null si non enregistré.
|
|
1007
|
+
*/
|
|
1008
|
+
get appId() {
|
|
1009
|
+
if (!this.token) return null;
|
|
1010
|
+
try {
|
|
1011
|
+
const [, b64] = this.token.split(".");
|
|
1012
|
+
if (!b64) return null;
|
|
1013
|
+
const padded = b64.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - b64.length % 4) % 4);
|
|
1014
|
+
const payload = JSON.parse(atob(padded));
|
|
1015
|
+
return payload.sub ?? null;
|
|
1016
|
+
} catch {
|
|
1017
|
+
return null;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
};
|
|
463
1021
|
export {
|
|
464
1022
|
AppManifestSchema,
|
|
1023
|
+
GRANT_REQUIRED_SCOPE_PREFIXES,
|
|
465
1024
|
MNEMOSYNE_CHAINS,
|
|
466
1025
|
MNEMOSYNE_METHODS,
|
|
467
1026
|
MNEMOSYNE_NFT_CACHE_TTL_MS,
|
|
@@ -469,11 +1028,13 @@ export {
|
|
|
469
1028
|
MNEMOSYNE_WS_HOST,
|
|
470
1029
|
MNEMOSYNE_WS_PORT,
|
|
471
1030
|
MnemoClient,
|
|
1031
|
+
MnemoClientBrowser,
|
|
472
1032
|
assertScope,
|
|
473
1033
|
assertVault,
|
|
474
1034
|
generateAppToken,
|
|
475
1035
|
generateSecret,
|
|
476
1036
|
loadManifest,
|
|
477
1037
|
parseManifest,
|
|
1038
|
+
requiresOsGrant,
|
|
478
1039
|
verifyToken
|
|
479
1040
|
};
|