@dropout-ai/runtime 0.2.6 → 0.2.8

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 +95 -47
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.08",
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,37 @@ 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
+ session_id: GLOBAL_OBJ.__dropout_session_id__,
77
+ turn_index: turnIndex,
78
+ direction: 'meta',
79
+ turn_role: 'system',
80
+ created_at: Date.now(),
81
+ metadata_flags: {
82
+ session_end: true,
83
+ end_reason: reason
84
+ }
85
+ });
86
+ }
87
+
88
+ // Browser: Navigation/Reload
89
+ if (typeof window !== 'undefined' && window.addEventListener) {
90
+ window.addEventListener('beforeunload', () => emitSessionEnd('navigation'));
91
+ }
92
+
93
+ // Node.js: Process Exit
94
+ if (typeof process !== 'undefined' && process.on) {
95
+ process.on('exit', () => emitSessionEnd('process_exit'));
96
+ process.on('SIGINT', () => { emitSessionEnd('sigint'); process.exit(); });
97
+ process.on('SIGTERM', () => { emitSessionEnd('sigterm'); process.exit(); });
98
+ }
99
+
68
100
  // --- Content Utilities ---
69
101
 
70
102
  function hash(text) {
@@ -122,7 +154,16 @@ if (typeof GLOBAL_OBJ.fetch === 'function' && !GLOBAL_OBJ.fetch.__dropout_patche
122
154
  if (!isAI) return GLOBAL_OBJ.__dropout_original_fetch__(input, init);
123
155
 
124
156
  const start = Date.now();
125
- const turn = turnIndex++;
157
+
158
+ // --- Explicit Turn Increment Rule ---
159
+ let activeTurn;
160
+ if (lastTurnConfirmed) {
161
+ activeTurn = turnIndex++;
162
+ lastTurnConfirmed = false;
163
+ } else {
164
+ activeTurn = turnIndex > 0 ? turnIndex - 1 : 0;
165
+ }
166
+
126
167
  const { provider, model } = normalize(url, init && init.body);
127
168
 
128
169
  // --- 1. Emit Request Event ---
@@ -134,23 +175,15 @@ if (typeof GLOBAL_OBJ.fetch === 'function' && !GLOBAL_OBJ.fetch.__dropout_patche
134
175
  const isRetry = pHash && pHash === lastPromptHash;
135
176
 
136
177
  const requestEvent = {
137
- identity: {
138
- session_id: GLOBAL_OBJ.__dropout_session_id__,
139
- turn_index: turn,
140
- direction: 'request',
141
- turn_role: 'user'
142
- },
143
- timing: {
144
- created_at: Date.now()
145
- },
146
- provider_context: {
147
- provider,
148
- model
149
- },
150
- content: {
151
- content_raw: config.privacyMode === 'full' ? pText : null,
152
- content_hash: pHash
153
- },
178
+ session_id: GLOBAL_OBJ.__dropout_session_id__,
179
+ turn_index: activeTurn,
180
+ direction: 'request',
181
+ turn_role: 'user',
182
+ created_at: Date.now(),
183
+ provider,
184
+ model,
185
+ content_raw: config.privacyMode === 'full' ? pText : null,
186
+ content_hash: pHash,
154
187
  metadata_flags: {
155
188
  retry_like: isRetry ? 1 : 0
156
189
  }
@@ -165,7 +198,6 @@ if (typeof GLOBAL_OBJ.fetch === 'function' && !GLOBAL_OBJ.fetch.__dropout_patche
165
198
  try {
166
199
  response = await GLOBAL_OBJ.__dropout_original_fetch__(input, init);
167
200
  } catch (err) {
168
- // Re-throw after giving it a chance to be reported if needed (though runtime usually silent)
169
201
  throw err;
170
202
  }
171
203
 
@@ -184,24 +216,16 @@ if (typeof GLOBAL_OBJ.fetch === 'function' && !GLOBAL_OBJ.fetch.__dropout_patche
184
216
  const isNonAdaptive = oHash && oHash === lastResponseHash;
185
217
 
186
218
  const responseEvent = {
187
- identity: {
188
- session_id: GLOBAL_OBJ.__dropout_session_id__,
189
- turn_index: turn,
190
- direction: 'response',
191
- turn_role: 'assistant'
192
- },
193
- timing: {
194
- created_at: Date.now(),
195
- latency_ms: latency
196
- },
197
- provider_context: {
198
- provider,
199
- model
200
- },
201
- content: {
202
- content_raw: config.privacyMode === 'full' ? oText : null,
203
- content_hash: oHash
204
- },
219
+ session_id: GLOBAL_OBJ.__dropout_session_id__,
220
+ turn_index: activeTurn,
221
+ direction: 'response',
222
+ turn_role: 'assistant',
223
+ created_at: Date.now(),
224
+ latency_ms: latency,
225
+ provider,
226
+ model,
227
+ content_raw: config.privacyMode === 'full' ? oText : null,
228
+ content_hash: oHash,
205
229
  metadata_flags: {
206
230
  non_adaptive_response: isNonAdaptive ? 1 : 0,
207
231
  turn_boundary_confirmed: 1
@@ -210,6 +234,7 @@ if (typeof GLOBAL_OBJ.fetch === 'function' && !GLOBAL_OBJ.fetch.__dropout_patche
210
234
 
211
235
  emit(responseEvent);
212
236
  lastResponseHash = oHash;
237
+ lastTurnConfirmed = true;
213
238
 
214
239
  return response;
215
240
  };
@@ -222,7 +247,15 @@ if (typeof GLOBAL_OBJ.fetch === 'function' && !GLOBAL_OBJ.fetch.__dropout_patche
222
247
  */
223
248
  async function capture(target, options = {}) {
224
249
  const start = Date.now();
225
- const turn = turnIndex++;
250
+
251
+ let activeTurn;
252
+ if (lastTurnConfirmed) {
253
+ activeTurn = turnIndex++;
254
+ lastTurnConfirmed = false;
255
+ } else {
256
+ activeTurn = turnIndex > 0 ? turnIndex - 1 : 0;
257
+ }
258
+
226
259
  const mode = options.privacy || config.privacyMode;
227
260
 
228
261
  let prompt, output, latency_ms = options.latency || 0;
@@ -242,33 +275,48 @@ async function capture(target, options = {}) {
242
275
 
243
276
  // Emit Request
244
277
  emit({
245
- identity: { session_id: GLOBAL_OBJ.__dropout_session_id__, turn_index: turn, direction: 'request', turn_role: 'user' },
246
- timing: { created_at: Date.now() },
247
- provider_context: { provider: options.provider || 'manual', model: options.model || 'unknown' },
248
- content: { content_raw: mode === 'full' ? prompt : null, content_hash: pHash },
278
+ session_id: GLOBAL_OBJ.__dropout_session_id__,
279
+ turn_index: activeTurn,
280
+ direction: 'request',
281
+ turn_role: 'user',
282
+ created_at: Date.now(),
283
+ provider: options.provider || 'manual',
284
+ model: options.model || 'unknown',
285
+ content_raw: mode === 'full' ? prompt : null,
286
+ content_hash: pHash,
249
287
  metadata_flags: { retry_like: pHash === lastPromptHash ? 1 : 0 }
250
288
  });
251
289
 
252
290
  // Emit Response
253
291
  emit({
254
- identity: { session_id: GLOBAL_OBJ.__dropout_session_id__, turn_index: turn, direction: 'response', turn_role: 'assistant' },
255
- timing: { created_at: Date.now(), latency_ms },
256
- provider_context: { provider: options.provider || 'manual', model: options.model || 'unknown' },
257
- content: { content_raw: mode === 'full' ? output : null, content_hash: oHash },
292
+ session_id: GLOBAL_OBJ.__dropout_session_id__,
293
+ turn_index: activeTurn,
294
+ direction: 'response',
295
+ turn_role: 'assistant',
296
+ created_at: Date.now(),
297
+ latency_ms,
298
+ provider: options.provider || 'manual',
299
+ model: options.model || 'unknown',
300
+ content_raw: mode === 'full' ? output : null,
301
+ content_hash: oHash,
258
302
  metadata_flags: { non_adaptive_response: oHash === lastResponseHash ? 1 : 0, turn_boundary_confirmed: 1 }
259
303
  });
260
304
 
261
305
  lastPromptHash = pHash;
262
306
  lastResponseHash = oHash;
307
+ lastTurnConfirmed = true;
263
308
 
264
309
  return output;
265
310
  }
266
311
 
267
312
  module.exports = {
268
313
  capture,
269
- reset: () => {
314
+ reset: (reason = 'manual_reset') => {
315
+ emitSessionEnd(reason);
270
316
  GLOBAL_OBJ.__dropout_session_id__ = generateSessionId();
317
+ GLOBAL_OBJ.__dropout_session_ended__ = false;
271
318
  turnIndex = 0;
319
+ lastTurnConfirmed = true;
272
320
  lastPromptHash = null;
273
321
  lastResponseHash = null;
274
322
  }