@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.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:COOK",
68
- "vault:write:COOK",
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 VALID_INTENTS = ["INGEST", "QUERY", "CORRELATE", "FORGET"];
73
- var VALID_VAULTS = ["DEV", "SOCIAL", "PERSONAL", "FINANCE", "COOK"];
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
- vaults: import_zod.z.array(import_zod.z.enum(VALID_VAULTS)).min(1),
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
- }).strict();
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
- const buf = typeof input === "string" ? Buffer.from(input, "utf8") : input;
136
- return buf.toString("base64url");
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
- return Buffer.from(input, "base64url").toString("utf8");
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://localhost:${this.options.wsPort}`;
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
- /** Inspecte le token actuel (décodé, sans vérification) */
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
- return JSON.parse(Buffer.from(payloadB64, "base64url").toString("utf8"));
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
- // ── Cross-App ─────────────────────────────────────────────────────────────
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
- * Requête le NeuralGraph retourne les nœuds et arêtes proches du texte.
500
- * Requiert le scope `neural:graph:read` dans le manifest.
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
- GRAPH_QUERY: "sdk.graph.query"
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
  });