@yhong91/vibetime 0.1.48 → 0.1.50

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 +334 -85
  2. 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.48" : "0.1.1";
2050
+ var PACKAGE_VERSION = true ? "0.1.50" : "0.1.1";
2051
2051
  var DEFAULT_API_URL = "http://121.196.224.82:3001";
2052
2052
  var DEFAULT_BACKFILL_BATCH_SIZE = 50;
2053
2053
  var DEFAULT_BACKFILL_BATCH_BYTES = 800 * 1024;
@@ -7501,6 +7501,79 @@ function createOpenCodeAdapter() {
7501
7501
  // src/adapters/pi.ts
7502
7502
  import { readFile as readFile9 } from "node:fs/promises";
7503
7503
  import path15 from "node:path";
7504
+ function parsePiSubagentLink(filePath, headerParentSession) {
7505
+ if (headerParentSession) {
7506
+ const parentFile = path15.isAbsolute(headerParentSession) ? headerParentSession : void 0;
7507
+ const parentSessionId2 = parentFile ? path15.basename(parentFile, ".jsonl") : headerParentSession;
7508
+ return { parentSessionId: parentSessionId2, parentSessionFile: parentFile, explicit: true };
7509
+ }
7510
+ const normalized = filePath.replaceAll("\\", "/");
7511
+ const match = normalized.match(/([^/]+)\/[^/]+\/run-\d+\/session\.jsonl$/);
7512
+ if (!match) {
7513
+ return void 0;
7514
+ }
7515
+ const parentBasename = match[1];
7516
+ if (!parentBasename || parentBasename === "session") {
7517
+ return void 0;
7518
+ }
7519
+ const parentSessionId = parentBasename.endsWith(".jsonl") ? parentBasename.slice(0, -".jsonl".length) : parentBasename;
7520
+ const parentDir = path15.dirname(path15.dirname(path15.dirname(filePath)));
7521
+ const parentSessionFile = path15.join(path15.dirname(parentDir), `${parentBasename}.jsonl`);
7522
+ return { parentSessionId, parentSessionFile, explicit: false };
7523
+ }
7524
+ async function resolveParentContext(link, options) {
7525
+ if (link.parentSessionFile) {
7526
+ try {
7527
+ const text = await readFile9(link.parentSessionFile, "utf8");
7528
+ const firstLine = text.split("\n").find((line) => line.trim().length > 0);
7529
+ const raw = firstLine ? parseJsonLine(firstLine) : void 0;
7530
+ if (raw) {
7531
+ const id = stringField(raw, "id");
7532
+ const cwd = stringField(raw, "cwd");
7533
+ const project = cwd ? path15.basename(cwd) : void 0;
7534
+ if (id || cwd || project) {
7535
+ return { sessionId: id, cwd, project };
7536
+ }
7537
+ }
7538
+ } catch {
7539
+ }
7540
+ }
7541
+ const persisted = await readPersistedSessionContextFromOptions(options, link.parentSessionId);
7542
+ if (persisted) {
7543
+ return { cwd: persisted.cwd, project: persisted.project };
7544
+ }
7545
+ return void 0;
7546
+ }
7547
+ function foldEventsIntoParent(events, link) {
7548
+ const childSessionId = events.find((event) => event.sessionId)?.sessionId;
7549
+ return events.map((event) => {
7550
+ const rewritten = {
7551
+ ...event,
7552
+ sessionId: link.parentSessionId
7553
+ };
7554
+ if (childSessionId && childSessionId !== link.parentSessionId) {
7555
+ rewritten.refs = {
7556
+ ...event.refs,
7557
+ supersededSessionId: childSessionId
7558
+ };
7559
+ }
7560
+ return rebuildEventIdentity2(rewritten);
7561
+ });
7562
+ }
7563
+ function rebuildEventIdentity2(event) {
7564
+ const importKey = createImportKey([
7565
+ event.source,
7566
+ event.refs?.sourcePathHash,
7567
+ event.refs?.sourceLine,
7568
+ event.type,
7569
+ event.refs?.sourceId
7570
+ ]);
7571
+ return {
7572
+ ...event,
7573
+ id: createStableEventId(importKey),
7574
+ refs: { ...event.refs, importKey }
7575
+ };
7576
+ }
7504
7577
  async function parsePiSessionFile(filePath, options) {
7505
7578
  const text = await readFile9(filePath, "utf8");
7506
7579
  const lines = text.split("\n").filter(Boolean);
@@ -7511,6 +7584,7 @@ async function parsePiSessionFile(filePath, options) {
7511
7584
  let provider;
7512
7585
  let turnStartedAt;
7513
7586
  let reasoningEffort;
7587
+ let headerParentSession;
7514
7588
  const pendingToolCalls = /* @__PURE__ */ new Map();
7515
7589
  const state = new SessionParserState(filePath, options, (event) => basePiEvent({ ...event, cwd, project, model }));
7516
7590
  const push = (event, ln) => {
@@ -7534,6 +7608,7 @@ async function parsePiSessionFile(filePath, options) {
7534
7608
  state.sessionId = sessionId || state.sessionId;
7535
7609
  cwd = stringField(raw, "cwd") || cwd;
7536
7610
  project = cwd ? path15.basename(cwd) : project;
7611
+ headerParentSession = stringField(raw, "parentSession") || headerParentSession;
7537
7612
  continue;
7538
7613
  }
7539
7614
  if (entryType === "model_change") {
@@ -7717,6 +7792,17 @@ async function parsePiSessionFile(filePath, options) {
7717
7792
  if (isTurnIdle(state.currentTurnLastEventAt)) {
7718
7793
  state.closeTurn(state.currentTurnLastEventAt, lines.length);
7719
7794
  }
7795
+ const link = parsePiSubagentLink(filePath, headerParentSession);
7796
+ if (link) {
7797
+ const parentContext = await resolveParentContext(link, options);
7798
+ const effectiveLink = parentContext?.sessionId ? { ...link, parentSessionId: parentContext.sessionId } : link;
7799
+ if (parentContext) {
7800
+ cwd = cwd || parentContext.cwd;
7801
+ project = project || parentContext.project;
7802
+ }
7803
+ const events = state.events.filter((event) => matchesBackfillFilters(event, options));
7804
+ return foldEventsIntoParent(events, effectiveLink);
7805
+ }
7720
7806
  return state.events.filter((event) => matchesBackfillFilters(event, options));
7721
7807
  }
7722
7808
  function basePiEvent(event) {
@@ -8008,27 +8094,98 @@ import path17 from "node:path";
8008
8094
  import { access } from "node:fs/promises";
8009
8095
  import os7 from "node:os";
8010
8096
  import path16 from "node:path";
8097
+ function takeQoderDbModelCall(calls, requestId, blockStart) {
8098
+ if (requestId) {
8099
+ const call = calls.byRequestId.get(requestId)?.shift();
8100
+ if (call) {
8101
+ const index = calls.ordered.indexOf(call);
8102
+ if (index !== -1) {
8103
+ calls.ordered.splice(index, 1);
8104
+ }
8105
+ }
8106
+ return call;
8107
+ }
8108
+ if (!blockStart) {
8109
+ return void 0;
8110
+ }
8111
+ return calls.ordered.shift();
8112
+ }
8113
+ function appDataRoot(appDirName, home = os7.homedir()) {
8114
+ if (process.platform === "darwin") {
8115
+ return path16.join(home, "Library", "Application Support", appDirName);
8116
+ }
8117
+ if (process.platform === "win32") {
8118
+ return path16.join(process.env.APPDATA || path16.join(home, "AppData", "Roaming"), appDirName);
8119
+ }
8120
+ return path16.join(home, ".config", appDirName);
8121
+ }
8011
8122
  function qoderLocalDbCandidates(appDirName) {
8012
8123
  const candidates = [];
8013
8124
  const envPath = process.env.QODER_LOCAL_DB_PATH;
8014
8125
  if (envPath) {
8015
8126
  candidates.push(envPath);
8016
8127
  }
8017
- const home = os7.homedir();
8018
- let configRoot;
8019
- if (process.platform === "darwin") {
8020
- configRoot = path16.join(home, "Library", "Application Support", appDirName);
8021
- } else if (process.platform === "win32") {
8022
- configRoot = path16.join(process.env.APPDATA || path16.join(home, "AppData", "Roaming"), appDirName);
8023
- } else {
8024
- configRoot = path16.join(home, ".config", appDirName);
8025
- }
8128
+ const configRoot = appDataRoot(appDirName);
8026
8129
  candidates.push(
8027
8130
  path16.join(configRoot, "SharedClientCache", "cache", "db", "local.db"),
8028
8131
  path16.join(configRoot, "SharedClientCache", "db", "local.db")
8029
8132
  );
8030
8133
  return candidates;
8031
8134
  }
8135
+ async function loadQoderIdeModelCatalog(appDirName, home) {
8136
+ const map = {};
8137
+ try {
8138
+ const { DatabaseSync } = await import("node:sqlite");
8139
+ const db = new DatabaseSync(
8140
+ path16.join(appDataRoot(appDirName, home), "User", "globalStorage", "state.vscdb"),
8141
+ { readOnly: true }
8142
+ );
8143
+ try {
8144
+ const rows = db.prepare(
8145
+ `select key, value from ItemTable where key like 'aicoding.modelConfigs.cache.%' or key = 'aicoding.customModels'`
8146
+ ).all();
8147
+ const customIds = [];
8148
+ for (const row of rows) {
8149
+ const key = stringField(row, "key");
8150
+ const value = stringField(row, "value");
8151
+ if (!key || !value) {
8152
+ continue;
8153
+ }
8154
+ let entries;
8155
+ try {
8156
+ entries = JSON.parse(value);
8157
+ } catch {
8158
+ continue;
8159
+ }
8160
+ for (const entry of entries) {
8161
+ const displayName = stringField(entry, "displayName");
8162
+ if (key === "aicoding.customModels") {
8163
+ const id = stringField(entry, "id");
8164
+ if (id && displayName) {
8165
+ map[`custom:${id}`] = displayName;
8166
+ customIds.push(id);
8167
+ }
8168
+ } else {
8169
+ const name = stringField(entry, "name");
8170
+ if (name && displayName) {
8171
+ map[name] = displayName;
8172
+ }
8173
+ }
8174
+ }
8175
+ }
8176
+ if (customIds.length === 1) {
8177
+ const displayName = map[`custom:${customIds[0]}`];
8178
+ if (displayName) {
8179
+ map.custom_model = displayName;
8180
+ }
8181
+ }
8182
+ } finally {
8183
+ db.close();
8184
+ }
8185
+ } catch {
8186
+ }
8187
+ return map;
8188
+ }
8032
8189
  async function loadQoderDbModelCalls(appDirName, sessionId, modelMap) {
8033
8190
  const calls = { byRequestId: /* @__PURE__ */ new Map(), ordered: [] };
8034
8191
  if (!sessionId) {
@@ -8146,7 +8303,7 @@ function parseQoderCnPaths(filePath) {
8146
8303
  }
8147
8304
  return { configDir: configDir2, projectName, sessionId, mainTranscriptPath };
8148
8305
  }
8149
- function rebuildEventIdentity2(event) {
8306
+ function rebuildEventIdentity3(event) {
8150
8307
  const importKey = createImportKey([
8151
8308
  event.source,
8152
8309
  event.refs?.sourcePathHash,
@@ -8180,7 +8337,7 @@ async function parseModelNamesFromDynamicTexts(dynamicTextsPath) {
8180
8337
  return {};
8181
8338
  }
8182
8339
  }
8183
- async function loadQoderCnModelNames(configDir2) {
8340
+ async function loadQoderCnModelNames(configDir2, home) {
8184
8341
  const map = await parseModelNamesFromDynamicTexts(path17.join(configDir2, ".auth", "dynamic-texts.json"));
8185
8342
  const siblingConfigDir = configDir2.replace(/\.qoder-cn$/, ".qoder");
8186
8343
  if (siblingConfigDir !== configDir2) {
@@ -8191,6 +8348,14 @@ async function loadQoderCnModelNames(configDir2) {
8191
8348
  }
8192
8349
  }
8193
8350
  }
8351
+ for (const appDirName of ["QoderCN", "Qoder"]) {
8352
+ const ideMap = await loadQoderIdeModelCatalog(appDirName, home);
8353
+ for (const [key, val] of Object.entries(ideMap)) {
8354
+ if (!(key in map)) {
8355
+ map[key] = val;
8356
+ }
8357
+ }
8358
+ }
8194
8359
  return map;
8195
8360
  }
8196
8361
  async function loadQoderCnSegmentModelCalls(filePath, isSubagentSession, modelMap) {
@@ -8224,7 +8389,8 @@ async function loadQoderCnSegmentModelCalls(filePath, isSubagentSession, modelMa
8224
8389
  inputTokens: numberField(data, "input_tokens") || 0,
8225
8390
  outputTokens: numberField(data, "output_tokens") || 0,
8226
8391
  cacheCreationInputTokens: numberField(data, "cache_creation_input_tokens") || 0,
8227
- cacheReadInputTokens: numberField(data, "cache_read_input_tokens") || 0
8392
+ cacheReadInputTokens: numberField(data, "cache_read_input_tokens") || 0,
8393
+ stopReason: stringField(data, "stop_reason") || void 0
8228
8394
  });
8229
8395
  }
8230
8396
  }
@@ -8247,20 +8413,15 @@ async function parseQoderCnSessionFile(filePath, options) {
8247
8413
  let cwd;
8248
8414
  let project = projectContext.project;
8249
8415
  let model;
8250
- const modelMap = await loadQoderCnModelNames(configDir2);
8416
+ const home = path17.resolve(stringOption(options.home) || os8.homedir());
8417
+ const modelMap = await loadQoderCnModelNames(configDir2, home);
8251
8418
  const isSubagentSession = filePath.includes("subagents");
8252
8419
  const segmentModelCalls = await loadQoderCnSegmentModelCalls(filePath, isSubagentSession, modelMap);
8253
8420
  let modelCallIndex = 0;
8254
8421
  let dbModelCalls;
8255
8422
  const nextDbModelCall = async (requestId, blockStart) => {
8256
8423
  dbModelCalls ??= await loadQoderDbModelCalls("QoderCN", sessionId, modelMap);
8257
- if (requestId) {
8258
- return dbModelCalls.byRequestId.get(requestId)?.shift();
8259
- }
8260
- if (!blockStart) {
8261
- return void 0;
8262
- }
8263
- return dbModelCalls.ordered.shift();
8424
+ return takeQoderDbModelCall(dbModelCalls, requestId, blockStart);
8264
8425
  };
8265
8426
  const state = new SessionParserState(filePath, options, (event) => baseQoderCnEvent({ ...event, cwd, project, model }));
8266
8427
  state.sessionId = sessionId;
@@ -8446,6 +8607,20 @@ async function parseQoderCnSessionFile(filePath, options) {
8446
8607
  modelCalls: 1
8447
8608
  };
8448
8609
  model = call.model || model;
8610
+ if (!usage.tokensTotal && !usage.tokensCachedInput) {
8611
+ const dbCall = await nextDbModelCall(requestId, isBlockStart);
8612
+ if (dbCall && (dbCall.inputTokens || dbCall.outputTokens || dbCall.cachedTokens)) {
8613
+ usage = {
8614
+ tokensInput: dbCall.inputTokens || void 0,
8615
+ tokensCachedInput: dbCall.cachedTokens || void 0,
8616
+ tokensCacheReadInput: dbCall.cachedTokens || void 0,
8617
+ tokensOutput: dbCall.outputTokens || void 0,
8618
+ tokensTotal: dbCall.inputTokens + dbCall.outputTokens || void 0,
8619
+ modelCalls: 1
8620
+ };
8621
+ model = dbCall.model || model;
8622
+ }
8623
+ }
8449
8624
  } else if (shouldEmitUsage) {
8450
8625
  const dbCall = await nextDbModelCall(requestId, isBlockStart);
8451
8626
  if (dbCall) {
@@ -8567,7 +8742,7 @@ async function parseQoderCnSessionFile(filePath, options) {
8567
8742
  ...event.sessionId && event.sessionId !== parentSessionId ? { supersededSessionId: event.sessionId } : {}
8568
8743
  }
8569
8744
  };
8570
- return rebuildEventIdentity2(mapped);
8745
+ return rebuildEventIdentity3(mapped);
8571
8746
  });
8572
8747
  }
8573
8748
  }
@@ -8575,7 +8750,7 @@ async function parseQoderCnSessionFile(filePath, options) {
8575
8750
  if (dbModelCalls.rootSessionId) {
8576
8751
  const parentPath = path17.join(path17.dirname(filePath), `${dbModelCalls.rootSessionId}.jsonl`);
8577
8752
  const parentSourcePathHash = `sha256:${createStableHash(parentPath)}`;
8578
- return validEvents.map((event) => rebuildEventIdentity2({
8753
+ return validEvents.map((event) => rebuildEventIdentity3({
8579
8754
  ...event,
8580
8755
  sessionId: dbModelCalls.rootSessionId,
8581
8756
  refs: {
@@ -8896,6 +9071,7 @@ function createQoderCnAdapter() {
8896
9071
  }
8897
9072
 
8898
9073
  // src/adapters/qoder.ts
9074
+ import { existsSync } from "node:fs";
8899
9075
  import { readdir as readdir8, readFile as readFile11, stat as stat9 } from "node:fs/promises";
8900
9076
  import os9 from "node:os";
8901
9077
  import path18 from "node:path";
@@ -8924,7 +9100,7 @@ function parseQoderPaths(filePath) {
8924
9100
  }
8925
9101
  return { configDir: configDir2, projectName, sessionId, mainTranscriptPath };
8926
9102
  }
8927
- function rebuildEventIdentity3(event) {
9103
+ function rebuildEventIdentity4(event) {
8928
9104
  const importKey = createImportKey([
8929
9105
  event.source,
8930
9106
  event.refs?.sourcePathHash,
@@ -8958,7 +9134,7 @@ async function parseModelNamesFromDynamicTexts2(dynamicTextsPath) {
8958
9134
  return {};
8959
9135
  }
8960
9136
  }
8961
- async function loadQoderModelNames(configDir2) {
9137
+ async function loadQoderModelNames(configDir2, home) {
8962
9138
  const map = await parseModelNamesFromDynamicTexts2(path18.join(configDir2, ".auth", "dynamic-texts.json"));
8963
9139
  const siblingConfigDir = configDir2.replace(/\.qoder$/, ".qoder-cn");
8964
9140
  if (siblingConfigDir !== configDir2) {
@@ -8969,8 +9145,38 @@ async function loadQoderModelNames(configDir2) {
8969
9145
  }
8970
9146
  }
8971
9147
  }
9148
+ if (isQwenworkConfigRoot(configDir2)) {
9149
+ for (const dir of [path18.join(home, ".qoder"), path18.join(home, ".qoder-cn")]) {
9150
+ if (path18.resolve(dir) === path18.resolve(configDir2)) {
9151
+ continue;
9152
+ }
9153
+ const fallbackMap = await parseModelNamesFromDynamicTexts2(path18.join(dir, ".auth", "dynamic-texts.json"));
9154
+ for (const [key, val] of Object.entries(fallbackMap)) {
9155
+ if (!(key in map)) {
9156
+ map[key] = val;
9157
+ }
9158
+ }
9159
+ }
9160
+ }
9161
+ for (const appDirName of ["Qoder", "QoderCN"]) {
9162
+ const ideMap = await loadQoderIdeModelCatalog(appDirName, home);
9163
+ for (const [key, val] of Object.entries(ideMap)) {
9164
+ if (!(key in map)) {
9165
+ map[key] = val;
9166
+ }
9167
+ }
9168
+ }
8972
9169
  return map;
8973
9170
  }
9171
+ function isQwenworkConfigRoot(configDir2) {
9172
+ const name = path18.basename(path18.resolve(configDir2));
9173
+ if (name === ".qwenworkcn" || name === ".qwenwork") {
9174
+ return true;
9175
+ }
9176
+ const override = process.env.QWENWORK_CONFIG_DIR;
9177
+ return Boolean(override && override.trim() && path18.basename(path18.resolve(override)) === name);
9178
+ }
9179
+ var FAILED_SEGMENT_STOP_REASONS = /* @__PURE__ */ new Set(["cancelled", "canceled", "error", "failed", "refusal"]);
8974
9180
  async function loadQoderSegmentModelCalls(filePath, isSubagentSession, modelMap) {
8975
9181
  const { configDir: configDir2, projectName, sessionId } = parseQoderPaths(filePath);
8976
9182
  const segmentsPath = path18.join(configDir2, "logs", "sessions", projectName, sessionId, "segments");
@@ -9002,7 +9208,8 @@ async function loadQoderSegmentModelCalls(filePath, isSubagentSession, modelMap)
9002
9208
  inputTokens: numberField(data, "input_tokens") || 0,
9003
9209
  outputTokens: numberField(data, "output_tokens") || 0,
9004
9210
  cacheCreationInputTokens: numberField(data, "cache_creation_input_tokens") || 0,
9005
- cacheReadInputTokens: numberField(data, "cache_read_input_tokens") || 0
9211
+ cacheReadInputTokens: numberField(data, "cache_read_input_tokens") || 0,
9212
+ stopReason: stringField(data, "stop_reason") || void 0
9006
9213
  });
9007
9214
  }
9008
9215
  }
@@ -9025,20 +9232,16 @@ async function parseQoderSessionFile(filePath, options) {
9025
9232
  let cwd;
9026
9233
  let project = projectContext.project;
9027
9234
  let model;
9028
- const modelMap = await loadQoderModelNames(configDir2);
9235
+ const home = path18.resolve(stringOption(options.home) || os9.homedir());
9236
+ const modelMap = await loadQoderModelNames(configDir2, home);
9237
+ const qwenworkRoot = isQwenworkConfigRoot(configDir2);
9029
9238
  const isSubagentSession = filePath.includes("subagents");
9030
9239
  const segmentModelCalls = await loadQoderSegmentModelCalls(filePath, isSubagentSession, modelMap);
9031
9240
  let modelCallIndex = 0;
9032
9241
  let dbModelCalls;
9033
9242
  const nextDbModelCall = async (requestId, blockStart) => {
9034
9243
  dbModelCalls ??= await loadQoderDbModelCalls("Qoder", sessionId, modelMap);
9035
- if (requestId) {
9036
- return dbModelCalls.byRequestId.get(requestId)?.shift();
9037
- }
9038
- if (!blockStart) {
9039
- return void 0;
9040
- }
9041
- return dbModelCalls.ordered.shift();
9244
+ return takeQoderDbModelCall(dbModelCalls, requestId, blockStart);
9042
9245
  };
9043
9246
  const state = new SessionParserState(filePath, options, (event) => baseQoderEvent({ ...event, cwd, project, model }));
9044
9247
  state.sessionId = sessionId;
@@ -9206,39 +9409,28 @@ async function parseQoderSessionFile(filePath, options) {
9206
9409
  seenUsageKeys.add(usageKey);
9207
9410
  }
9208
9411
  let usage;
9412
+ let keepZeroTokenUsage = false;
9209
9413
  if (shouldEmitUsage && modelCallIndex < segmentModelCalls.length) {
9210
9414
  const call = segmentModelCalls[modelCallIndex++];
9211
- const inputTokens = call.inputTokens;
9212
- const cacheCreationInputTokens = call.cacheCreationInputTokens;
9213
- const cacheReadInputTokens = call.cacheReadInputTokens;
9214
- const outputTokens = call.outputTokens;
9215
- const cachedInputTokens = cacheCreationInputTokens + cacheReadInputTokens;
9216
- const totalInputTokens = inputTokens;
9217
- usage = {
9218
- tokensInput: totalInputTokens || void 0,
9219
- tokensCachedInput: cachedInputTokens || void 0,
9220
- tokensCacheCreationInput: cacheCreationInputTokens || void 0,
9221
- tokensCacheReadInput: cacheReadInputTokens || void 0,
9222
- tokensOutput: outputTokens || void 0,
9223
- tokensTotal: totalInputTokens + outputTokens || void 0,
9224
- modelCalls: 1
9225
- };
9415
+ usage = qoderSegmentUsage(call);
9226
9416
  model = call.model || model;
9417
+ if (!usage.tokensTotal && !usage.tokensCachedInput) {
9418
+ const dbCall = await nextDbModelCall(requestId, isBlockStart);
9419
+ if (dbCall && (dbCall.inputTokens || dbCall.outputTokens || dbCall.cachedTokens)) {
9420
+ usage = qoderDbUsage(dbCall);
9421
+ model = dbCall.model || model;
9422
+ } else if (qwenworkRoot && (!call.stopReason || !FAILED_SEGMENT_STOP_REASONS.has(call.stopReason))) {
9423
+ keepZeroTokenUsage = true;
9424
+ }
9425
+ }
9227
9426
  } else if (shouldEmitUsage) {
9228
9427
  const dbCall = await nextDbModelCall(requestId, isBlockStart);
9229
9428
  if (dbCall) {
9230
- usage = {
9231
- tokensInput: dbCall.inputTokens || void 0,
9232
- tokensCachedInput: dbCall.cachedTokens || void 0,
9233
- tokensCacheReadInput: dbCall.cachedTokens || void 0,
9234
- tokensOutput: dbCall.outputTokens || void 0,
9235
- tokensTotal: dbCall.inputTokens + dbCall.outputTokens || void 0,
9236
- modelCalls: 1
9237
- };
9429
+ usage = qoderDbUsage(dbCall);
9238
9430
  model = dbCall.model || model;
9239
9431
  }
9240
9432
  }
9241
- if (usage && (usage.tokensTotal || usage.tokensCachedInput)) {
9433
+ if (usage && (usage.tokensTotal || usage.tokensCachedInput || keepZeroTokenUsage)) {
9242
9434
  const speed = stringField(objectField(message, "usage"), "speed");
9243
9435
  const usageModel = speed === "fast" && model ? `${model}-fast` : model;
9244
9436
  push(baseQoderEvent({
@@ -9345,7 +9537,7 @@ async function parseQoderSessionFile(filePath, options) {
9345
9537
  ...event.sessionId && event.sessionId !== parentSessionId ? { supersededSessionId: event.sessionId } : {}
9346
9538
  }
9347
9539
  };
9348
- return rebuildEventIdentity3(mapped);
9540
+ return rebuildEventIdentity4(mapped);
9349
9541
  });
9350
9542
  }
9351
9543
  }
@@ -9353,7 +9545,7 @@ async function parseQoderSessionFile(filePath, options) {
9353
9545
  if (dbModelCalls.rootSessionId) {
9354
9546
  const parentPath = path18.join(path18.dirname(filePath), `${dbModelCalls.rootSessionId}.jsonl`);
9355
9547
  const parentSourcePathHash = `sha256:${createStableHash(parentPath)}`;
9356
- return validEvents.map((event) => rebuildEventIdentity3({
9548
+ return validEvents.map((event) => rebuildEventIdentity4({
9357
9549
  ...event,
9358
9550
  sessionId: dbModelCalls.rootSessionId,
9359
9551
  refs: {
@@ -9365,6 +9557,28 @@ async function parseQoderSessionFile(filePath, options) {
9365
9557
  }
9366
9558
  return validEvents;
9367
9559
  }
9560
+ function qoderSegmentUsage(call) {
9561
+ const cachedInputTokens = call.cacheCreationInputTokens + call.cacheReadInputTokens;
9562
+ return {
9563
+ tokensInput: call.inputTokens || void 0,
9564
+ tokensCachedInput: cachedInputTokens || void 0,
9565
+ tokensCacheCreationInput: call.cacheCreationInputTokens || void 0,
9566
+ tokensCacheReadInput: call.cacheReadInputTokens || void 0,
9567
+ tokensOutput: call.outputTokens || void 0,
9568
+ tokensTotal: call.inputTokens + call.outputTokens || void 0,
9569
+ modelCalls: 1
9570
+ };
9571
+ }
9572
+ function qoderDbUsage(dbCall) {
9573
+ return {
9574
+ tokensInput: dbCall.inputTokens || void 0,
9575
+ tokensCachedInput: dbCall.cachedTokens || void 0,
9576
+ tokensCacheReadInput: dbCall.cachedTokens || void 0,
9577
+ tokensOutput: dbCall.outputTokens || void 0,
9578
+ tokensTotal: dbCall.inputTokens + dbCall.outputTokens || void 0,
9579
+ modelCalls: 1
9580
+ };
9581
+ }
9368
9582
  function baseQoderEvent(event) {
9369
9583
  return {
9370
9584
  schemaVersion: AGENT_TIME_SCHEMA_VERSION,
@@ -9448,12 +9662,20 @@ async function qoderProjectContextFromLines(filePath, lines, options, configDir2
9448
9662
  const isSubagent = filePath.includes(`${path18.sep}subagents${path18.sep}`);
9449
9663
  const inherited = isSubagent ? await readPersistedSessionContextFromOptions(options, sessionId) : void 0;
9450
9664
  let cwds = [];
9665
+ const workspaceDirs = [];
9451
9666
  for (const line of lines) {
9452
9667
  const raw = parseJsonLine(line);
9453
9668
  const cwd = raw ? stringField(raw, "cwd") : void 0;
9454
9669
  if (cwd && path18.isAbsolute(cwd)) {
9455
9670
  cwds.push(cwd);
9456
9671
  }
9672
+ if (raw && stringField(raw, "type") === "workspace-directories") {
9673
+ for (const dir of arrayField5(raw, "directories")) {
9674
+ if (typeof dir === "string" && path18.isAbsolute(dir)) {
9675
+ workspaceDirs.push(dir);
9676
+ }
9677
+ }
9678
+ }
9457
9679
  }
9458
9680
  if (isSubagent) {
9459
9681
  if (inherited?.cwd && path18.isAbsolute(inherited.cwd)) {
@@ -9469,6 +9691,13 @@ async function qoderProjectContextFromLines(filePath, lines, options, configDir2
9469
9691
  if (cwd && path18.isAbsolute(cwd)) {
9470
9692
  parentCwds.push(cwd);
9471
9693
  }
9694
+ if (workspaceDirs.length === 0 && raw && stringField(raw, "type") === "workspace-directories") {
9695
+ for (const dir of arrayField5(raw, "directories")) {
9696
+ if (typeof dir === "string" && path18.isAbsolute(dir)) {
9697
+ workspaceDirs.push(dir);
9698
+ }
9699
+ }
9700
+ }
9472
9701
  }
9473
9702
  if (parentCwds.length > 0) {
9474
9703
  cwds = parentCwds;
@@ -9477,6 +9706,12 @@ async function qoderProjectContextFromLines(filePath, lines, options, configDir2
9477
9706
  }
9478
9707
  }
9479
9708
  }
9709
+ if (cwds.length > 0 && cwds.every((cwd) => pathInsideDir(cwd, configDir2))) {
9710
+ const external = workspaceDirs.filter((dir) => !pathInsideDir(dir, configDir2));
9711
+ if (external.length > 0) {
9712
+ cwds = external;
9713
+ }
9714
+ }
9480
9715
  const root = await gitRootFromCwds3(cwds) || qoderProjectRootFromCwds(projectDir, cwds);
9481
9716
  const project = inherited?.project || (cwds.length > 0 ? path18.basename(cwds[0]) : root ? path18.basename(root) : await qoderProjectFromFilePath(filePath, options));
9482
9717
  return {
@@ -9484,6 +9719,11 @@ async function qoderProjectContextFromLines(filePath, lines, options, configDir2
9484
9719
  workspaceId: createWorkspaceId({ projectName: project, repoRoot: root })
9485
9720
  };
9486
9721
  }
9722
+ function pathInsideDir(candidate, dir) {
9723
+ const resolvedDir = path18.resolve(dir);
9724
+ const resolved = path18.resolve(candidate);
9725
+ return resolved === resolvedDir || resolved.startsWith(`${resolvedDir}${path18.sep}`);
9726
+ }
9487
9727
  async function gitRootFromCwds3(cwds) {
9488
9728
  const seen = /* @__PURE__ */ new Set();
9489
9729
  for (const cwd of cwds) {
@@ -9508,7 +9748,7 @@ function qoderProjectRootFromCwds(projectDir, cwds) {
9508
9748
  for (const cwd of cwds) {
9509
9749
  let current = path18.resolve(cwd);
9510
9750
  while (true) {
9511
- if (encodeQoderProjectPath(current) === projectDir) {
9751
+ if (qoderEncodedVariants(current).includes(projectDir)) {
9512
9752
  return current;
9513
9753
  }
9514
9754
  const parent = path18.dirname(current);
@@ -9520,21 +9760,23 @@ function qoderProjectRootFromCwds(projectDir, cwds) {
9520
9760
  }
9521
9761
  return void 0;
9522
9762
  }
9523
- function encodeQoderProjectPath(value) {
9524
- return path18.resolve(value).split(path18.sep).join("-").replaceAll("_", "-");
9525
- }
9526
9763
  function rawQoderProjectPath(value) {
9527
9764
  return path18.resolve(value).split(path18.sep).join("-");
9528
9765
  }
9529
9766
  function qoderEncodedVariants(value) {
9530
9767
  const raw = rawQoderProjectPath(value);
9531
- const normalized = raw.replaceAll("_", "-");
9532
- return raw === normalized ? [raw] : [raw, normalized];
9768
+ const variants = /* @__PURE__ */ new Set([
9769
+ raw,
9770
+ raw.replaceAll("_", "-"),
9771
+ raw.replaceAll(".", "-"),
9772
+ raw.replaceAll("_", "-").replaceAll(".", "-")
9773
+ ]);
9774
+ return [...variants];
9533
9775
  }
9534
9776
  function qoderEncodedProjectSuffix(projectDir, home) {
9535
9777
  for (const prefix of qoderEncodedVariants(home).map((value) => `${value}-`)) {
9536
9778
  if (projectDir.startsWith(prefix)) {
9537
- return projectDir.slice(prefix.length) || void 0;
9779
+ return projectDir.slice(prefix.length).replace(/^-+/, "") || void 0;
9538
9780
  }
9539
9781
  }
9540
9782
  return void 0;
@@ -9619,6 +9861,16 @@ function qoderConfigDir(home, env) {
9619
9861
  }
9620
9862
  return path18.join(home, ".qoder");
9621
9863
  }
9864
+ function qwenworkConfigDir(home, env) {
9865
+ const override = env?.QWENWORK_CONFIG_DIR;
9866
+ if (override && override.trim()) {
9867
+ return path18.resolve(override);
9868
+ }
9869
+ return path18.join(home, ".qwenworkcn");
9870
+ }
9871
+ function qoderConfigDirs(home, env) {
9872
+ return [...new Set([qoderConfigDir(home, env), qwenworkConfigDir(home, env)].map((dir) => path18.resolve(dir)))];
9873
+ }
9622
9874
  function createQoderAdapter() {
9623
9875
  return {
9624
9876
  id: "qoder",
@@ -9638,19 +9890,22 @@ function createQoderAdapter() {
9638
9890
  );
9639
9891
  },
9640
9892
  installEntries(home, env) {
9641
- return [{
9893
+ const [primary, ...variants] = qoderConfigDirs(home, env);
9894
+ const targets = [primary, ...variants.filter((dir) => existsSync(dir))];
9895
+ return targets.map((base) => ({
9642
9896
  kind: "hooks-json",
9643
- path: path18.join(qoderConfigDir(home, env), "settings.json"),
9897
+ path: path18.join(base, "settings.json"),
9644
9898
  content: hookConfig7()
9645
- }];
9899
+ }));
9646
9900
  },
9647
9901
  sourcePaths(home, env) {
9902
+ const paths = qoderConfigDirs(home, env).map((base2) => path18.join(base2, "projects"));
9648
9903
  const base = qoderConfigDir(home, env);
9649
- return [
9650
- path18.join(base, "projects"),
9904
+ paths.push(
9651
9905
  path18.join(base, ".qoder.json"),
9652
9906
  path18.join(home, ".qoder.json")
9653
- ];
9907
+ );
9908
+ return paths;
9654
9909
  },
9655
9910
  parseSessionFile: parseQoderSessionFile
9656
9911
  };
@@ -11085,13 +11340,7 @@ function createZedAdapter() {
11085
11340
  }
11086
11341
 
11087
11342
  // src/lib/pricing.ts
11088
- function estimateEventCostUsd(event) {
11089
- if (event.type !== "model.usage") {
11090
- return 0;
11091
- }
11092
- if (typeof event.metrics?.costUsd === "number" && event.metrics.costUsd > 0) {
11093
- return event.metrics.costUsd;
11094
- }
11343
+ function estimateEventCostUsd(_event) {
11095
11344
  return 0;
11096
11345
  }
11097
11346
 
@@ -11554,7 +11803,7 @@ function hookCommandFromGroup(group) {
11554
11803
 
11555
11804
  // src/lib/config.ts
11556
11805
  import { randomUUID } from "node:crypto";
11557
- import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
11806
+ import { existsSync as existsSync2, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
11558
11807
  import { homedir, hostname } from "node:os";
11559
11808
  import path22 from "node:path";
11560
11809
  function configDir(home = homedir()) {
@@ -11568,7 +11817,7 @@ function machineIdPath(home = homedir()) {
11568
11817
  }
11569
11818
  function readConfig(home = homedir()) {
11570
11819
  const file = configPath(home);
11571
- if (!existsSync(file)) {
11820
+ if (!existsSync2(file)) {
11572
11821
  return {};
11573
11822
  }
11574
11823
  try {
@@ -11580,7 +11829,7 @@ function readConfig(home = homedir()) {
11580
11829
  }
11581
11830
  function writeConfig(config, home = homedir()) {
11582
11831
  const dir = configDir(home);
11583
- if (!existsSync(dir)) {
11832
+ if (!existsSync2(dir)) {
11584
11833
  mkdirSync(dir, { recursive: true });
11585
11834
  }
11586
11835
  writeFileSync(configPath(home), `${JSON.stringify(config, null, 2)}
@@ -11588,7 +11837,7 @@ function writeConfig(config, home = homedir()) {
11588
11837
  }
11589
11838
  function ensureLocalMachineId(home = homedir()) {
11590
11839
  const file = machineIdPath(home);
11591
- if (existsSync(file)) {
11840
+ if (existsSync2(file)) {
11592
11841
  const value = readFileSync(file, "utf8").trim();
11593
11842
  if (value.length > 0) {
11594
11843
  return value;
@@ -11596,7 +11845,7 @@ function ensureLocalMachineId(home = homedir()) {
11596
11845
  }
11597
11846
  const id = randomUUID();
11598
11847
  const dir = configDir(home);
11599
- if (!existsSync(dir)) {
11848
+ if (!existsSync2(dir)) {
11600
11849
  mkdirSync(dir, { recursive: true });
11601
11850
  }
11602
11851
  writeFileSync(file, `${id}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@yhong91/vibetime",
3
3
  "type": "module",
4
- "version": "0.1.48",
4
+ "version": "0.1.50",
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": {