@neurolift-technologies/sleepwalker-protocol 1.0.0 → 1.0.1

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 CHANGED
@@ -1,3 +1,11 @@
1
+ > ## ⚠️ PROTOTYPE — NOT A SAFETY SYSTEM
2
+ >
3
+ > This is an **experimental** crisis-detection library with **stubbed/placeholder intervention layers**. It is **NOT medical advice, NOT a crisis service**, and performs **no real-time monitoring**. It **can miss real crisis signals** (known detection/recall gaps) — **do not rely on it as a safety net or as the sole safety mechanism**.
4
+ >
5
+ > **If you or someone else needs help now:** in the US, call or text **988** (Suicide & Crisis Lifeline) or chat [988lifeline.org](https://988lifeline.org); in an emergency call **911**. Outside the US: [findahelpline.com](https://findahelpline.com).
6
+ >
7
+ > Provided **AS-IS, without warranty**.
8
+
1
9
  # Sleepwalker Protocol (SWP)
2
10
 
3
11
  ```yaml
@@ -1,9 +1,29 @@
1
1
  /**
2
2
  * Temporal Continuity Management Module
3
+ *
4
+ * Maintains emotional state and boundary awareness across sessions.
3
5
  */
4
6
  export declare class ContinuityManager {
5
7
  private storagePath;
6
8
  constructor(storagePath?: string);
9
+ /**
10
+ * Resolve the storage file for a user — traversal-safe and collision-safe.
11
+ *
12
+ * `userId` becomes part of a filename, which raises two risks:
13
+ *
14
+ * - **Path traversal.** A raw value like `"../../etc/passwd"` must not read or
15
+ * write outside `storagePath`.
16
+ * - **Collisions.** Simply stripping disallowed characters would map distinct
17
+ * ids onto the same file (`"a/b"` and `"ab"`, or `"alice@example.com"` and
18
+ * `"aliceexample.com"`), letting one user's continuity overwrite or leak
19
+ * into another's.
20
+ *
21
+ * We therefore key the file on a SHA-256 of the *full* id — deterministic,
22
+ * collision-resistant, and filesystem-safe (hex only) — prefixed with a
23
+ * sanitized, human-readable slug purely for debuggability. Mirrors the Python
24
+ * `ContinuityManager._user_file`.
25
+ */
26
+ private userFile;
7
27
  saveSession(userId: string, sessionData: any): void;
8
28
  getContext(userId: string): any;
9
29
  private loadUserData;
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  /**
3
3
  * Temporal Continuity Management Module
4
+ *
5
+ * Maintains emotional state and boundary awareness across sessions.
4
6
  */
5
7
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
8
  if (k2 === undefined) k2 = k;
@@ -37,6 +39,7 @@ var __importStar = (this && this.__importStar) || (function () {
37
39
  })();
38
40
  Object.defineProperty(exports, "__esModule", { value: true });
39
41
  exports.ContinuityManager = void 0;
42
+ const crypto = __importStar(require("crypto"));
40
43
  const fs = __importStar(require("fs"));
41
44
  const path = __importStar(require("path"));
42
45
  class ContinuityManager {
@@ -46,27 +49,58 @@ class ContinuityManager {
46
49
  fs.mkdirSync(this.storagePath, { recursive: true });
47
50
  }
48
51
  }
52
+ /**
53
+ * Resolve the storage file for a user — traversal-safe and collision-safe.
54
+ *
55
+ * `userId` becomes part of a filename, which raises two risks:
56
+ *
57
+ * - **Path traversal.** A raw value like `"../../etc/passwd"` must not read or
58
+ * write outside `storagePath`.
59
+ * - **Collisions.** Simply stripping disallowed characters would map distinct
60
+ * ids onto the same file (`"a/b"` and `"ab"`, or `"alice@example.com"` and
61
+ * `"aliceexample.com"`), letting one user's continuity overwrite or leak
62
+ * into another's.
63
+ *
64
+ * We therefore key the file on a SHA-256 of the *full* id — deterministic,
65
+ * collision-resistant, and filesystem-safe (hex only) — prefixed with a
66
+ * sanitized, human-readable slug purely for debuggability. Mirrors the Python
67
+ * `ContinuityManager._user_file`.
68
+ */
69
+ userFile(userId) {
70
+ const raw = String(userId);
71
+ const slug = (raw.match(/[A-Za-z0-9_-]/g) || []).join('').slice(0, 32) || 'user';
72
+ // Full SHA-256 hex (the filename is the user-isolation boundary; the extra
73
+ // length is free and maximizes separation between users).
74
+ const digest = crypto.createHash('sha256').update(raw, 'utf-8').digest('hex');
75
+ return path.join(this.storagePath, `${slug}-${digest}.json`);
76
+ }
49
77
  saveSession(userId, sessionData) {
50
- const userFile = path.join(this.storagePath, `${userId}.json`);
78
+ const userFile = this.userFile(userId);
51
79
  const existingData = this.loadUserData(userId);
52
80
  sessionData.timestamp = new Date().toISOString();
53
81
  existingData.lastSession = sessionData;
54
82
  existingData.sessionCount = (existingData.sessionCount || 0) + 1;
83
+ // Preserve declared boundaries across sessions.
84
+ if (sessionData.declaredBoundaries !== undefined) {
85
+ existingData.declaredBoundaries = sessionData.declaredBoundaries;
86
+ }
55
87
  fs.writeFileSync(userFile, JSON.stringify(existingData, null, 2));
56
88
  }
57
89
  getContext(userId) {
58
90
  const userData = this.loadUserData(userId);
59
91
  if (Object.keys(userData).length === 0) {
60
- return { hasHistory: false, protectiveStateActive: false };
92
+ return { hasHistory: false, protectiveStateActive: false, declaredBoundaries: [] };
61
93
  }
62
94
  return {
63
95
  hasHistory: true,
64
96
  lastSessionState: userData.lastSession?.emotionalState || 'unknown',
65
97
  protectiveStateActive: userData.lastSession?.protectiveStateActive || false,
98
+ declaredBoundaries: userData.declaredBoundaries || [],
99
+ sessionCount: userData.sessionCount || 0,
66
100
  };
67
101
  }
68
102
  loadUserData(userId) {
69
- const userFile = path.join(this.storagePath, `${userId}.json`);
103
+ const userFile = this.userFile(userId);
70
104
  if (!fs.existsSync(userFile))
71
105
  return {};
72
106
  try {
@@ -7,16 +7,31 @@ export interface SWPOptions {
7
7
  privacyMode?: string;
8
8
  loggingEnabled?: boolean;
9
9
  storagePath?: string;
10
+ /**
11
+ * Stable identifier for the user this instance serves. Used as the continuity
12
+ * key when `assessInteraction` is called without an explicit `userId`. Falls
13
+ * back to a `userId` declared in the TOI, or to `'default_user'` so a single
14
+ * instance still maintains continuity across calls.
15
+ */
16
+ userId?: string;
10
17
  }
11
18
  export declare class SleepwalkerProtocol {
12
19
  private userToi;
20
+ private userId;
13
21
  private stateDetector;
14
22
  private consentManager;
15
23
  private continuityManager;
16
24
  private loggingEnabled;
17
25
  constructor(options?: SWPOptions);
18
26
  detectEmotionalState(userInput: string, sessionHistory?: any[]): EmotionalState;
19
- assessInteraction(userInput: string, sessionHistory?: any[]): any;
27
+ assessInteraction(userInput: string, sessionHistory?: any[], userId?: string): any;
28
+ /**
29
+ * Preserves emotional boundaries across sessions. Mirrors Python
30
+ * `maintain_continuity`: the read (`assessInteraction`) and the write
31
+ * (`maintainContinuity`) stay separate so that assessment never implicitly
32
+ * writes to disk.
33
+ */
34
+ maintainContinuity(userId: string, sessionData: any): void;
20
35
  generateResponse(userInput: string, detectedState?: EmotionalState): any;
21
36
  requiresRrtaHandoff(userState: EmotionalState): boolean;
22
37
  }
package/dist/protocol.js CHANGED
@@ -12,7 +12,17 @@ class SleepwalkerProtocol {
12
12
  constructor(options = {}) {
13
13
  this.loggingEnabled = options.loggingEnabled !== false;
14
14
  const toiLoader = new toiLoader_1.TOILoader(options.userToiPath);
15
- this.userToi = options.userToiPath ? toiLoader.load() : {};
15
+ const loadedToi = options.userToiPath ? toiLoader.load() : {};
16
+ // A malformed TOI can parse to a non-object; coerce so downstream `.swp`
17
+ // lookups are always safe.
18
+ this.userToi = loadedToi && typeof loadedToi === 'object' ? loadedToi : {};
19
+ // Stable continuity identity for this instance. Never derive this from the
20
+ // user's input text — doing so makes every interaction look like a brand new
21
+ // user and continuity can never be retrieved. Accept an explicit id, then a
22
+ // top-level or swp-nested TOI id. The id is hashed where it becomes a
23
+ // filename (see ContinuityManager.userFile), so traversal is contained.
24
+ const swpToi = this.userToi.swp && typeof this.userToi.swp === 'object' ? this.userToi.swp : {};
25
+ this.userId = options.userId || this.userToi.user_id || swpToi.user_id || 'default_user';
16
26
  this.stateDetector = new stateDetection_1.StateDetector();
17
27
  this.consentManager = new consent_1.ConsentManager(this.userToi);
18
28
  this.continuityManager = new continuity_1.ContinuityManager(options.storagePath || '.swp_storage');
@@ -26,16 +36,30 @@ class SleepwalkerProtocol {
26
36
  }
27
37
  return state;
28
38
  }
29
- assessInteraction(userInput, sessionHistory = []) {
39
+ assessInteraction(userInput, sessionHistory = [], userId) {
30
40
  const emotionalState = this.detectEmotionalState(userInput, sessionHistory);
31
41
  const consentLevel = this.consentManager.determineLevel(emotionalState);
42
+ // Get continuity context, keyed by the stable user identifier. Passing
43
+ // `userInput` here (the previous behavior on the Python side) keyed
44
+ // continuity on the message text, so it always reported "no history".
45
+ const continuityContext = this.continuityManager.getContext(userId || this.userId);
32
46
  return {
33
47
  emotionalState,
34
48
  consentLevel,
49
+ continuityContext,
35
50
  swpActive: this.userToi.swp?.active !== false,
36
51
  protectiveStateActive: emotionalState.protective,
37
52
  };
38
53
  }
54
+ /**
55
+ * Preserves emotional boundaries across sessions. Mirrors Python
56
+ * `maintain_continuity`: the read (`assessInteraction`) and the write
57
+ * (`maintainContinuity`) stay separate so that assessment never implicitly
58
+ * writes to disk.
59
+ */
60
+ maintainContinuity(userId, sessionData) {
61
+ this.continuityManager.saveSession(userId, sessionData);
62
+ }
39
63
  generateResponse(userInput, detectedState) {
40
64
  const state = detectedState || this.detectEmotionalState(userInput);
41
65
  if ((this.userToi.swp?.active !== false) && state.protective) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@neurolift-technologies/sleepwalker-protocol",
3
- "version": "1.0.0",
4
- "description": "Emotional Continuity Governance for AI Systems",
3
+ "version": "1.0.1",
4
+ "description": "⚠️ PROTOTYPE / not medical advice — Emotional Continuity Governance for AI Systems",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "publishConfig": {
@@ -22,7 +22,10 @@
22
22
  "mental-health",
23
23
  "neurodivergent",
24
24
  "ai-ethics",
25
- "trauma-informed"
25
+ "trauma-informed",
26
+ "prototype",
27
+ "experimental",
28
+ "not-medical-advice"
26
29
  ],
27
30
  "author": "NeuroLift Technologies / HAIEF <haief@neuroliftsolutions.com>",
28
31
  "license": "Apache-2.0",