trickle-observe 0.2.88 → 0.2.90
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/trace-var.js +57 -0
- package/observe-esm-hooks.mjs +32 -0
- package/package.json +1 -1
- package/src/trace-var.ts +57 -0
package/dist/trace-var.js
CHANGED
|
@@ -238,4 +238,61 @@ if (typeof process !== 'undefined' && process.on) {
|
|
|
238
238
|
process.on('beforeExit', exitFlush);
|
|
239
239
|
process.on('SIGTERM', exitFlush);
|
|
240
240
|
process.on('SIGINT', exitFlush);
|
|
241
|
+
// Capture uncaught exceptions with variable context for agent debugging.
|
|
242
|
+
// Write error + nearby variable values to errors.jsonl before the process exits.
|
|
243
|
+
process.on('uncaughtException', (err) => {
|
|
244
|
+
flushVarBuffer();
|
|
245
|
+
try {
|
|
246
|
+
const dir = varsFilePath
|
|
247
|
+
? path.dirname(varsFilePath)
|
|
248
|
+
: process.env.TRICKLE_LOCAL_DIR || path.join(process.cwd(), '.trickle');
|
|
249
|
+
try {
|
|
250
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
251
|
+
}
|
|
252
|
+
catch { }
|
|
253
|
+
const errorsFile = path.join(dir, 'errors.jsonl');
|
|
254
|
+
// Extract file and line from stack trace
|
|
255
|
+
const stackLines = (err.stack || '').split('\n');
|
|
256
|
+
let errorFile;
|
|
257
|
+
let errorLine;
|
|
258
|
+
for (const sl of stackLines.slice(1)) {
|
|
259
|
+
const m = sl.match(/\((.+):(\d+):\d+\)/) || sl.match(/at (.+):(\d+):\d+/);
|
|
260
|
+
if (m && !m[1].includes('node_modules') && !m[1].includes('node:') && !m[1].includes('trickle')) {
|
|
261
|
+
errorFile = m[1];
|
|
262
|
+
errorLine = parseInt(m[2]);
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// Find nearby variable values from the cache
|
|
267
|
+
const nearbyVars = {};
|
|
268
|
+
if (errorFile && errorLine) {
|
|
269
|
+
for (const [key, entry] of varCache) {
|
|
270
|
+
const parts = key.split(':');
|
|
271
|
+
const file = parts[0];
|
|
272
|
+
const line = parseInt(parts[1]);
|
|
273
|
+
const varName = parts.slice(2).join(':');
|
|
274
|
+
if (file === errorFile && Math.abs(line - errorLine) <= 10) {
|
|
275
|
+
nearbyVars[`L${parts[1]} ${varName}`] = entry.fp;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
const record = {
|
|
280
|
+
kind: 'error',
|
|
281
|
+
error: err.message,
|
|
282
|
+
type: err.constructor.name,
|
|
283
|
+
file: errorFile,
|
|
284
|
+
line: errorLine,
|
|
285
|
+
stack: stackLines.slice(0, 6).join('\n'),
|
|
286
|
+
nearbyVariables: Object.keys(nearbyVars).length > 0 ? nearbyVars : undefined,
|
|
287
|
+
timestamp: new Date().toISOString(),
|
|
288
|
+
};
|
|
289
|
+
fs.appendFileSync(errorsFile, JSON.stringify(record) + '\n');
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
// Never suppress the original error
|
|
293
|
+
}
|
|
294
|
+
// Print the original error and exit (don't re-throw to preserve original stack)
|
|
295
|
+
console.error(err.stack || err.message);
|
|
296
|
+
process.exit(1);
|
|
297
|
+
});
|
|
241
298
|
}
|
package/observe-esm-hooks.mjs
CHANGED
|
@@ -236,6 +236,10 @@ function findVarDeclarationsESM(source) {
|
|
|
236
236
|
const restOfLine = source.slice(match.index + match[0].length - 1, match.index + match[0].length + 200);
|
|
237
237
|
if (/^\s*require\s*\(/.test(restOfLine)) continue;
|
|
238
238
|
|
|
239
|
+
// Skip variable declarations inside for-loop headers
|
|
240
|
+
const beforeDecl = source.slice(Math.max(0, match.index - 50), match.index);
|
|
241
|
+
if (/\bfor\s*\(\s*$/.test(beforeDecl)) continue;
|
|
242
|
+
|
|
239
243
|
// Calculate line number where the declaration starts
|
|
240
244
|
let lineNo = 1;
|
|
241
245
|
for (let i = 0; i < match.index; i++) {
|
|
@@ -260,6 +264,19 @@ function findVarDeclarationsESM(source) {
|
|
|
260
264
|
} else if (ch === '\n' && depth === 0) {
|
|
261
265
|
const nextNonWs = source.slice(pos + 1).match(/^\s*(\S)/);
|
|
262
266
|
if (nextNonWs && !'.+=-|&?:,'.includes(nextNonWs[1])) {
|
|
267
|
+
// Check if a recent non-empty line ends with an operator (continuation across empty lines)
|
|
268
|
+
let checkPos = pos;
|
|
269
|
+
let lastCh = '';
|
|
270
|
+
for (let back = 0; back < 5; back++) {
|
|
271
|
+
const prevNL = source.lastIndexOf('\n', checkPos - 1);
|
|
272
|
+
const prevLine = source.slice(prevNL + 1, checkPos).trimEnd();
|
|
273
|
+
if (prevLine.length > 0) { lastCh = prevLine[prevLine.length - 1]; break; }
|
|
274
|
+
checkPos = prevNL;
|
|
275
|
+
if (prevNL <= 0) break;
|
|
276
|
+
}
|
|
277
|
+
if (lastCh && '=+-*/%&|^~<>?:,({['.includes(lastCh)) {
|
|
278
|
+
pos++; continue;
|
|
279
|
+
}
|
|
263
280
|
foundEnd = pos;
|
|
264
281
|
break;
|
|
265
282
|
}
|
|
@@ -270,6 +287,21 @@ function findVarDeclarationsESM(source) {
|
|
|
270
287
|
else if (source[pos] === quote) break;
|
|
271
288
|
pos++;
|
|
272
289
|
}
|
|
290
|
+
} else if (ch === '/' && pos + 1 < source.length && source[pos + 1] !== '/' && source[pos + 1] !== '*') {
|
|
291
|
+
// Possible regex literal
|
|
292
|
+
let rp = pos - 1;
|
|
293
|
+
while (rp >= 0 && (source[rp] === ' ' || source[rp] === '\t')) rp--;
|
|
294
|
+
const rpCh = rp >= 0 ? source[rp] : '';
|
|
295
|
+
if ('=(!,;:?[{&|^~+-><%'.includes(rpCh) || source.slice(Math.max(0, rp - 5), rp + 1).match(/\b(return|typeof|instanceof|in|of|void|delete|throw|new|case)\s*$/)) {
|
|
296
|
+
pos++;
|
|
297
|
+
while (pos < source.length) {
|
|
298
|
+
if (source[pos] === '\\') pos++;
|
|
299
|
+
else if (source[pos] === '[') { pos++; while (pos < source.length && source[pos] !== ']') { if (source[pos] === '\\') pos++; pos++; } }
|
|
300
|
+
else if (source[pos] === '/') break;
|
|
301
|
+
pos++;
|
|
302
|
+
}
|
|
303
|
+
while (pos + 1 < source.length && /[gimsuy]/.test(source[pos + 1])) pos++;
|
|
304
|
+
}
|
|
273
305
|
} else if (ch === '/' && pos + 1 < source.length && source[pos + 1] === '/') {
|
|
274
306
|
while (pos < source.length && source[pos] !== '\n') pos++;
|
|
275
307
|
continue;
|
package/package.json
CHANGED
package/src/trace-var.ts
CHANGED
|
@@ -221,4 +221,61 @@ if (typeof process !== 'undefined' && process.on) {
|
|
|
221
221
|
process.on('beforeExit', exitFlush);
|
|
222
222
|
process.on('SIGTERM', exitFlush);
|
|
223
223
|
process.on('SIGINT', exitFlush);
|
|
224
|
+
|
|
225
|
+
// Capture uncaught exceptions with variable context for agent debugging.
|
|
226
|
+
// Write error + nearby variable values to errors.jsonl before the process exits.
|
|
227
|
+
process.on('uncaughtException', (err: Error) => {
|
|
228
|
+
flushVarBuffer();
|
|
229
|
+
try {
|
|
230
|
+
const dir = varsFilePath
|
|
231
|
+
? path.dirname(varsFilePath)
|
|
232
|
+
: process.env.TRICKLE_LOCAL_DIR || path.join(process.cwd(), '.trickle');
|
|
233
|
+
try { fs.mkdirSync(dir, { recursive: true }); } catch {}
|
|
234
|
+
const errorsFile = path.join(dir, 'errors.jsonl');
|
|
235
|
+
|
|
236
|
+
// Extract file and line from stack trace
|
|
237
|
+
const stackLines = (err.stack || '').split('\n');
|
|
238
|
+
let errorFile: string | undefined;
|
|
239
|
+
let errorLine: number | undefined;
|
|
240
|
+
for (const sl of stackLines.slice(1)) {
|
|
241
|
+
const m = sl.match(/\((.+):(\d+):\d+\)/) || sl.match(/at (.+):(\d+):\d+/);
|
|
242
|
+
if (m && !m[1].includes('node_modules') && !m[1].includes('node:') && !m[1].includes('trickle')) {
|
|
243
|
+
errorFile = m[1];
|
|
244
|
+
errorLine = parseInt(m[2]);
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Find nearby variable values from the cache
|
|
250
|
+
const nearbyVars: Record<string, string> = {};
|
|
251
|
+
if (errorFile && errorLine) {
|
|
252
|
+
for (const [key, entry] of varCache) {
|
|
253
|
+
const parts = key.split(':');
|
|
254
|
+
const file = parts[0];
|
|
255
|
+
const line = parseInt(parts[1]);
|
|
256
|
+
const varName = parts.slice(2).join(':');
|
|
257
|
+
if (file === errorFile && Math.abs(line - errorLine) <= 10) {
|
|
258
|
+
nearbyVars[`L${parts[1]} ${varName}`] = entry.fp;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const record = {
|
|
264
|
+
kind: 'error',
|
|
265
|
+
error: err.message,
|
|
266
|
+
type: err.constructor.name,
|
|
267
|
+
file: errorFile,
|
|
268
|
+
line: errorLine,
|
|
269
|
+
stack: stackLines.slice(0, 6).join('\n'),
|
|
270
|
+
nearbyVariables: Object.keys(nearbyVars).length > 0 ? nearbyVars : undefined,
|
|
271
|
+
timestamp: new Date().toISOString(),
|
|
272
|
+
};
|
|
273
|
+
fs.appendFileSync(errorsFile, JSON.stringify(record) + '\n');
|
|
274
|
+
} catch {
|
|
275
|
+
// Never suppress the original error
|
|
276
|
+
}
|
|
277
|
+
// Print the original error and exit (don't re-throw to preserve original stack)
|
|
278
|
+
console.error(err.stack || err.message);
|
|
279
|
+
process.exit(1);
|
|
280
|
+
});
|
|
224
281
|
}
|