@yhong91/vibetime 0.1.72 → 0.1.74
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/bin/vibetime.mjs +284 -68
- package/package.json +1 -1
package/bin/vibetime.mjs
CHANGED
|
@@ -2047,7 +2047,7 @@ function claudeStyleFileMetrics(tool, input) {
|
|
|
2047
2047
|
}
|
|
2048
2048
|
|
|
2049
2049
|
// src/lib/constants.ts
|
|
2050
|
-
var PACKAGE_VERSION = true ? "0.1.
|
|
2050
|
+
var PACKAGE_VERSION = true ? "0.1.74" : "0.1.1";
|
|
2051
2051
|
var GENERATED_MARKER = "Generated by vibetime.";
|
|
2052
2052
|
var DEFAULT_API_URL = "http://121.196.224.82:3001";
|
|
2053
2053
|
var DEFAULT_BACKFILL_BATCH_SIZE = 50;
|
|
@@ -5163,7 +5163,6 @@ async function parseCodexSessionFile(filePath, options) {
|
|
|
5163
5163
|
let lastTurnIdForComplete;
|
|
5164
5164
|
let turnIdAtLastUserMessage;
|
|
5165
5165
|
let sessionMetaLocked = false;
|
|
5166
|
-
let forkedSession = false;
|
|
5167
5166
|
let lastTotalTokenUsage;
|
|
5168
5167
|
let lastPerCallUsageKey;
|
|
5169
5168
|
let compactionPending = false;
|
|
@@ -5184,7 +5183,6 @@ async function parseCodexSessionFile(filePath, options) {
|
|
|
5184
5183
|
}
|
|
5185
5184
|
sessionMetaLocked = true;
|
|
5186
5185
|
const forkedFromId = stringField(payload, "forked_from_id");
|
|
5187
|
-
forkedSession = Boolean(forkedFromId);
|
|
5188
5186
|
const inherited = forkedFromId ? await readPersistedSessionContextFromOptions(options, forkedFromId) : void 0;
|
|
5189
5187
|
sessionId = stringField(payload, "id") || sessionId;
|
|
5190
5188
|
cwd = inherited?.cwd || stringField(payload, "cwd") || cwd;
|
|
@@ -5336,7 +5334,7 @@ async function parseCodexSessionFile(filePath, options) {
|
|
|
5336
5334
|
project,
|
|
5337
5335
|
model: rewriteCodexModelForTier(model, serviceTier),
|
|
5338
5336
|
confidence: "partial",
|
|
5339
|
-
metrics:
|
|
5337
|
+
metrics: usage
|
|
5340
5338
|
}), { filePath, sourcePathHash, lineNumber, topType, payloadType, options }));
|
|
5341
5339
|
lastPerCallUsageKey = usageKey;
|
|
5342
5340
|
}
|
|
@@ -5354,7 +5352,7 @@ async function parseCodexSessionFile(filePath, options) {
|
|
|
5354
5352
|
project,
|
|
5355
5353
|
model: rewriteCodexModelForTier(model, serviceTier),
|
|
5356
5354
|
confidence: "partial",
|
|
5357
|
-
metrics:
|
|
5355
|
+
metrics: { ...lastUsage, reasoningEffort }
|
|
5358
5356
|
}), { filePath, sourcePathHash, lineNumber, topType, payloadType, options }));
|
|
5359
5357
|
}
|
|
5360
5358
|
lastTotalTokenUsage = { input, cached, output, reasoning, total };
|
|
@@ -5378,7 +5376,7 @@ async function parseCodexSessionFile(filePath, options) {
|
|
|
5378
5376
|
project,
|
|
5379
5377
|
model: rewriteCodexModelForTier(model, serviceTier),
|
|
5380
5378
|
confidence: "partial",
|
|
5381
|
-
metrics:
|
|
5379
|
+
metrics: {
|
|
5382
5380
|
tokensInput: deltaInput > 0 ? deltaInput : void 0,
|
|
5383
5381
|
tokensCachedInput: deltaCached > 0 ? deltaCached : void 0,
|
|
5384
5382
|
tokensOutput: deltaOutput > 0 ? deltaOutput : void 0,
|
|
@@ -5386,7 +5384,7 @@ async function parseCodexSessionFile(filePath, options) {
|
|
|
5386
5384
|
tokensTotal: deltaTotal > 0 ? deltaTotal : void 0,
|
|
5387
5385
|
modelContextWindow: usage.modelContextWindow,
|
|
5388
5386
|
reasoningEffort
|
|
5389
|
-
}
|
|
5387
|
+
}
|
|
5390
5388
|
}), { filePath, sourcePathHash, lineNumber, topType, payloadType, options }));
|
|
5391
5389
|
}
|
|
5392
5390
|
} else {
|
|
@@ -5399,7 +5397,7 @@ async function parseCodexSessionFile(filePath, options) {
|
|
|
5399
5397
|
project,
|
|
5400
5398
|
model: rewriteCodexModelForTier(model, serviceTier),
|
|
5401
5399
|
confidence: "partial",
|
|
5402
|
-
metrics:
|
|
5400
|
+
metrics: {
|
|
5403
5401
|
tokensInput: input,
|
|
5404
5402
|
tokensCachedInput: cached,
|
|
5405
5403
|
tokensOutput: output,
|
|
@@ -5407,7 +5405,7 @@ async function parseCodexSessionFile(filePath, options) {
|
|
|
5407
5405
|
tokensTotal: total,
|
|
5408
5406
|
modelContextWindow: usage.modelContextWindow,
|
|
5409
5407
|
reasoningEffort
|
|
5410
|
-
}
|
|
5408
|
+
}
|
|
5411
5409
|
}), { filePath, sourcePathHash, lineNumber, topType, payloadType, options }));
|
|
5412
5410
|
}
|
|
5413
5411
|
lastTotalTokenUsage = { input, cached, output, reasoning, total };
|
|
@@ -5708,18 +5706,6 @@ function tokenUsageFromRecord(usage, info) {
|
|
|
5708
5706
|
modelContextWindow: numberField(info, "model_context_window")
|
|
5709
5707
|
};
|
|
5710
5708
|
}
|
|
5711
|
-
function excludeForkInheritedCache(usage, forked) {
|
|
5712
|
-
if (!forked || !usage) {
|
|
5713
|
-
return usage;
|
|
5714
|
-
}
|
|
5715
|
-
const cached = usage.tokensCachedInput ?? 0;
|
|
5716
|
-
return {
|
|
5717
|
-
...usage,
|
|
5718
|
-
tokensInput: Math.max(0, (usage.tokensInput ?? 0) - cached) || void 0,
|
|
5719
|
-
tokensCachedInput: void 0,
|
|
5720
|
-
tokensTotal: Math.max(0, (usage.tokensTotal ?? 0) - cached) || void 0
|
|
5721
|
-
};
|
|
5722
|
-
}
|
|
5723
5709
|
function toolNameFromPayload(payload, payloadType) {
|
|
5724
5710
|
if (payloadType === "web_search_call") {
|
|
5725
5711
|
return "web_search";
|
|
@@ -9079,11 +9065,121 @@ function projectFromCwd(cwd, repoRoot) {
|
|
|
9079
9065
|
const basis = repoRoot || cwd;
|
|
9080
9066
|
return basis ? path15.basename(basis) || SOURCE : SOURCE;
|
|
9081
9067
|
}
|
|
9082
|
-
function
|
|
9068
|
+
function unixFrom(value) {
|
|
9083
9069
|
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
9084
9070
|
return void 0;
|
|
9085
9071
|
}
|
|
9086
|
-
return
|
|
9072
|
+
return value;
|
|
9073
|
+
}
|
|
9074
|
+
function isoFromUnix(value) {
|
|
9075
|
+
const unix = unixFrom(value);
|
|
9076
|
+
return unix === void 0 ? void 0 : timestampFrom(unix);
|
|
9077
|
+
}
|
|
9078
|
+
function maxUnix(...values) {
|
|
9079
|
+
let max;
|
|
9080
|
+
for (const value of values) {
|
|
9081
|
+
const unix = unixFrom(value);
|
|
9082
|
+
if (unix === void 0) {
|
|
9083
|
+
continue;
|
|
9084
|
+
}
|
|
9085
|
+
if (max === void 0 || unix > max) {
|
|
9086
|
+
max = unix;
|
|
9087
|
+
}
|
|
9088
|
+
}
|
|
9089
|
+
return max;
|
|
9090
|
+
}
|
|
9091
|
+
function isReapEndReason(reason) {
|
|
9092
|
+
return reason === "ws_orphan_reap" || reason === "startup_orphan_reap";
|
|
9093
|
+
}
|
|
9094
|
+
function isSyntheticUserContent(content) {
|
|
9095
|
+
if (!content) {
|
|
9096
|
+
return false;
|
|
9097
|
+
}
|
|
9098
|
+
const trimmed = content.trim();
|
|
9099
|
+
return trimmed.startsWith("[CONTEXT COMPACTION") || trimmed.startsWith("[IMPORTANT: Background process");
|
|
9100
|
+
}
|
|
9101
|
+
var INT_METRIC_KEYS = [
|
|
9102
|
+
"tokensInput",
|
|
9103
|
+
"tokensOutput",
|
|
9104
|
+
"tokensCacheReadInput",
|
|
9105
|
+
"tokensCacheCreationInput",
|
|
9106
|
+
"tokensCachedInput",
|
|
9107
|
+
"tokensReasoningOutput",
|
|
9108
|
+
"tokensTotal",
|
|
9109
|
+
"modelCalls"
|
|
9110
|
+
];
|
|
9111
|
+
function splitMetricBag(bag, fractions) {
|
|
9112
|
+
if (fractions.length === 0) {
|
|
9113
|
+
return [];
|
|
9114
|
+
}
|
|
9115
|
+
if (fractions.length === 1) {
|
|
9116
|
+
return [{ ...bag }];
|
|
9117
|
+
}
|
|
9118
|
+
const out = fractions.map(() => ({}));
|
|
9119
|
+
for (const key of INT_METRIC_KEYS) {
|
|
9120
|
+
const value = bag[key];
|
|
9121
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value === 0) {
|
|
9122
|
+
continue;
|
|
9123
|
+
}
|
|
9124
|
+
let allocated = 0;
|
|
9125
|
+
for (let i = 0; i < fractions.length; i += 1) {
|
|
9126
|
+
const part = i === fractions.length - 1 ? value - allocated : Math.floor(value * fractions[i]);
|
|
9127
|
+
allocated += part;
|
|
9128
|
+
if (part) {
|
|
9129
|
+
out[i][key] = part;
|
|
9130
|
+
}
|
|
9131
|
+
}
|
|
9132
|
+
}
|
|
9133
|
+
const cost = bag.costUsd;
|
|
9134
|
+
if (typeof cost === "number" && Number.isFinite(cost) && cost > 0) {
|
|
9135
|
+
let allocated = 0;
|
|
9136
|
+
for (let i = 0; i < fractions.length; i += 1) {
|
|
9137
|
+
const part = i === fractions.length - 1 ? Math.round((cost - allocated) * 1e6) / 1e6 : Math.round(cost * fractions[i] * 1e6) / 1e6;
|
|
9138
|
+
allocated += part;
|
|
9139
|
+
if (part) {
|
|
9140
|
+
out[i].costUsd = part;
|
|
9141
|
+
}
|
|
9142
|
+
}
|
|
9143
|
+
}
|
|
9144
|
+
return out;
|
|
9145
|
+
}
|
|
9146
|
+
function metricBagHasWork(bag) {
|
|
9147
|
+
return INT_METRIC_KEYS.some((key) => (bag[key] || 0) > 0);
|
|
9148
|
+
}
|
|
9149
|
+
function allocateUsageAcrossTurns(usage, turns) {
|
|
9150
|
+
const first = unixFrom(usage.first_seen);
|
|
9151
|
+
const last = unixFrom(usage.last_seen);
|
|
9152
|
+
const start = first ?? last;
|
|
9153
|
+
const end = last ?? first;
|
|
9154
|
+
if (start === void 0 || end === void 0) {
|
|
9155
|
+
const turn = turns.at(-1);
|
|
9156
|
+
return [{ turn, fraction: 1, tsUnix: turn?.endUnix || turn?.startedUnix || 0 }];
|
|
9157
|
+
}
|
|
9158
|
+
if (turns.length === 0) {
|
|
9159
|
+
return [{ fraction: 1, tsUnix: end }];
|
|
9160
|
+
}
|
|
9161
|
+
if (end <= start) {
|
|
9162
|
+
const turn = turns.find((item) => start >= item.startedUnix && start < item.endUnix) || [...turns].reverse().find((item) => item.startedUnix <= start) || turns[0];
|
|
9163
|
+
return [{ turn, fraction: 1, tsUnix: start }];
|
|
9164
|
+
}
|
|
9165
|
+
const overlaps = [];
|
|
9166
|
+
for (const turn of turns) {
|
|
9167
|
+
const lo = Math.max(start, turn.startedUnix);
|
|
9168
|
+
const hi = Math.min(end, turn.endUnix);
|
|
9169
|
+
if (hi > lo) {
|
|
9170
|
+
overlaps.push({ turn, overlap: hi - lo });
|
|
9171
|
+
}
|
|
9172
|
+
}
|
|
9173
|
+
const total = overlaps.reduce((sum, item) => sum + item.overlap, 0);
|
|
9174
|
+
if (total <= 0) {
|
|
9175
|
+
const turn = [...turns].reverse().find((item) => item.startedUnix <= end) || turns[0];
|
|
9176
|
+
return [{ turn, fraction: 1, tsUnix: Math.min(end, turn?.endUnix || end) }];
|
|
9177
|
+
}
|
|
9178
|
+
return overlaps.map((item) => ({
|
|
9179
|
+
turn: item.turn,
|
|
9180
|
+
fraction: item.overlap / total,
|
|
9181
|
+
tsUnix: Math.min(end, item.turn.endUnix)
|
|
9182
|
+
}));
|
|
9087
9183
|
}
|
|
9088
9184
|
function tableColumns(db, table) {
|
|
9089
9185
|
try {
|
|
@@ -9260,6 +9356,7 @@ async function parseHermesDb(filePath, options) {
|
|
|
9260
9356
|
"parent_session_id",
|
|
9261
9357
|
"started_at",
|
|
9262
9358
|
"ended_at",
|
|
9359
|
+
"end_reason",
|
|
9263
9360
|
"message_count",
|
|
9264
9361
|
"tool_call_count",
|
|
9265
9362
|
"input_tokens",
|
|
@@ -9349,8 +9446,9 @@ async function parseHermesDb(filePath, options) {
|
|
|
9349
9446
|
if (!sessionHasWork(session, usageRows) && messages.length === 0) {
|
|
9350
9447
|
continue;
|
|
9351
9448
|
}
|
|
9352
|
-
const
|
|
9353
|
-
|
|
9449
|
+
const startedUnix = unixFrom(session.started_at);
|
|
9450
|
+
const startedTs = isoFromUnix(startedUnix);
|
|
9451
|
+
if (!startedUnix || !startedTs) {
|
|
9354
9452
|
continue;
|
|
9355
9453
|
}
|
|
9356
9454
|
lineNumber += 1;
|
|
@@ -9378,7 +9476,15 @@ async function parseHermesDb(filePath, options) {
|
|
|
9378
9476
|
if (started) {
|
|
9379
9477
|
events.push(started);
|
|
9380
9478
|
}
|
|
9381
|
-
const
|
|
9479
|
+
const lastMessageUnix = maxUnix(...messages.map((message) => message.timestamp));
|
|
9480
|
+
const lastUsageUnix = maxUnix(
|
|
9481
|
+
...usageRows.flatMap((usage) => [usage.last_seen, usage.first_seen])
|
|
9482
|
+
);
|
|
9483
|
+
const lastActivityUnix = unixFrom(session.last_activity_at);
|
|
9484
|
+
const endReason = stringField(session, "end_reason");
|
|
9485
|
+
const closedUnix = isReapEndReason(endReason) ? void 0 : unixFrom(session.ended_at);
|
|
9486
|
+
const endedUnix = maxUnix(lastMessageUnix, lastUsageUnix, closedUnix) ?? lastActivityUnix;
|
|
9487
|
+
const endedTs = isoFromUnix(endedUnix);
|
|
9382
9488
|
if (endedTs && endedTs !== startedTs) {
|
|
9383
9489
|
lineNumber += 1;
|
|
9384
9490
|
const ended = makeEvent({
|
|
@@ -9398,16 +9504,102 @@ async function parseHermesDb(filePath, options) {
|
|
|
9398
9504
|
refs: refs({
|
|
9399
9505
|
sourceId: `${ctx.rawId}:session:ended`,
|
|
9400
9506
|
hermesPlatform: ctx.platform,
|
|
9401
|
-
hermesProfile: ctx.profileName
|
|
9507
|
+
hermesProfile: ctx.profileName,
|
|
9508
|
+
hermesEndReason: endReason
|
|
9402
9509
|
})
|
|
9403
9510
|
}, lineNumber, filePath, options);
|
|
9404
9511
|
if (ended) {
|
|
9405
9512
|
events.push(ended);
|
|
9406
9513
|
}
|
|
9407
9514
|
}
|
|
9515
|
+
const userTurns = [];
|
|
9516
|
+
for (const message of messages) {
|
|
9517
|
+
const tsUnix = unixFrom(message.timestamp);
|
|
9518
|
+
if (!tsUnix) {
|
|
9519
|
+
continue;
|
|
9520
|
+
}
|
|
9521
|
+
const role = stringField(message, "role");
|
|
9522
|
+
if (role === "user") {
|
|
9523
|
+
if (isSyntheticUserContent(stringField(message, "content"))) {
|
|
9524
|
+
continue;
|
|
9525
|
+
}
|
|
9526
|
+
const index = userTurns.length + 1;
|
|
9527
|
+
userTurns.push({
|
|
9528
|
+
index,
|
|
9529
|
+
turnId: `${ctx.sessionId}:turn:${index}`,
|
|
9530
|
+
startedUnix: tsUnix,
|
|
9531
|
+
endUnix: tsUnix
|
|
9532
|
+
});
|
|
9533
|
+
continue;
|
|
9534
|
+
}
|
|
9535
|
+
const current = userTurns[userTurns.length - 1];
|
|
9536
|
+
if (current && (role === "assistant" || role === "tool")) {
|
|
9537
|
+
current.endUnix = Math.max(current.endUnix, tsUnix);
|
|
9538
|
+
}
|
|
9539
|
+
}
|
|
9408
9540
|
if (usageRows.length > 0) {
|
|
9409
9541
|
for (const usage of usageRows) {
|
|
9410
|
-
const
|
|
9542
|
+
const allocations = allocateUsageAcrossTurns(usage, userTurns);
|
|
9543
|
+
const metricsParts = splitMetricBag(usageMetrics2(usage), allocations.map((item) => item.fraction));
|
|
9544
|
+
const model = stringField(usage, "model") || ctx.model;
|
|
9545
|
+
const provider = stringField(usage, "billing_provider") || ctx.provider;
|
|
9546
|
+
const task = stringField(usage, "task") || "main";
|
|
9547
|
+
const billing = stringField(usage, "billing_provider") || "";
|
|
9548
|
+
for (let i = 0; i < allocations.length; i += 1) {
|
|
9549
|
+
const allocation = allocations[i];
|
|
9550
|
+
const metrics = metricsParts[i];
|
|
9551
|
+
if (!metrics || !metricBagHasWork(metrics)) {
|
|
9552
|
+
continue;
|
|
9553
|
+
}
|
|
9554
|
+
const ts = isoFromUnix(allocation.tsUnix) || startedTs;
|
|
9555
|
+
const turnId = allocation.turn?.turnId;
|
|
9556
|
+
const slice = allocation.turn ? `:turn:${allocation.turn.index}` : "";
|
|
9557
|
+
lineNumber += 1;
|
|
9558
|
+
const event = makeEvent({
|
|
9559
|
+
schemaVersion: AGENT_TIME_SCHEMA_VERSION,
|
|
9560
|
+
ts,
|
|
9561
|
+
type: "model.usage",
|
|
9562
|
+
source: SOURCE,
|
|
9563
|
+
workspaceId: ctx.workspaceId,
|
|
9564
|
+
project: ctx.project,
|
|
9565
|
+
cwd: ctx.cwd,
|
|
9566
|
+
sessionId: ctx.sessionId,
|
|
9567
|
+
turnId,
|
|
9568
|
+
agent: SOURCE,
|
|
9569
|
+
model,
|
|
9570
|
+
provider,
|
|
9571
|
+
success: true,
|
|
9572
|
+
metrics,
|
|
9573
|
+
confidence: allocations.length > 1 ? "derived" : "exact",
|
|
9574
|
+
refs: refs({
|
|
9575
|
+
sourceId: `${ctx.rawId}:model:${model || "unknown"}:${task}:${billing}${slice}`,
|
|
9576
|
+
hermesPlatform: ctx.platform,
|
|
9577
|
+
hermesProfile: ctx.profileName,
|
|
9578
|
+
hermesTask: stringField(usage, "task"),
|
|
9579
|
+
hermesBillingMode: stringField(usage, "billing_mode")
|
|
9580
|
+
})
|
|
9581
|
+
}, lineNumber, filePath, options);
|
|
9582
|
+
if (event) {
|
|
9583
|
+
events.push(event);
|
|
9584
|
+
}
|
|
9585
|
+
}
|
|
9586
|
+
}
|
|
9587
|
+
} else if ((numberField(session, "input_tokens") || 0) + (numberField(session, "output_tokens") || 0) > 0) {
|
|
9588
|
+
const fallbackUsage = {
|
|
9589
|
+
first_seen: startedUnix,
|
|
9590
|
+
last_seen: endedUnix || startedUnix
|
|
9591
|
+
};
|
|
9592
|
+
const allocations = allocateUsageAcrossTurns(fallbackUsage, userTurns);
|
|
9593
|
+
const metricsParts = splitMetricBag(usageMetrics2(session), allocations.map((item) => item.fraction));
|
|
9594
|
+
for (let i = 0; i < allocations.length; i += 1) {
|
|
9595
|
+
const allocation = allocations[i];
|
|
9596
|
+
const metrics = metricsParts[i];
|
|
9597
|
+
if (!metrics || !metricBagHasWork(metrics)) {
|
|
9598
|
+
continue;
|
|
9599
|
+
}
|
|
9600
|
+
const ts = isoFromUnix(allocation.tsUnix) || startedTs;
|
|
9601
|
+
const turnId = allocation.turn?.turnId;
|
|
9602
|
+
const slice = allocation.turn ? `:turn:${allocation.turn.index}` : "";
|
|
9411
9603
|
lineNumber += 1;
|
|
9412
9604
|
const event = makeEvent({
|
|
9413
9605
|
schemaVersion: AGENT_TIME_SCHEMA_VERSION,
|
|
@@ -9418,62 +9610,72 @@ async function parseHermesDb(filePath, options) {
|
|
|
9418
9610
|
project: ctx.project,
|
|
9419
9611
|
cwd: ctx.cwd,
|
|
9420
9612
|
sessionId: ctx.sessionId,
|
|
9613
|
+
turnId,
|
|
9421
9614
|
agent: SOURCE,
|
|
9422
|
-
model:
|
|
9423
|
-
provider:
|
|
9615
|
+
model: ctx.model,
|
|
9616
|
+
provider: ctx.provider,
|
|
9424
9617
|
success: true,
|
|
9425
|
-
metrics
|
|
9426
|
-
confidence: "
|
|
9618
|
+
metrics,
|
|
9619
|
+
confidence: "partial",
|
|
9427
9620
|
refs: refs({
|
|
9428
|
-
sourceId: `${ctx.rawId}:model
|
|
9621
|
+
sourceId: `${ctx.rawId}:model:session${slice}`,
|
|
9429
9622
|
hermesPlatform: ctx.platform,
|
|
9430
|
-
hermesProfile: ctx.profileName
|
|
9431
|
-
hermesTask: stringField(usage, "task"),
|
|
9432
|
-
hermesBillingMode: stringField(usage, "billing_mode")
|
|
9623
|
+
hermesProfile: ctx.profileName
|
|
9433
9624
|
})
|
|
9434
9625
|
}, lineNumber, filePath, options);
|
|
9435
9626
|
if (event) {
|
|
9436
9627
|
events.push(event);
|
|
9437
9628
|
}
|
|
9438
9629
|
}
|
|
9439
|
-
}
|
|
9630
|
+
}
|
|
9631
|
+
let turnIndex = 0;
|
|
9632
|
+
let activeTurnId;
|
|
9633
|
+
let activeTurnStartedTs;
|
|
9634
|
+
let lastWorkTs;
|
|
9635
|
+
const closeActiveTurn = (ts) => {
|
|
9636
|
+
if (!activeTurnId) {
|
|
9637
|
+
return;
|
|
9638
|
+
}
|
|
9440
9639
|
lineNumber += 1;
|
|
9441
|
-
const
|
|
9640
|
+
const completed = makeEvent({
|
|
9442
9641
|
schemaVersion: AGENT_TIME_SCHEMA_VERSION,
|
|
9443
|
-
ts
|
|
9444
|
-
type: "
|
|
9642
|
+
ts,
|
|
9643
|
+
type: "turn.completed",
|
|
9445
9644
|
source: SOURCE,
|
|
9446
9645
|
workspaceId: ctx.workspaceId,
|
|
9447
9646
|
project: ctx.project,
|
|
9448
9647
|
cwd: ctx.cwd,
|
|
9449
9648
|
sessionId: ctx.sessionId,
|
|
9649
|
+
turnId: activeTurnId,
|
|
9450
9650
|
agent: SOURCE,
|
|
9451
|
-
|
|
9452
|
-
|
|
9453
|
-
success: true,
|
|
9454
|
-
metrics: usageMetrics2(session),
|
|
9455
|
-
confidence: "partial",
|
|
9456
|
-
refs: refs({
|
|
9457
|
-
sourceId: `${ctx.rawId}:model:session`,
|
|
9458
|
-
hermesPlatform: ctx.platform,
|
|
9459
|
-
hermesProfile: ctx.profileName
|
|
9460
|
-
})
|
|
9651
|
+
confidence: "derived",
|
|
9652
|
+
refs: refs({ sourceId: `${ctx.rawId}:turn:${turnIndex}:end` })
|
|
9461
9653
|
}, lineNumber, filePath, options);
|
|
9462
|
-
if (
|
|
9463
|
-
events.push(
|
|
9654
|
+
if (completed) {
|
|
9655
|
+
events.push(completed);
|
|
9464
9656
|
}
|
|
9465
|
-
|
|
9466
|
-
|
|
9467
|
-
|
|
9657
|
+
activeTurnId = void 0;
|
|
9658
|
+
activeTurnStartedTs = void 0;
|
|
9659
|
+
lastWorkTs = void 0;
|
|
9660
|
+
};
|
|
9468
9661
|
for (const message of messages) {
|
|
9469
|
-
const
|
|
9470
|
-
|
|
9662
|
+
const tsUnix = unixFrom(message.timestamp);
|
|
9663
|
+
const ts = isoFromUnix(tsUnix);
|
|
9664
|
+
if (!tsUnix || !ts) {
|
|
9471
9665
|
continue;
|
|
9472
9666
|
}
|
|
9473
9667
|
const role = stringField(message, "role");
|
|
9474
9668
|
if (role === "user") {
|
|
9669
|
+
if (isSyntheticUserContent(stringField(message, "content"))) {
|
|
9670
|
+
continue;
|
|
9671
|
+
}
|
|
9672
|
+
if (activeTurnId && activeTurnStartedTs) {
|
|
9673
|
+
closeActiveTurn(lastWorkTs || activeTurnStartedTs);
|
|
9674
|
+
}
|
|
9475
9675
|
turnIndex += 1;
|
|
9476
9676
|
activeTurnId = `${ctx.sessionId}:turn:${turnIndex}`;
|
|
9677
|
+
activeTurnStartedTs = ts;
|
|
9678
|
+
lastWorkTs = void 0;
|
|
9477
9679
|
lineNumber += 1;
|
|
9478
9680
|
const turnStarted = makeEvent({
|
|
9479
9681
|
schemaVersion: AGENT_TIME_SCHEMA_VERSION,
|
|
@@ -9514,6 +9716,10 @@ async function parseHermesDb(filePath, options) {
|
|
|
9514
9716
|
}
|
|
9515
9717
|
continue;
|
|
9516
9718
|
}
|
|
9719
|
+
if (role !== "assistant" && role !== "tool") {
|
|
9720
|
+
continue;
|
|
9721
|
+
}
|
|
9722
|
+
lastWorkTs = ts;
|
|
9517
9723
|
if (role !== "assistant") {
|
|
9518
9724
|
continue;
|
|
9519
9725
|
}
|
|
@@ -9568,6 +9774,9 @@ async function parseHermesDb(filePath, options) {
|
|
|
9568
9774
|
}
|
|
9569
9775
|
}
|
|
9570
9776
|
}
|
|
9777
|
+
if (activeTurnId) {
|
|
9778
|
+
closeActiveTurn(lastWorkTs || endedTs || startedTs);
|
|
9779
|
+
}
|
|
9571
9780
|
}
|
|
9572
9781
|
return events.sort((a, b) => a.ts.localeCompare(b.ts) || (a.id || "").localeCompare(b.id || ""));
|
|
9573
9782
|
} finally {
|
|
@@ -9628,7 +9837,6 @@ description: "VibeTime session sync \u2014 thin trigger that runs local backfill
|
|
|
9628
9837
|
author: VibeTime
|
|
9629
9838
|
hooks:
|
|
9630
9839
|
- on_session_start
|
|
9631
|
-
- on_session_end
|
|
9632
9840
|
- on_session_finalize
|
|
9633
9841
|
- post_tool_call
|
|
9634
9842
|
`;
|
|
@@ -9639,6 +9847,10 @@ function pluginInit() {
|
|
|
9639
9847
|
|
|
9640
9848
|
Thin trigger only: spawn \`vibetime hook --agent hermes\` on session/tool
|
|
9641
9849
|
lifecycle events. Token usage is read from state.db by vibetime backfill.
|
|
9850
|
+
|
|
9851
|
+
\`on_session_end\` is a per-turn observer in Hermes (end of every
|
|
9852
|
+
run_conversation). Do not map it to SessionEnd \u2014 that would fire a
|
|
9853
|
+
backfill after every prompt. Real session teardown is on_session_finalize.
|
|
9642
9854
|
"""
|
|
9643
9855
|
from __future__ import annotations
|
|
9644
9856
|
|
|
@@ -9654,15 +9866,20 @@ def _report(payload: dict[str, Any]) -> None:
|
|
|
9654
9866
|
if not command:
|
|
9655
9867
|
return
|
|
9656
9868
|
try:
|
|
9657
|
-
|
|
9869
|
+
# Popen + close stdin: do not block the Hermes tool loop waiting for
|
|
9870
|
+
# vibetime hook / backfill. stdout/stderr inherited would spam the
|
|
9871
|
+
# agent UI, so discard them.
|
|
9872
|
+
proc = subprocess.Popen(
|
|
9658
9873
|
[command, "hook", "--agent", "hermes"],
|
|
9659
|
-
|
|
9660
|
-
|
|
9661
|
-
|
|
9662
|
-
timeout=10,
|
|
9663
|
-
check=False,
|
|
9874
|
+
stdin=subprocess.PIPE,
|
|
9875
|
+
stdout=subprocess.DEVNULL,
|
|
9876
|
+
stderr=subprocess.DEVNULL,
|
|
9664
9877
|
env=os.environ.copy(),
|
|
9878
|
+
start_new_session=True,
|
|
9665
9879
|
)
|
|
9880
|
+
if proc.stdin is not None:
|
|
9881
|
+
proc.stdin.write(json.dumps(payload).encode())
|
|
9882
|
+
proc.stdin.close()
|
|
9666
9883
|
except Exception:
|
|
9667
9884
|
return
|
|
9668
9885
|
|
|
@@ -9671,8 +9888,8 @@ def on_session_start(*, session_id: str = "", **_: Any) -> None:
|
|
|
9671
9888
|
_report({"hook_event_name": "SessionStart", "session_id": session_id})
|
|
9672
9889
|
|
|
9673
9890
|
|
|
9674
|
-
def
|
|
9675
|
-
_report({"hook_event_name": "SessionEnd", "session_id": session_id})
|
|
9891
|
+
def on_session_finalize(*, session_id: str = "", **_: Any) -> None:
|
|
9892
|
+
_report({"hook_event_name": "SessionEnd", "session_id": session_id or ""})
|
|
9676
9893
|
|
|
9677
9894
|
|
|
9678
9895
|
def on_post_tool_call(*, tool_name: str = "", session_id: str = "", **_: Any) -> None:
|
|
@@ -9685,8 +9902,7 @@ def on_post_tool_call(*, tool_name: str = "", session_id: str = "", **_: Any) ->
|
|
|
9685
9902
|
|
|
9686
9903
|
def register(ctx) -> None:
|
|
9687
9904
|
ctx.register_hook("on_session_start", on_session_start)
|
|
9688
|
-
ctx.register_hook("
|
|
9689
|
-
ctx.register_hook("on_session_finalize", on_session_end)
|
|
9905
|
+
ctx.register_hook("on_session_finalize", on_session_finalize)
|
|
9690
9906
|
ctx.register_hook("post_tool_call", on_post_tool_call)
|
|
9691
9907
|
`;
|
|
9692
9908
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yhong91/vibetime",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.74",
|
|
5
5
|
"description": "vibetime CLI — install AI-agent hooks (Claude Code, Codex, OpenCode, Pi, Cursor) and report activity to vibetime.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"publishConfig": {
|