@yhong91/vibetime 0.1.29 → 0.1.31

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/bin/vibetime.mjs +114 -14
  2. package/package.json +1 -1
package/bin/vibetime.mjs CHANGED
@@ -1928,7 +1928,7 @@ function countTextLines(text) {
1928
1928
  }
1929
1929
 
1930
1930
  // src/lib/constants.ts
1931
- var PACKAGE_VERSION = true ? "0.1.29" : "0.1.1";
1931
+ var PACKAGE_VERSION = true ? "0.1.31" : "0.1.1";
1932
1932
  var DEFAULT_API_URL = "http://121.196.224.82:3001";
1933
1933
  var DEFAULT_BACKFILL_BATCH_SIZE = 50;
1934
1934
  var DEFAULT_BACKFILL_BATCH_BYTES = 800 * 1024;
@@ -4913,8 +4913,10 @@ async function parseCodexSessionFile(filePath, options) {
4913
4913
  let lastTurnIdForComplete;
4914
4914
  let turnIdAtLastUserMessage;
4915
4915
  let sessionMetaLocked = false;
4916
+ let forkedSession = false;
4916
4917
  let lastTotalTokenUsage;
4917
4918
  let lastPerCallUsageKey;
4919
+ let compactionPending = false;
4918
4920
  const pendingToolCalls = /* @__PURE__ */ new Map();
4919
4921
  for (const [index, line] of lines.entries()) {
4920
4922
  const lineNumber = index + 1;
@@ -4932,6 +4934,7 @@ async function parseCodexSessionFile(filePath, options) {
4932
4934
  }
4933
4935
  sessionMetaLocked = true;
4934
4936
  const forkedFromId = stringField(payload, "forked_from_id");
4937
+ forkedSession = Boolean(forkedFromId);
4935
4938
  const inherited = forkedFromId ? await readPersistedSessionContextFromOptions(options, forkedFromId) : void 0;
4936
4939
  sessionId = stringField(payload, "id") || sessionId;
4937
4940
  cwd = inherited?.cwd || stringField(payload, "cwd") || cwd;
@@ -4953,6 +4956,10 @@ async function parseCodexSessionFile(filePath, options) {
4953
4956
  sessionStartEmitted = true;
4954
4957
  continue;
4955
4958
  }
4959
+ if (topType === "compacted") {
4960
+ compactionPending = true;
4961
+ continue;
4962
+ }
4956
4963
  if (topType === "turn_context") {
4957
4964
  currentTurnId = stringField(payload, "turn_id") || currentTurnId;
4958
4965
  cwd = stringField(payload, "cwd") || cwd;
@@ -5080,12 +5087,32 @@ async function parseCodexSessionFile(filePath, options) {
5080
5087
  project,
5081
5088
  model: rewriteCodexModelForTier(model, serviceTier),
5082
5089
  confidence: "partial",
5083
- metrics: usage
5090
+ metrics: excludeForkInheritedCache(usage, forkedSession)
5084
5091
  }), { filePath, sourcePathHash, lineNumber, topType, payloadType, options }));
5085
5092
  lastPerCallUsageKey = usageKey;
5086
5093
  }
5087
5094
  break;
5088
5095
  }
5096
+ if (compactionPending && lastTotalTokenUsage && total < lastTotalTokenUsage.total) {
5097
+ const lastUsage = lastTokenUsageFromPayload(payload);
5098
+ if (lastUsage) {
5099
+ events.push(withBackfillRefs(baseCodexEvent({
5100
+ ts,
5101
+ type: "model.usage",
5102
+ sessionId,
5103
+ turnId: currentTurnId,
5104
+ cwd,
5105
+ project,
5106
+ model: rewriteCodexModelForTier(model, serviceTier),
5107
+ confidence: "partial",
5108
+ metrics: excludeForkInheritedCache({ ...lastUsage, reasoningEffort }, forkedSession)
5109
+ }), { filePath, sourcePathHash, lineNumber, topType, payloadType, options }));
5110
+ }
5111
+ lastTotalTokenUsage = { input, cached, output, reasoning, total };
5112
+ compactionPending = false;
5113
+ break;
5114
+ }
5115
+ compactionPending = false;
5089
5116
  if (lastTotalTokenUsage && total >= lastTotalTokenUsage.total) {
5090
5117
  const deltaInput = input - lastTotalTokenUsage.input;
5091
5118
  const deltaCached = cached - lastTotalTokenUsage.cached;
@@ -5102,7 +5129,7 @@ async function parseCodexSessionFile(filePath, options) {
5102
5129
  project,
5103
5130
  model: rewriteCodexModelForTier(model, serviceTier),
5104
5131
  confidence: "partial",
5105
- metrics: {
5132
+ metrics: excludeForkInheritedCache({
5106
5133
  tokensInput: deltaInput > 0 ? deltaInput : void 0,
5107
5134
  tokensCachedInput: deltaCached > 0 ? deltaCached : void 0,
5108
5135
  tokensOutput: deltaOutput > 0 ? deltaOutput : void 0,
@@ -5110,7 +5137,7 @@ async function parseCodexSessionFile(filePath, options) {
5110
5137
  tokensTotal: deltaTotal > 0 ? deltaTotal : void 0,
5111
5138
  modelContextWindow: usage.modelContextWindow,
5112
5139
  reasoningEffort
5113
- }
5140
+ }, forkedSession)
5114
5141
  }), { filePath, sourcePathHash, lineNumber, topType, payloadType, options }));
5115
5142
  }
5116
5143
  } else {
@@ -5123,7 +5150,7 @@ async function parseCodexSessionFile(filePath, options) {
5123
5150
  project,
5124
5151
  model: rewriteCodexModelForTier(model, serviceTier),
5125
5152
  confidence: "partial",
5126
- metrics: {
5153
+ metrics: excludeForkInheritedCache({
5127
5154
  tokensInput: input,
5128
5155
  tokensCachedInput: cached,
5129
5156
  tokensOutput: output,
@@ -5131,7 +5158,7 @@ async function parseCodexSessionFile(filePath, options) {
5131
5158
  tokensTotal: total,
5132
5159
  modelContextWindow: usage.modelContextWindow,
5133
5160
  reasoningEffort
5134
- }
5161
+ }, forkedSession)
5135
5162
  }), { filePath, sourcePathHash, lineNumber, topType, payloadType, options }));
5136
5163
  }
5137
5164
  lastTotalTokenUsage = { input, cached, output, reasoning, total };
@@ -5389,6 +5416,13 @@ function tokenUsageFromPayload(payload) {
5389
5416
  const info = objectField(payload, "info");
5390
5417
  const totalUsage = objectField(info, "total_token_usage");
5391
5418
  const usage = Object.keys(totalUsage).length > 0 ? totalUsage : objectField(info, "last_token_usage");
5419
+ return tokenUsageFromRecord(usage, info);
5420
+ }
5421
+ function lastTokenUsageFromPayload(payload) {
5422
+ const info = objectField(payload, "info");
5423
+ return tokenUsageFromRecord(objectField(info, "last_token_usage"), info);
5424
+ }
5425
+ function tokenUsageFromRecord(usage, info) {
5392
5426
  if (Object.keys(usage).length === 0) {
5393
5427
  return;
5394
5428
  }
@@ -5410,6 +5444,18 @@ function tokenUsageFromPayload(payload) {
5410
5444
  modelContextWindow: numberField(info, "model_context_window")
5411
5445
  };
5412
5446
  }
5447
+ function excludeForkInheritedCache(usage, forked) {
5448
+ if (!forked || !usage) {
5449
+ return usage;
5450
+ }
5451
+ const cached = usage.tokensCachedInput ?? 0;
5452
+ return {
5453
+ ...usage,
5454
+ tokensInput: Math.max(0, (usage.tokensInput ?? 0) - cached) || void 0,
5455
+ tokensCachedInput: void 0,
5456
+ tokensTotal: Math.max(0, (usage.tokensTotal ?? 0) - cached) || void 0
5457
+ };
5458
+ }
5413
5459
  function toolNameFromPayload(payload, payloadType) {
5414
5460
  if (payloadType === "web_search_call") {
5415
5461
  return "web_search";
@@ -7390,11 +7436,13 @@ function parseQoderCnPaths(filePath) {
7390
7436
  let sessionId = "";
7391
7437
  let projectName = "";
7392
7438
  let configDir2 = "";
7439
+ let mainTranscriptPath;
7393
7440
  if (subagentsIdx > 2) {
7394
7441
  sessionId = parts[subagentsIdx - 1];
7395
7442
  projectName = parts[subagentsIdx - 2];
7396
7443
  const projectsIdx = parts.lastIndexOf("projects");
7397
7444
  configDir2 = parts.slice(0, projectsIdx).join(path16.sep);
7445
+ mainTranscriptPath = [...parts.slice(0, subagentsIdx - 1), `${sessionId}.jsonl`].join(path16.sep);
7398
7446
  } else {
7399
7447
  const filename = parts.at(-1) || "";
7400
7448
  sessionId = path16.basename(filename, ".jsonl");
@@ -7402,7 +7450,24 @@ function parseQoderCnPaths(filePath) {
7402
7450
  const projectsIdx = parts.lastIndexOf("projects");
7403
7451
  configDir2 = parts.slice(0, projectsIdx).join(path16.sep);
7404
7452
  }
7405
- return { configDir: configDir2, projectName, sessionId };
7453
+ return { configDir: configDir2, projectName, sessionId, mainTranscriptPath };
7454
+ }
7455
+ function rebuildEventIdentity2(event) {
7456
+ const importKey = createImportKey([
7457
+ event.source,
7458
+ event.refs?.sourcePathHash,
7459
+ event.refs?.sourceLine,
7460
+ event.type,
7461
+ event.refs?.sourceId
7462
+ ]);
7463
+ return {
7464
+ ...event,
7465
+ id: createStableEventId(importKey),
7466
+ refs: {
7467
+ ...event.refs,
7468
+ importKey
7469
+ }
7470
+ };
7406
7471
  }
7407
7472
  async function loadQoderCnModelNames(configDir2) {
7408
7473
  try {
@@ -7752,9 +7817,17 @@ async function parseQoderCnSessionFile(filePath, options) {
7752
7817
  }
7753
7818
  const validEvents = state.events.filter((event) => validateCanonicalEvent(event).valid);
7754
7819
  if (isSubagentSession) {
7755
- const parentSessionId = parseQoderCnPaths(filePath).sessionId;
7756
- if (parentSessionId) {
7757
- return validEvents.map((event) => ({ ...event, sessionId: parentSessionId }));
7820
+ const { sessionId: parentSessionId, mainTranscriptPath } = parseQoderCnPaths(filePath);
7821
+ if (parentSessionId && mainTranscriptPath) {
7822
+ const parentSourcePathHash = `sha256:${createStableHash(mainTranscriptPath)}`;
7823
+ return validEvents.map((event) => {
7824
+ const mapped = {
7825
+ ...event,
7826
+ sessionId: parentSessionId,
7827
+ refs: { ...event.refs, sourcePathHash: parentSourcePathHash }
7828
+ };
7829
+ return rebuildEventIdentity2(mapped);
7830
+ });
7758
7831
  }
7759
7832
  }
7760
7833
  return validEvents;
@@ -8133,11 +8206,13 @@ function parseQoderPaths(filePath) {
8133
8206
  let sessionId = "";
8134
8207
  let projectName = "";
8135
8208
  let configDir2 = "";
8209
+ let mainTranscriptPath;
8136
8210
  if (subagentsIdx > 2) {
8137
8211
  sessionId = parts[subagentsIdx - 1];
8138
8212
  projectName = parts[subagentsIdx - 2];
8139
8213
  const projectsIdx = parts.lastIndexOf("projects");
8140
8214
  configDir2 = parts.slice(0, projectsIdx).join(path17.sep);
8215
+ mainTranscriptPath = [...parts.slice(0, subagentsIdx - 1), `${sessionId}.jsonl`].join(path17.sep);
8141
8216
  } else {
8142
8217
  const filename = parts.at(-1) || "";
8143
8218
  sessionId = path17.basename(filename, ".jsonl");
@@ -8145,7 +8220,24 @@ function parseQoderPaths(filePath) {
8145
8220
  const projectsIdx = parts.lastIndexOf("projects");
8146
8221
  configDir2 = parts.slice(0, projectsIdx).join(path17.sep);
8147
8222
  }
8148
- return { configDir: configDir2, projectName, sessionId };
8223
+ return { configDir: configDir2, projectName, sessionId, mainTranscriptPath };
8224
+ }
8225
+ function rebuildEventIdentity3(event) {
8226
+ const importKey = createImportKey([
8227
+ event.source,
8228
+ event.refs?.sourcePathHash,
8229
+ event.refs?.sourceLine,
8230
+ event.type,
8231
+ event.refs?.sourceId
8232
+ ]);
8233
+ return {
8234
+ ...event,
8235
+ id: createStableEventId(importKey),
8236
+ refs: {
8237
+ ...event.refs,
8238
+ importKey
8239
+ }
8240
+ };
8149
8241
  }
8150
8242
  async function loadQoderModelNames(configDir2) {
8151
8243
  try {
@@ -8495,9 +8587,17 @@ async function parseQoderSessionFile(filePath, options) {
8495
8587
  }
8496
8588
  const validEvents = state.events.filter((event) => validateCanonicalEvent(event).valid);
8497
8589
  if (isSubagentSession) {
8498
- const parentSessionId = parseQoderPaths(filePath).sessionId;
8499
- if (parentSessionId) {
8500
- return validEvents.map((event) => ({ ...event, sessionId: parentSessionId }));
8590
+ const { sessionId: parentSessionId, mainTranscriptPath } = parseQoderPaths(filePath);
8591
+ if (parentSessionId && mainTranscriptPath) {
8592
+ const parentSourcePathHash = `sha256:${createStableHash(mainTranscriptPath)}`;
8593
+ return validEvents.map((event) => {
8594
+ const mapped = {
8595
+ ...event,
8596
+ sessionId: parentSessionId,
8597
+ refs: { ...event.refs, sourcePathHash: parentSourcePathHash }
8598
+ };
8599
+ return rebuildEventIdentity3(mapped);
8600
+ });
8501
8601
  }
8502
8602
  }
8503
8603
  return validEvents;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@yhong91/vibetime",
3
3
  "type": "module",
4
- "version": "0.1.29",
4
+ "version": "0.1.31",
5
5
  "description": "vibetime CLI — install AI-agent hooks (Claude Code, Codex, OpenCode, Pi) and report activity to vibetime.",
6
6
  "license": "MIT",
7
7
  "publishConfig": {