ai-gains 1.5.1 → 1.5.2
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/package.json
CHANGED
|
@@ -31,15 +31,44 @@ function findFirstTimestamp() {
|
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
function getEntryText(entry) {
|
|
35
|
+
const content = entry.message?.content;
|
|
36
|
+
if (!content) return '';
|
|
37
|
+
if (typeof content === 'string') return content;
|
|
38
|
+
if (Array.isArray(content)) {
|
|
39
|
+
return content
|
|
40
|
+
.filter(c => c.type === 'text')
|
|
41
|
+
.map(c => c.text || '')
|
|
42
|
+
.join(' ');
|
|
43
|
+
}
|
|
44
|
+
return '';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// A user message that contains the expanded /ai-gains skill content.
|
|
48
|
+
// 'estimated_human_time_minutes' is unique to SKILL.md and won't appear
|
|
49
|
+
// in normal conversation or the UserPromptSubmit hook injection.
|
|
50
|
+
function isAiGainsInvocation(entry) {
|
|
51
|
+
if (entry.type !== 'user' && entry.message?.role !== 'user') return false;
|
|
52
|
+
return getEntryText(entry).includes('estimated_human_time_minutes');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function isAssistant(entry) {
|
|
56
|
+
return entry.type === 'assistant' || entry.message?.role === 'assistant';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Scan from the end. Find the last assistant timestamp that precedes the
|
|
60
|
+
// most recent /ai-gains skill invocation. Falls back to the last timestamp
|
|
61
|
+
// overall if no ai-gains invocation is found (e.g. during a dry-run).
|
|
62
|
+
function findEndTimestamp() {
|
|
36
63
|
const fd = fs.openSync(transcriptPath, 'r');
|
|
37
64
|
const { size } = fs.fstatSync(fd);
|
|
38
65
|
let offset = size;
|
|
39
66
|
let remainder = '';
|
|
40
|
-
let
|
|
67
|
+
let phase = 'looking_for_aigains'; // then 'looking_for_assistant'
|
|
68
|
+
let lastTimestamp = null; // fallback
|
|
69
|
+
let result = null;
|
|
41
70
|
|
|
42
|
-
while (offset > 0) {
|
|
71
|
+
outer: while (offset > 0) {
|
|
43
72
|
const readSize = Math.min(CHUNK_SIZE, offset);
|
|
44
73
|
offset -= readSize;
|
|
45
74
|
const buf = Buffer.alloc(readSize);
|
|
@@ -53,22 +82,34 @@ function findLastTimestamp() {
|
|
|
53
82
|
if (!line) continue;
|
|
54
83
|
try {
|
|
55
84
|
const entry = JSON.parse(line);
|
|
56
|
-
|
|
85
|
+
|
|
86
|
+
// Track the absolute last timestamp as a fallback
|
|
87
|
+
if (lastTimestamp === null && entry.timestamp) {
|
|
57
88
|
lastTimestamp = entry.timestamp;
|
|
58
|
-
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (phase === 'looking_for_aigains') {
|
|
92
|
+
if (isAiGainsInvocation(entry)) {
|
|
93
|
+
phase = 'looking_for_assistant';
|
|
94
|
+
}
|
|
95
|
+
} else {
|
|
96
|
+
// phase === 'looking_for_assistant'
|
|
97
|
+
if (isAssistant(entry) && entry.timestamp) {
|
|
98
|
+
result = entry.timestamp;
|
|
99
|
+
break outer;
|
|
100
|
+
}
|
|
59
101
|
}
|
|
60
102
|
} catch {}
|
|
61
103
|
}
|
|
62
|
-
if (lastTimestamp) break;
|
|
63
104
|
}
|
|
64
105
|
|
|
65
106
|
fs.closeSync(fd);
|
|
66
|
-
return lastTimestamp;
|
|
107
|
+
return result || lastTimestamp;
|
|
67
108
|
}
|
|
68
109
|
|
|
69
110
|
(async () => {
|
|
70
111
|
const start_time = await findFirstTimestamp();
|
|
71
|
-
const end_time =
|
|
112
|
+
const end_time = findEndTimestamp();
|
|
72
113
|
|
|
73
114
|
if (!start_time || !end_time) {
|
|
74
115
|
console.error('Could not extract timestamps from transcript');
|