@yhong91/vibetime 0.1.28 → 0.1.30

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 +131 -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.28" : "0.1.1";
1931
+ var PACKAGE_VERSION = true ? "0.1.30" : "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;
@@ -4069,6 +4069,19 @@ function prefixCoworkId(value) {
4069
4069
  }
4070
4070
  return value.startsWith(COWORK_PREFIX) ? value : `${COWORK_PREFIX}${value}`;
4071
4071
  }
4072
+ function parseCoworkSubagentPath(filePath) {
4073
+ const parts = filePath.split(path8.sep);
4074
+ const subagentsIdx = parts.lastIndexOf("subagents");
4075
+ if (subagentsIdx < 2) {
4076
+ return void 0;
4077
+ }
4078
+ const cliSessionId = parts[subagentsIdx - 1];
4079
+ if (!cliSessionId) {
4080
+ return void 0;
4081
+ }
4082
+ const mainTranscriptPath = [...parts.slice(0, subagentsIdx - 1), `${cliSessionId}.jsonl`].join(path8.sep);
4083
+ return { cliSessionId, mainTranscriptPath };
4084
+ }
4072
4085
  function rebuildEventIdentity(event) {
4073
4086
  const importKey = createImportKey([
4074
4087
  event.source,
@@ -4094,15 +4107,21 @@ async function parseClaudeCoworkSessionFile(filePath, options) {
4094
4107
  if (!parser) {
4095
4108
  return [];
4096
4109
  }
4110
+ const subagentInfo = parseCoworkSubagentPath(filePath);
4111
+ const parentSourcePathHash = subagentInfo ? `sha256:${createStableHash(subagentInfo.mainTranscriptPath)}` : void 0;
4112
+ const parentSessionId = subagentInfo ? prefixCoworkId(subagentInfo.cliSessionId) : void 0;
4097
4113
  const events = await parser(filePath, options);
4098
4114
  return events.map((event) => {
4099
- const sessionId = prefixCoworkId(event.sessionId);
4115
+ const sessionId = parentSessionId || prefixCoworkId(event.sessionId);
4100
4116
  const turnId = prefixCoworkId(event.turnId);
4101
4117
  const workspaceId = createWorkspaceId({ projectName: project, repoRoot: cwd });
4102
4118
  const refs2 = { ...event.refs };
4103
4119
  if (metadata.title) {
4104
4120
  refs2.coworkTitleHash = `sha256:${createStableHash(metadata.title)}`;
4105
4121
  }
4122
+ if (parentSourcePathHash) {
4123
+ refs2.sourcePathHash = parentSourcePathHash;
4124
+ }
4106
4125
  const mapped = {
4107
4126
  ...event,
4108
4127
  schemaVersion: event.schemaVersion || AGENT_TIME_SCHEMA_VERSION,
@@ -6348,6 +6367,7 @@ async function parseOpenCodeSessionFile(dbPath, options) {
6348
6367
  const hasDirectory = sessionCols.has("directory");
6349
6368
  const hasPath = sessionCols.has("path");
6350
6369
  const hasArchived = sessionCols.has("time_archived");
6370
+ const hasParentId = sessionCols.has("parent_id");
6351
6371
  const selectCols = ["id", "title", "time_created"];
6352
6372
  if (hasDirectory) {
6353
6373
  selectCols.push("directory");
@@ -6358,11 +6378,36 @@ async function parseOpenCodeSessionFile(dbPath, options) {
6358
6378
  if (hasArchived) {
6359
6379
  selectCols.push("time_archived");
6360
6380
  }
6381
+ if (hasParentId) {
6382
+ selectCols.push("parent_id");
6383
+ }
6361
6384
  const sessions = db.prepare(
6362
6385
  `SELECT ${selectCols.join(", ")} FROM session WHERE time_created IS NOT NULL ORDER BY time_created`
6363
6386
  ).all();
6387
+ const rootIdByRawId = /* @__PURE__ */ new Map();
6388
+ if (hasParentId) {
6389
+ const byId = new Map(sessions.map((s) => [s.id, s]));
6390
+ for (const session of sessions) {
6391
+ let current = session.id;
6392
+ const visited = /* @__PURE__ */ new Set();
6393
+ while (true) {
6394
+ if (visited.has(current)) {
6395
+ break;
6396
+ }
6397
+ visited.add(current);
6398
+ const node = byId.get(current);
6399
+ const parentId = node?.parent_id || void 0;
6400
+ if (!parentId || !byId.has(parentId)) {
6401
+ break;
6402
+ }
6403
+ current = parentId;
6404
+ }
6405
+ rootIdByRawId.set(session.id, current);
6406
+ }
6407
+ }
6364
6408
  for (const session of sessions) {
6365
- const sessionId = session.id;
6409
+ const rawSessionId = session.id;
6410
+ const sessionId = rootIdByRawId.get(rawSessionId) || rawSessionId;
6366
6411
  const cwd = session.directory || session.path || void 0;
6367
6412
  const project = cwd ? path14.basename(cwd) : void 0;
6368
6413
  const sessionTs = msToIso(session.time_created);
@@ -6376,14 +6421,14 @@ async function parseOpenCodeSessionFile(dbPath, options) {
6376
6421
  }));
6377
6422
  const messages = db.prepare(
6378
6423
  "SELECT id, data FROM message WHERE session_id = ? ORDER BY time_created"
6379
- ).all(sessionId);
6424
+ ).all(rawSessionId);
6380
6425
  let currentTurnId;
6381
6426
  let currentProvider;
6382
6427
  let turnTs;
6383
6428
  let reasoningEffort;
6384
6429
  const modelSwitchedEvents = db.prepare(
6385
6430
  "SELECT data FROM event WHERE aggregate_id = ? AND type = 'session.next.model.switched.1' ORDER BY seq"
6386
- ).all(sessionId);
6431
+ ).all(rawSessionId);
6387
6432
  for (const evt of modelSwitchedEvents) {
6388
6433
  try {
6389
6434
  const evtData = JSON.parse(evt.data);
@@ -7345,11 +7390,13 @@ function parseQoderCnPaths(filePath) {
7345
7390
  let sessionId = "";
7346
7391
  let projectName = "";
7347
7392
  let configDir2 = "";
7393
+ let mainTranscriptPath;
7348
7394
  if (subagentsIdx > 2) {
7349
7395
  sessionId = parts[subagentsIdx - 1];
7350
7396
  projectName = parts[subagentsIdx - 2];
7351
7397
  const projectsIdx = parts.lastIndexOf("projects");
7352
7398
  configDir2 = parts.slice(0, projectsIdx).join(path16.sep);
7399
+ mainTranscriptPath = [...parts.slice(0, subagentsIdx - 1), `${sessionId}.jsonl`].join(path16.sep);
7353
7400
  } else {
7354
7401
  const filename = parts.at(-1) || "";
7355
7402
  sessionId = path16.basename(filename, ".jsonl");
@@ -7357,7 +7404,24 @@ function parseQoderCnPaths(filePath) {
7357
7404
  const projectsIdx = parts.lastIndexOf("projects");
7358
7405
  configDir2 = parts.slice(0, projectsIdx).join(path16.sep);
7359
7406
  }
7360
- return { configDir: configDir2, projectName, sessionId };
7407
+ return { configDir: configDir2, projectName, sessionId, mainTranscriptPath };
7408
+ }
7409
+ function rebuildEventIdentity2(event) {
7410
+ const importKey = createImportKey([
7411
+ event.source,
7412
+ event.refs?.sourcePathHash,
7413
+ event.refs?.sourceLine,
7414
+ event.type,
7415
+ event.refs?.sourceId
7416
+ ]);
7417
+ return {
7418
+ ...event,
7419
+ id: createStableEventId(importKey),
7420
+ refs: {
7421
+ ...event.refs,
7422
+ importKey
7423
+ }
7424
+ };
7361
7425
  }
7362
7426
  async function loadQoderCnModelNames(configDir2) {
7363
7427
  try {
@@ -7707,9 +7771,17 @@ async function parseQoderCnSessionFile(filePath, options) {
7707
7771
  }
7708
7772
  const validEvents = state.events.filter((event) => validateCanonicalEvent(event).valid);
7709
7773
  if (isSubagentSession) {
7710
- const parentSessionId = parseQoderCnPaths(filePath).sessionId;
7711
- if (parentSessionId) {
7712
- return validEvents.map((event) => ({ ...event, sessionId: parentSessionId }));
7774
+ const { sessionId: parentSessionId, mainTranscriptPath } = parseQoderCnPaths(filePath);
7775
+ if (parentSessionId && mainTranscriptPath) {
7776
+ const parentSourcePathHash = `sha256:${createStableHash(mainTranscriptPath)}`;
7777
+ return validEvents.map((event) => {
7778
+ const mapped = {
7779
+ ...event,
7780
+ sessionId: parentSessionId,
7781
+ refs: { ...event.refs, sourcePathHash: parentSourcePathHash }
7782
+ };
7783
+ return rebuildEventIdentity2(mapped);
7784
+ });
7713
7785
  }
7714
7786
  }
7715
7787
  return validEvents;
@@ -8088,11 +8160,13 @@ function parseQoderPaths(filePath) {
8088
8160
  let sessionId = "";
8089
8161
  let projectName = "";
8090
8162
  let configDir2 = "";
8163
+ let mainTranscriptPath;
8091
8164
  if (subagentsIdx > 2) {
8092
8165
  sessionId = parts[subagentsIdx - 1];
8093
8166
  projectName = parts[subagentsIdx - 2];
8094
8167
  const projectsIdx = parts.lastIndexOf("projects");
8095
8168
  configDir2 = parts.slice(0, projectsIdx).join(path17.sep);
8169
+ mainTranscriptPath = [...parts.slice(0, subagentsIdx - 1), `${sessionId}.jsonl`].join(path17.sep);
8096
8170
  } else {
8097
8171
  const filename = parts.at(-1) || "";
8098
8172
  sessionId = path17.basename(filename, ".jsonl");
@@ -8100,7 +8174,24 @@ function parseQoderPaths(filePath) {
8100
8174
  const projectsIdx = parts.lastIndexOf("projects");
8101
8175
  configDir2 = parts.slice(0, projectsIdx).join(path17.sep);
8102
8176
  }
8103
- return { configDir: configDir2, projectName, sessionId };
8177
+ return { configDir: configDir2, projectName, sessionId, mainTranscriptPath };
8178
+ }
8179
+ function rebuildEventIdentity3(event) {
8180
+ const importKey = createImportKey([
8181
+ event.source,
8182
+ event.refs?.sourcePathHash,
8183
+ event.refs?.sourceLine,
8184
+ event.type,
8185
+ event.refs?.sourceId
8186
+ ]);
8187
+ return {
8188
+ ...event,
8189
+ id: createStableEventId(importKey),
8190
+ refs: {
8191
+ ...event.refs,
8192
+ importKey
8193
+ }
8194
+ };
8104
8195
  }
8105
8196
  async function loadQoderModelNames(configDir2) {
8106
8197
  try {
@@ -8450,9 +8541,17 @@ async function parseQoderSessionFile(filePath, options) {
8450
8541
  }
8451
8542
  const validEvents = state.events.filter((event) => validateCanonicalEvent(event).valid);
8452
8543
  if (isSubagentSession) {
8453
- const parentSessionId = parseQoderPaths(filePath).sessionId;
8454
- if (parentSessionId) {
8455
- return validEvents.map((event) => ({ ...event, sessionId: parentSessionId }));
8544
+ const { sessionId: parentSessionId, mainTranscriptPath } = parseQoderPaths(filePath);
8545
+ if (parentSessionId && mainTranscriptPath) {
8546
+ const parentSourcePathHash = `sha256:${createStableHash(mainTranscriptPath)}`;
8547
+ return validEvents.map((event) => {
8548
+ const mapped = {
8549
+ ...event,
8550
+ sessionId: parentSessionId,
8551
+ refs: { ...event.refs, sourcePathHash: parentSourcePathHash }
8552
+ };
8553
+ return rebuildEventIdentity3(mapped);
8554
+ });
8456
8555
  }
8457
8556
  }
8458
8557
  return validEvents;
@@ -9359,14 +9458,32 @@ function modelMetrics(row) {
9359
9458
  toolCalls: numberField(row, "tool_call_count")
9360
9459
  };
9361
9460
  }
9461
+ function resolveRootSessionId(sessionId, sessions) {
9462
+ let current = sessionId;
9463
+ const visited = /* @__PURE__ */ new Set();
9464
+ while (true) {
9465
+ if (visited.has(current)) {
9466
+ break;
9467
+ }
9468
+ visited.add(current);
9469
+ const session = sessions.get(current);
9470
+ const parentId = stringField(session, "parent_id");
9471
+ if (!parentId || !sessions.has(parentId)) {
9472
+ break;
9473
+ }
9474
+ current = parentId;
9475
+ }
9476
+ return current;
9477
+ }
9362
9478
  function sessionContext(row, sessions) {
9363
9479
  const sessionId = stringField(row, "session_id") || stringField(row, "id") || "unknown";
9364
9480
  const session = sessions.get(sessionId);
9365
9481
  const cwd = stringField(session, "directory");
9366
9482
  const project = projectFromDirectory(cwd);
9483
+ const rootSessionId = resolveRootSessionId(sessionId, sessions);
9367
9484
  return {
9368
9485
  rawSessionId: sessionId,
9369
- sessionId: `zcode:${sessionId}`,
9486
+ sessionId: `zcode:${rootSessionId}`,
9370
9487
  cwd,
9371
9488
  project,
9372
9489
  workspaceId: createWorkspaceId({ projectName: project, repoRoot: cwd }),
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@yhong91/vibetime",
3
3
  "type": "module",
4
- "version": "0.1.28",
4
+ "version": "0.1.30",
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": {