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