@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/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/client.ts
2
- import WebSocket from "ws";
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:COOK",
18
- "vault:write:COOK",
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 VALID_INTENTS = ["INGEST", "QUERY", "CORRELATE", "FORGET"];
23
- var VALID_VAULTS = ["DEV", "SOCIAL", "PERSONAL", "FINANCE", "COOK"];
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
- vaults: z.array(z.enum(VALID_VAULTS)).min(1),
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
- }).strict();
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
- const buf = typeof input === "string" ? Buffer.from(input, "utf8") : input;
86
- return buf.toString("base64url");
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
- return Buffer.from(input, "base64url").toString("utf8");
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://localhost:${this.options.wsPort}`;
203
- this.ws = new WebSocket(url);
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 !== WebSocket.OPEN) {
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
- /** Inspecte le token actuel (décodé, sans vérification) */
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
- return JSON.parse(Buffer.from(payloadB64, "base64url").toString("utf8"));
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
- // ── Cross-App ─────────────────────────────────────────────────────────────
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
- * Requête le NeuralGraph retourne les nœuds et arêtes proches du texte.
450
- * Requiert le scope `neural:graph:read` dans le manifest.
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
- GRAPH_QUERY: "sdk.graph.query"
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
  };