@dropout-ai/runtime 0.2.6 → 0.2.7

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +63 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dropout-ai/runtime",
3
- "version": "0.2.06",
3
+ "version": "0.2.07",
4
4
  "description": "Invisible Node.js runtime for capturing AI interactions.",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
package/src/index.js CHANGED
@@ -24,6 +24,7 @@ if (!GLOBAL_OBJ.__dropout_session_id__) {
24
24
  }
25
25
 
26
26
  let turnIndex = 0;
27
+ let lastTurnConfirmed = true;
27
28
  let lastPromptHash = null;
28
29
  let lastResponseHash = null;
29
30
 
@@ -65,6 +66,39 @@ function emit(payload) {
65
66
  }, 0);
66
67
  }
67
68
 
69
+ // --- Lifecycle Signals ---
70
+
71
+ function emitSessionEnd(reason) {
72
+ if (GLOBAL_OBJ.__dropout_session_ended__) return;
73
+ GLOBAL_OBJ.__dropout_session_ended__ = true;
74
+
75
+ emit({
76
+ identity: {
77
+ session_id: GLOBAL_OBJ.__dropout_session_id__,
78
+ turn_index: turnIndex,
79
+ direction: 'meta',
80
+ turn_role: 'system'
81
+ },
82
+ timing: { created_at: Date.now() },
83
+ metadata_flags: {
84
+ session_end: true,
85
+ end_reason: reason
86
+ }
87
+ });
88
+ }
89
+
90
+ // Browser: Navigation/Reload
91
+ if (typeof window !== 'undefined' && window.addEventListener) {
92
+ window.addEventListener('beforeunload', () => emitSessionEnd('navigation'));
93
+ }
94
+
95
+ // Node.js: Process Exit
96
+ if (typeof process !== 'undefined' && process.on) {
97
+ process.on('exit', () => emitSessionEnd('process_exit'));
98
+ process.on('SIGINT', () => { emitSessionEnd('sigint'); process.exit(); });
99
+ process.on('SIGTERM', () => { emitSessionEnd('sigterm'); process.exit(); });
100
+ }
101
+
68
102
  // --- Content Utilities ---
69
103
 
70
104
  function hash(text) {
@@ -122,7 +156,16 @@ if (typeof GLOBAL_OBJ.fetch === 'function' && !GLOBAL_OBJ.fetch.__dropout_patche
122
156
  if (!isAI) return GLOBAL_OBJ.__dropout_original_fetch__(input, init);
123
157
 
124
158
  const start = Date.now();
125
- const turn = turnIndex++;
159
+
160
+ // --- Explicit Turn Increment Rule ---
161
+ let activeTurn;
162
+ if (lastTurnConfirmed) {
163
+ activeTurn = turnIndex++;
164
+ lastTurnConfirmed = false;
165
+ } else {
166
+ activeTurn = turnIndex > 0 ? turnIndex - 1 : 0;
167
+ }
168
+
126
169
  const { provider, model } = normalize(url, init && init.body);
127
170
 
128
171
  // --- 1. Emit Request Event ---
@@ -136,7 +179,7 @@ if (typeof GLOBAL_OBJ.fetch === 'function' && !GLOBAL_OBJ.fetch.__dropout_patche
136
179
  const requestEvent = {
137
180
  identity: {
138
181
  session_id: GLOBAL_OBJ.__dropout_session_id__,
139
- turn_index: turn,
182
+ turn_index: activeTurn,
140
183
  direction: 'request',
141
184
  turn_role: 'user'
142
185
  },
@@ -165,7 +208,6 @@ if (typeof GLOBAL_OBJ.fetch === 'function' && !GLOBAL_OBJ.fetch.__dropout_patche
165
208
  try {
166
209
  response = await GLOBAL_OBJ.__dropout_original_fetch__(input, init);
167
210
  } catch (err) {
168
- // Re-throw after giving it a chance to be reported if needed (though runtime usually silent)
169
211
  throw err;
170
212
  }
171
213
 
@@ -186,7 +228,7 @@ if (typeof GLOBAL_OBJ.fetch === 'function' && !GLOBAL_OBJ.fetch.__dropout_patche
186
228
  const responseEvent = {
187
229
  identity: {
188
230
  session_id: GLOBAL_OBJ.__dropout_session_id__,
189
- turn_index: turn,
231
+ turn_index: activeTurn,
190
232
  direction: 'response',
191
233
  turn_role: 'assistant'
192
234
  },
@@ -210,6 +252,7 @@ if (typeof GLOBAL_OBJ.fetch === 'function' && !GLOBAL_OBJ.fetch.__dropout_patche
210
252
 
211
253
  emit(responseEvent);
212
254
  lastResponseHash = oHash;
255
+ lastTurnConfirmed = true; // Confirmation Rule locked
213
256
 
214
257
  return response;
215
258
  };
@@ -222,7 +265,15 @@ if (typeof GLOBAL_OBJ.fetch === 'function' && !GLOBAL_OBJ.fetch.__dropout_patche
222
265
  */
223
266
  async function capture(target, options = {}) {
224
267
  const start = Date.now();
225
- const turn = turnIndex++;
268
+
269
+ let activeTurn;
270
+ if (lastTurnConfirmed) {
271
+ activeTurn = turnIndex++;
272
+ lastTurnConfirmed = false;
273
+ } else {
274
+ activeTurn = turnIndex > 0 ? turnIndex - 1 : 0;
275
+ }
276
+
226
277
  const mode = options.privacy || config.privacyMode;
227
278
 
228
279
  let prompt, output, latency_ms = options.latency || 0;
@@ -242,7 +293,7 @@ async function capture(target, options = {}) {
242
293
 
243
294
  // Emit Request
244
295
  emit({
245
- identity: { session_id: GLOBAL_OBJ.__dropout_session_id__, turn_index: turn, direction: 'request', turn_role: 'user' },
296
+ identity: { session_id: GLOBAL_OBJ.__dropout_session_id__, turn_index: activeTurn, direction: 'request', turn_role: 'user' },
246
297
  timing: { created_at: Date.now() },
247
298
  provider_context: { provider: options.provider || 'manual', model: options.model || 'unknown' },
248
299
  content: { content_raw: mode === 'full' ? prompt : null, content_hash: pHash },
@@ -251,7 +302,7 @@ async function capture(target, options = {}) {
251
302
 
252
303
  // Emit Response
253
304
  emit({
254
- identity: { session_id: GLOBAL_OBJ.__dropout_session_id__, turn_index: turn, direction: 'response', turn_role: 'assistant' },
305
+ identity: { session_id: GLOBAL_OBJ.__dropout_session_id__, turn_index: activeTurn, direction: 'response', turn_role: 'assistant' },
255
306
  timing: { created_at: Date.now(), latency_ms },
256
307
  provider_context: { provider: options.provider || 'manual', model: options.model || 'unknown' },
257
308
  content: { content_raw: mode === 'full' ? output : null, content_hash: oHash },
@@ -260,15 +311,19 @@ async function capture(target, options = {}) {
260
311
 
261
312
  lastPromptHash = pHash;
262
313
  lastResponseHash = oHash;
314
+ lastTurnConfirmed = true;
263
315
 
264
316
  return output;
265
317
  }
266
318
 
267
319
  module.exports = {
268
320
  capture,
269
- reset: () => {
321
+ reset: (reason = 'manual_reset') => {
322
+ emitSessionEnd(reason);
270
323
  GLOBAL_OBJ.__dropout_session_id__ = generateSessionId();
324
+ GLOBAL_OBJ.__dropout_session_ended__ = false;
271
325
  turnIndex = 0;
326
+ lastTurnConfirmed = true;
272
327
  lastPromptHash = null;
273
328
  lastResponseHash = null;
274
329
  }