@yhong91/vibetime 0.1.43 → 0.1.45

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 +361 -236
  2. package/package.json +1 -1
package/bin/vibetime.mjs CHANGED
@@ -1719,6 +1719,69 @@ import { promisify } from "node:util";
1719
1719
  // src/lib/activity.ts
1720
1720
  import path2 from "node:path";
1721
1721
 
1722
+ // src/lib/fields.ts
1723
+ function stringField(object, key) {
1724
+ if (!isPlainObject(object)) {
1725
+ return void 0;
1726
+ }
1727
+ const value = object[key];
1728
+ return typeof value === "string" ? value : void 0;
1729
+ }
1730
+ function numberField(object, key) {
1731
+ const value = object[key];
1732
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
1733
+ }
1734
+ function objectField(object, key) {
1735
+ if (!isPlainObject(object) || !isPlainObject(object[key])) {
1736
+ return {};
1737
+ }
1738
+ return object[key];
1739
+ }
1740
+ function arrayField(object, key) {
1741
+ if (!isPlainObject(object) || !Array.isArray(object[key])) {
1742
+ return [];
1743
+ }
1744
+ return object[key];
1745
+ }
1746
+ function isPlainObject(value) {
1747
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1748
+ }
1749
+ function stringRefs(value) {
1750
+ const refs2 = {};
1751
+ for (const [key, item] of Object.entries(value || {})) {
1752
+ if (typeof item === "string" && item.length > 0) {
1753
+ refs2[key] = item;
1754
+ }
1755
+ }
1756
+ return refs2;
1757
+ }
1758
+ function stringOption(value) {
1759
+ if (Array.isArray(value)) {
1760
+ return value.at(-1);
1761
+ }
1762
+ return typeof value === "string" ? value : void 0;
1763
+ }
1764
+ function valuesOption(value) {
1765
+ if (Array.isArray(value)) {
1766
+ return value;
1767
+ }
1768
+ return typeof value === "string" ? [value] : [];
1769
+ }
1770
+ function numberOption(value) {
1771
+ if (typeof value === "number" && Number.isFinite(value)) {
1772
+ return value;
1773
+ }
1774
+ if (Array.isArray(value)) {
1775
+ return numberOption(value.at(-1));
1776
+ }
1777
+ const text = stringOption(value);
1778
+ if (!text) {
1779
+ return void 0;
1780
+ }
1781
+ const parsed = Number(text);
1782
+ return Number.isFinite(parsed) ? parsed : void 0;
1783
+ }
1784
+
1722
1785
  // src/lib/shell.ts
1723
1786
  import path from "node:path";
1724
1787
  function fileActivitiesFromShellCommand(command, ts, rootCwd, initialCwd) {
@@ -1845,7 +1908,7 @@ function operationForTool(tool) {
1845
1908
  if (["write"].includes(normalized)) {
1846
1909
  return "write";
1847
1910
  }
1848
- if (["edit", "multiedit", "notebookedit", "apply_patch", "applypatch"].includes(normalized)) {
1911
+ if (["edit", "multiedit", "notebookedit", "apply_patch", "applypatch", "searchreplace"].includes(normalized)) {
1849
1912
  return "edit";
1850
1913
  }
1851
1914
  return "read";
@@ -1926,78 +1989,71 @@ function countTextLines(text) {
1926
1989
  }
1927
1990
  return text.split(/\r\n|\r|\n/).length;
1928
1991
  }
1929
-
1930
- // src/lib/constants.ts
1931
- var PACKAGE_VERSION = true ? "0.1.43" : "0.1.1";
1932
- var DEFAULT_API_URL = "http://121.196.224.82:3001";
1933
- var DEFAULT_BACKFILL_BATCH_SIZE = 50;
1934
- var DEFAULT_BACKFILL_BATCH_BYTES = 800 * 1024;
1935
- var ROLLUP_BUCKET_MS = 15 * 60 * 1e3;
1936
- var DEFAULT_HOOK_SYNC_MIN_INTERVAL_SECONDS = 60;
1937
-
1938
- // src/lib/fields.ts
1939
- function stringField(object, key) {
1940
- if (!isPlainObject(object)) {
1941
- return void 0;
1942
- }
1943
- const value = object[key];
1944
- return typeof value === "string" ? value : void 0;
1945
- }
1946
- function numberField(object, key) {
1947
- const value = object[key];
1948
- return typeof value === "number" && Number.isFinite(value) ? value : void 0;
1949
- }
1950
- function objectField(object, key) {
1951
- if (!isPlainObject(object) || !isPlainObject(object[key])) {
1952
- return {};
1953
- }
1954
- return object[key];
1955
- }
1956
- function arrayField(object, key) {
1957
- if (!isPlainObject(object) || !Array.isArray(object[key])) {
1958
- return [];
1959
- }
1960
- return object[key];
1961
- }
1962
- function isPlainObject(value) {
1963
- return value !== null && typeof value === "object" && !Array.isArray(value);
1964
- }
1965
- function stringRefs(value) {
1966
- const refs2 = {};
1967
- for (const [key, item] of Object.entries(value || {})) {
1968
- if (typeof item === "string" && item.length > 0) {
1969
- refs2[key] = item;
1992
+ function claudeStyleToolFileActivities(tool, input, ts, cwd) {
1993
+ const changes = /* @__PURE__ */ new Map();
1994
+ const operation = operationForTool(tool);
1995
+ const workdir = stringField(input, "cwd") || cwd;
1996
+ for (const key of ["file_path", "path", "notebook_path"]) {
1997
+ const filePath = stringField(input, key);
1998
+ if (!filePath) {
1999
+ continue;
1970
2000
  }
2001
+ const metrics = claudeStyleFileMetrics(tool, input);
2002
+ addResolvedPathActivity(changes, filePath, operation, ts, cwd, workdir, metrics);
1971
2003
  }
1972
- return refs2;
1973
- }
1974
- function stringOption(value) {
1975
- if (Array.isArray(value)) {
1976
- return value.at(-1);
2004
+ for (const edit of [...arrayField(input, "edits"), ...arrayField(input, "replacements")]) {
2005
+ if (!isPlainObject(edit)) {
2006
+ continue;
2007
+ }
2008
+ const filePath = stringField(input, "file_path") || stringField(input, "path");
2009
+ if (!filePath) {
2010
+ continue;
2011
+ }
2012
+ addResolvedPathActivity(changes, filePath, "edit", ts, cwd, workdir, {
2013
+ linesAdded: countTextLines(stringField(edit, "new_string") || stringField(edit, "new_text")),
2014
+ linesRemoved: countTextLines(stringField(edit, "old_string") || stringField(edit, "original_text"))
2015
+ });
1977
2016
  }
1978
- return typeof value === "string" ? value : void 0;
1979
- }
1980
- function valuesOption(value) {
1981
- if (Array.isArray(value)) {
1982
- return value;
2017
+ const command = stringField(input, "command");
2018
+ if (command) {
2019
+ for (const item of fileActivitiesFromShellCommand(command, ts, cwd, workdir)) {
2020
+ changes.set(item.path, mergeFileActivity(changes.get(item.path), item));
2021
+ }
1983
2022
  }
1984
- return typeof value === "string" ? [value] : [];
2023
+ return [...changes.values()];
1985
2024
  }
1986
- function numberOption(value) {
1987
- if (typeof value === "number" && Number.isFinite(value)) {
1988
- return value;
2025
+ function claudeStyleFileMetrics(tool, input) {
2026
+ const normalized = tool.toLowerCase();
2027
+ if (normalized === "read") {
2028
+ return { linesRead: numberField(input, "limit") };
1989
2029
  }
1990
- if (Array.isArray(value)) {
1991
- return numberOption(value.at(-1));
2030
+ if (normalized === "write") {
2031
+ const content = stringField(input, "content") || stringField(input, "file_content");
2032
+ return {
2033
+ linesAdded: countTextLines(content),
2034
+ charsWritten: content?.length
2035
+ };
1992
2036
  }
1993
- const text = stringOption(value);
1994
- if (!text) {
1995
- return void 0;
2037
+ if (normalized === "edit" || normalized === "multiedit" || normalized === "searchreplace") {
2038
+ const oldString = stringField(input, "old_string");
2039
+ const newString = stringField(input, "new_string");
2040
+ return {
2041
+ linesAdded: countTextLines(newString),
2042
+ linesRemoved: countTextLines(oldString),
2043
+ charsWritten: newString?.length
2044
+ };
1996
2045
  }
1997
- const parsed = Number(text);
1998
- return Number.isFinite(parsed) ? parsed : void 0;
2046
+ return {};
1999
2047
  }
2000
2048
 
2049
+ // src/lib/constants.ts
2050
+ var PACKAGE_VERSION = true ? "0.1.45" : "0.1.1";
2051
+ var DEFAULT_API_URL = "http://121.196.224.82:3001";
2052
+ var DEFAULT_BACKFILL_BATCH_SIZE = 50;
2053
+ var DEFAULT_BACKFILL_BATCH_BYTES = 800 * 1024;
2054
+ var ROLLUP_BUCKET_MS = 15 * 60 * 1e3;
2055
+ var DEFAULT_HOOK_SYNC_MIN_INTERVAL_SECONDS = 60;
2056
+
2001
2057
  // src/lib/jsonl.ts
2002
2058
  function parseJsonLine(line) {
2003
2059
  try {
@@ -3868,14 +3924,14 @@ function claudeProjectRootFromCwds(projectDir, cwds) {
3868
3924
  return void 0;
3869
3925
  }
3870
3926
  function encodeClaudeProjectPath(value) {
3871
- return path7.resolve(value).split(path7.sep).join("-").replace(/_/g, "-");
3927
+ return path7.resolve(value).split(path7.sep).join("-").replaceAll("_", "-");
3872
3928
  }
3873
3929
  function rawClaudeProjectPath(value) {
3874
3930
  return path7.resolve(value).split(path7.sep).join("-");
3875
3931
  }
3876
3932
  function claudeEncodedVariants(value) {
3877
3933
  const raw = rawClaudeProjectPath(value);
3878
- const normalized = raw.replace(/_/g, "-");
3934
+ const normalized = raw.replaceAll("_", "-");
3879
3935
  return raw === normalized ? [raw] : [raw, normalized];
3880
3936
  }
3881
3937
  function claudeEncodedProjectSuffix(projectDir, home) {
@@ -4129,7 +4185,7 @@ async function discoverCoworkTranscripts(root) {
4129
4185
  function coworkMetaPathForTranscript(transcriptPath) {
4130
4186
  const marker = `${path8.sep}.claude${path8.sep}projects${path8.sep}`;
4131
4187
  const index = transcriptPath.indexOf(marker);
4132
- if (index < 0) {
4188
+ if (index === -1) {
4133
4189
  return void 0;
4134
4190
  }
4135
4191
  return `${transcriptPath.slice(0, index)}.json`;
@@ -4144,7 +4200,7 @@ function projectFromMeta(meta) {
4144
4200
  }
4145
4201
  const resolved = path8.resolve(folder);
4146
4202
  return {
4147
- project: path8.basename(resolved).replace(/-/g, "_") || "cowork",
4203
+ project: path8.basename(resolved).replaceAll("-", "_") || "cowork",
4148
4204
  cwd: resolved
4149
4205
  };
4150
4206
  }
@@ -4269,7 +4325,7 @@ function createClaudeCoworkAdapter() {
4269
4325
  }
4270
4326
 
4271
4327
  // src/adapters/codebuddy.ts
4272
- import { readFile as readFile5, readdir as readdir4 } from "node:fs/promises";
4328
+ import { readdir as readdir4, readFile as readFile5 } from "node:fs/promises";
4273
4329
  import path9 from "node:path";
4274
4330
  var ENDPOINT_MODEL_ID_RE = /^(?:ep|endpoint)-/i;
4275
4331
  async function parseCodebuddyTraceFile(filePath, options) {
@@ -4504,6 +4560,28 @@ async function parseCodebuddyTraceFile(filePath, options) {
4504
4560
  sourceId: span.spanId
4505
4561
  })
4506
4562
  }), index, "function");
4563
+ const toolInput = parseEmbeddedJson(span.toolInput);
4564
+ if (isPlainObject(toolInput)) {
4565
+ const fileActivities = claudeStyleToolFileActivities(tool, toolInput, ts, cwd);
4566
+ if (fileActivities.length > 0) {
4567
+ push(baseEvent({
4568
+ ts,
4569
+ type: eventTypeFromFileActivities(fileActivities),
4570
+ operation: `${tool} file activity`,
4571
+ sessionId,
4572
+ cwd,
4573
+ project,
4574
+ model,
4575
+ tool,
4576
+ confidence: "derived",
4577
+ fileActivities,
4578
+ metrics: summarizeFileActivities(fileActivities),
4579
+ refs: stringRefs({
4580
+ sourceId: span.spanId
4581
+ })
4582
+ }), index, "function");
4583
+ }
4584
+ }
4507
4585
  if (span.endedAt) {
4508
4586
  const durationMs = span.duration ?? void 0;
4509
4587
  push(baseEvent({
@@ -4527,30 +4605,28 @@ async function parseCodebuddyTraceFile(filePath, options) {
4527
4605
  })
4528
4606
  }), index, "function");
4529
4607
  }
4530
- if (tool === "Bash") {
4531
- if (span.endedAt) {
4532
- const durationMs = span.duration ?? void 0;
4533
- push(baseEvent({
4534
- ts: span.endedAt,
4535
- type: toolSuccess ? "command.completed" : "command.failed",
4536
- operation: "Bash completed",
4537
- sessionId,
4538
- cwd,
4539
- project,
4540
- model,
4541
- tool,
4542
- success: toolSuccess,
4543
- confidence: "derived",
4544
- metrics: {
4545
- commandCalls: 1,
4546
- commandDurationMs: durationMs,
4547
- durationMs
4548
- },
4549
- refs: stringRefs({
4550
- sourceId: span.spanId
4551
- })
4552
- }), index, "function");
4553
- }
4608
+ if (tool === "Bash" && span.endedAt) {
4609
+ const durationMs = span.duration ?? void 0;
4610
+ push(baseEvent({
4611
+ ts: span.endedAt,
4612
+ type: toolSuccess ? "command.completed" : "command.failed",
4613
+ operation: "Bash completed",
4614
+ sessionId,
4615
+ cwd,
4616
+ project,
4617
+ model,
4618
+ tool,
4619
+ success: toolSuccess,
4620
+ confidence: "derived",
4621
+ metrics: {
4622
+ commandCalls: 1,
4623
+ commandDurationMs: durationMs,
4624
+ durationMs
4625
+ },
4626
+ refs: stringRefs({
4627
+ sourceId: span.spanId
4628
+ })
4629
+ }), index, "function");
4554
4630
  }
4555
4631
  continue;
4556
4632
  }
@@ -4755,7 +4831,6 @@ async function resolveSessionIdFromSiblingTraces(filePath) {
4755
4831
  }
4756
4832
  } catch {
4757
4833
  }
4758
- return void 0;
4759
4834
  })();
4760
4835
  siblingSessionIdByDir.set(dir, cached);
4761
4836
  }
@@ -4949,6 +5024,33 @@ import { readFile as readFile6 } from "node:fs/promises";
4949
5024
  import path11 from "node:path";
4950
5025
 
4951
5026
  // src/lib/diff.ts
5027
+ function parseApplyPatch(patch, ts) {
5028
+ const changes = [];
5029
+ let current;
5030
+ for (const line of patch.split("\n")) {
5031
+ const fileMatch = line.match(/^\*\*\* (Add|Update|Delete) File: (.+)$/);
5032
+ if (fileMatch) {
5033
+ current = {
5034
+ ts,
5035
+ path: fileMatch[2].trim(),
5036
+ operation: fileMatch[1] === "Add" ? "create" : fileMatch[1] === "Delete" ? "delete" : "edit",
5037
+ linesAdded: 0,
5038
+ linesRemoved: 0
5039
+ };
5040
+ changes.push(current);
5041
+ continue;
5042
+ }
5043
+ if (!current) {
5044
+ continue;
5045
+ }
5046
+ if (line.startsWith("+") && !line.startsWith("+++")) {
5047
+ current.linesAdded = (current.linesAdded || 0) + 1;
5048
+ } else if (line.startsWith("-") && !line.startsWith("---")) {
5049
+ current.linesRemoved = (current.linesRemoved || 0) + 1;
5050
+ }
5051
+ }
5052
+ return changes;
5053
+ }
4952
5054
  function diffStats(diff) {
4953
5055
  let linesAdded = 0;
4954
5056
  let linesRemoved = 0;
@@ -4992,10 +5094,10 @@ function fileActivitiesFromPatchChanges(changes, ts, cwd, displayFilePath3) {
4992
5094
  }
4993
5095
 
4994
5096
  // src/lib/session-context.ts
4995
- init_fs();
4996
5097
  import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
4997
5098
  import os4 from "node:os";
4998
5099
  import path10 from "node:path";
5100
+ init_fs();
4999
5101
  var SESSION_CONTEXT_VERSION = 1;
5000
5102
  function sessionContextDir(home) {
5001
5103
  return path10.join(home, ".vibetime", "session-context");
@@ -5814,7 +5916,7 @@ async function parseCopilotSessionFile(filePath, options) {
5814
5916
  model = msgModel;
5815
5917
  }
5816
5918
  const msgTurnId = stringField(data, "turnId");
5817
- const turnId = msgTurnId !== void 0 ? `turn_${createStableHash([sessionId, msgTurnId]).slice(0, 24)}` : currentTurnId;
5919
+ const turnId = msgTurnId === void 0 ? currentTurnId : `turn_${createStableHash([sessionId, msgTurnId]).slice(0, 24)}`;
5818
5920
  events.push(baseCopilotEvent({
5819
5921
  ts,
5820
5922
  type: "agent.operation",
@@ -5841,7 +5943,7 @@ async function parseCopilotSessionFile(filePath, options) {
5841
5943
  const toolCallId = stringField(data, "toolCallId");
5842
5944
  const toolName = stringField(data, "toolName") || "tool";
5843
5945
  const toolTurnId = stringField(data, "turnId");
5844
- const turnId = toolTurnId !== void 0 ? `turn_${createStableHash([sessionId, toolTurnId]).slice(0, 24)}` : currentTurnId;
5946
+ const turnId = toolTurnId === void 0 ? currentTurnId : `turn_${createStableHash([sessionId, toolTurnId]).slice(0, 24)}`;
5845
5947
  if (toolCallId) {
5846
5948
  pendingTools.set(toolCallId, { tool: toolName, startedAt: ts, turnId });
5847
5949
  }
@@ -5859,6 +5961,24 @@ async function parseCopilotSessionFile(filePath, options) {
5859
5961
  metrics: { toolCalls: 1 },
5860
5962
  refs: stringRefs({ sourceId: toolCallId })
5861
5963
  }));
5964
+ const fileActivities = copilotFileActivities(toolName, data.arguments, ts, cwd);
5965
+ if (fileActivities.length > 0) {
5966
+ events.push(baseCopilotEvent({
5967
+ ts,
5968
+ type: eventTypeFromFileActivities(fileActivities),
5969
+ operation: `${toolName} file activity`,
5970
+ sessionId,
5971
+ turnId,
5972
+ cwd,
5973
+ project,
5974
+ model,
5975
+ tool: toolName,
5976
+ confidence: "derived",
5977
+ fileActivities,
5978
+ metrics: summarizeFileActivities(fileActivities),
5979
+ refs: stringRefs({ sourceId: toolCallId })
5980
+ }));
5981
+ }
5862
5982
  break;
5863
5983
  }
5864
5984
  case "tool.execution_complete": {
@@ -5881,7 +6001,7 @@ async function parseCopilotSessionFile(filePath, options) {
5881
6001
  tool: pending?.tool,
5882
6002
  success,
5883
6003
  confidence: "exact",
5884
- metrics: durationMs !== void 0 ? { toolDurationMs: durationMs, durationMs } : void 0,
6004
+ metrics: durationMs === void 0 ? void 0 : { toolDurationMs: durationMs, durationMs },
5885
6005
  refs: stringRefs({ sourceId: toolCallId })
5886
6006
  }));
5887
6007
  const toolLower = (pending?.tool || "").toLowerCase();
@@ -5910,7 +6030,7 @@ async function parseCopilotSessionFile(filePath, options) {
5910
6030
  }
5911
6031
  case "assistant.turn_end": {
5912
6032
  const endTurnId = stringField(data, "turnId");
5913
- const turnId = endTurnId !== void 0 ? `turn_${createStableHash([sessionId, endTurnId]).slice(0, 24)}` : currentTurnId;
6033
+ const turnId = endTurnId === void 0 ? currentTurnId : `turn_${createStableHash([sessionId, endTurnId]).slice(0, 24)}`;
5914
6034
  const accum = turnId ? turnTokenAccum.get(turnId) : void 0;
5915
6035
  if (accum && accum.tokensOutput && accum.tokensOutput > 0) {
5916
6036
  accum.reasoningEffort = reasoningEffort;
@@ -5997,6 +6117,29 @@ async function parseCopilotSessionFile(filePath, options) {
5997
6117
  }
5998
6118
  return events.filter((event) => validateCanonicalEvent(event).valid);
5999
6119
  }
6120
+ function copilotFileActivities(tool, args, ts, cwd) {
6121
+ const normalized = tool.toLowerCase();
6122
+ if (normalized === "apply_patch" && typeof args === "string") {
6123
+ return parseApplyPatch(args, ts);
6124
+ }
6125
+ if (!isPlainObject(args)) {
6126
+ return [];
6127
+ }
6128
+ const changes = /* @__PURE__ */ new Map();
6129
+ const filePath = stringField(args, "path") || stringField(args, "file_path");
6130
+ if (filePath) {
6131
+ const metrics = {};
6132
+ if (normalized === "edit") {
6133
+ const oldStr = stringField(args, "old_str");
6134
+ const newStr = stringField(args, "new_str");
6135
+ metrics.linesAdded = countTextLines(newStr);
6136
+ metrics.linesRemoved = countTextLines(oldStr);
6137
+ metrics.charsWritten = newStr?.length;
6138
+ }
6139
+ addResolvedPathActivity(changes, filePath, operationForTool(normalized === "view" ? "read" : tool), ts, cwd, cwd, metrics);
6140
+ }
6141
+ return [...changes.values()];
6142
+ }
6000
6143
  function baseCopilotEvent(event) {
6001
6144
  return {
6002
6145
  schemaVersion: AGENT_TIME_SCHEMA_VERSION,
@@ -6205,7 +6348,7 @@ async function parseGrokSessionFile(filePath, options) {
6205
6348
  }
6206
6349
  if (type === "turn_started") {
6207
6350
  const turnNumber = numberField(raw, "turn_number");
6208
- currentTurnId = turnNumber !== void 0 ? `turn_${turnNumber}` : `turn_${createStableHash([sessionId, lineNumber]).slice(0, 24)}`;
6351
+ currentTurnId = turnNumber === void 0 ? `turn_${createStableHash([sessionId, lineNumber]).slice(0, 24)}` : `turn_${turnNumber}`;
6209
6352
  currentTurnStartedAt = ts;
6210
6353
  const turnModel = stringField(raw, "model_id") || model;
6211
6354
  push(base({
@@ -6229,7 +6372,7 @@ async function parseGrokSessionFile(filePath, options) {
6229
6372
  turnId,
6230
6373
  success,
6231
6374
  confidence: "exact",
6232
- metrics: durationMs !== void 0 ? { durationMs } : void 0,
6375
+ metrics: durationMs === void 0 ? void 0 : { durationMs },
6233
6376
  refs: stringRefs({
6234
6377
  sourceId: `${sessionId}:${turnId || "turn"}:ended`,
6235
6378
  outcome
@@ -6374,7 +6517,7 @@ function resolveSessionDir(filePath) {
6374
6517
  if (base === "summary.json" || base === "events.jsonl" || base === "signals.json") {
6375
6518
  return path13.dirname(filePath);
6376
6519
  }
6377
- if (base.match(/^[0-9a-f]{8}-[0-9a-f-]{27,}$/i)) {
6520
+ if (/^[0-9a-f]{8}-[0-9a-f-]{27,}$/i.test(base)) {
6378
6521
  return filePath;
6379
6522
  }
6380
6523
  return void 0;
@@ -7381,6 +7524,18 @@ function piFileActivitiesFromToolCall(tool, input, ts, cwd) {
7381
7524
  addResolvedPathActivity(changes, filePath, operation, ts, cwd, cwd, piToolFileMetrics(normalized, input));
7382
7525
  }
7383
7526
  }
7527
+ if (normalized === "edit") {
7528
+ const filePath = stringField(input, "path") || stringField(input, "file_path") || stringField(input, "filePath");
7529
+ for (const edit of Array.isArray(input.edits) ? input.edits : []) {
7530
+ if (!isPlainObject(edit) || !filePath) {
7531
+ continue;
7532
+ }
7533
+ addResolvedPathActivity(changes, filePath, "edit", ts, cwd, cwd, {
7534
+ linesAdded: countTextLines(stringField(edit, "newText") || stringField(edit, "new_text")),
7535
+ linesRemoved: countTextLines(stringField(edit, "oldText") || stringField(edit, "old_text"))
7536
+ });
7537
+ }
7538
+ }
7384
7539
  if (normalized === "bash" || normalized === "run") {
7385
7540
  const command = stringField(input, "command");
7386
7541
  if (command) {
@@ -8069,7 +8224,7 @@ async function parseQoderCnSessionFile(filePath, options) {
8069
8224
  const toolUseId = stringField(toolUse, "id") || `tool_${createStableHash([filePath, lineNumber, tool]).slice(0, 24)}`;
8070
8225
  const input = objectField(toolUse, "input");
8071
8226
  const command = stringField(input, "command");
8072
- const fileActivities = fileActivitiesFromQoderCnToolUse(tool, input, ts, cwd);
8227
+ const fileActivities = claudeStyleToolFileActivities(tool, input, ts, cwd);
8073
8228
  const agentInstanceId = tool === "Agent" ? `agent_${createStableHash([sessionId, toolUseId]).slice(0, 24)}` : void 0;
8074
8229
  pendingTools.set(toolUseId, {
8075
8230
  id: toolUseId,
@@ -8150,7 +8305,11 @@ async function parseQoderCnSessionFile(filePath, options) {
8150
8305
  const mapped = {
8151
8306
  ...event,
8152
8307
  sessionId: parentSessionId,
8153
- refs: { ...event.refs, sourcePathHash: parentSourcePathHash }
8308
+ refs: {
8309
+ ...event.refs,
8310
+ sourcePathHash: parentSourcePathHash,
8311
+ ...event.sessionId && event.sessionId !== parentSessionId ? { supersededSessionId: event.sessionId } : {}
8312
+ }
8154
8313
  };
8155
8314
  return rebuildEventIdentity2(mapped);
8156
8315
  });
@@ -8163,7 +8322,11 @@ async function parseQoderCnSessionFile(filePath, options) {
8163
8322
  return validEvents.map((event) => rebuildEventIdentity2({
8164
8323
  ...event,
8165
8324
  sessionId: dbModelCalls.rootSessionId,
8166
- refs: { ...event.refs, sourcePathHash: parentSourcePathHash }
8325
+ refs: {
8326
+ ...event.refs,
8327
+ sourcePathHash: parentSourcePathHash,
8328
+ ...event.sessionId && event.sessionId !== dbModelCalls.rootSessionId ? { supersededSessionId: event.sessionId } : {}
8329
+ }
8167
8330
  }));
8168
8331
  }
8169
8332
  return validEvents;
@@ -8198,62 +8361,6 @@ function qoderCnSubagentMetrics(toolResult, fallbackDurationMs) {
8198
8361
  tokensTotal: numberField(toolResult, "totalTokens") || totalInputTokens + outputTokens || void 0
8199
8362
  };
8200
8363
  }
8201
- function fileActivitiesFromQoderCnToolUse(tool, input, ts, cwd) {
8202
- const changes = /* @__PURE__ */ new Map();
8203
- const operation = operationForTool(tool);
8204
- const workdir = stringField(input, "cwd") || cwd;
8205
- for (const key of ["file_path", "path", "notebook_path"]) {
8206
- const filePath = stringField(input, key);
8207
- if (!filePath) {
8208
- continue;
8209
- }
8210
- const metrics = fileMetricsFromQoderCnToolInput(tool, input);
8211
- addResolvedPathActivity(changes, filePath, operation, ts, cwd, workdir, metrics);
8212
- }
8213
- for (const edit of arrayField4(input, "edits")) {
8214
- if (!isPlainObject(edit)) {
8215
- continue;
8216
- }
8217
- const filePath = stringField(input, "file_path") || stringField(input, "path");
8218
- if (!filePath) {
8219
- continue;
8220
- }
8221
- addResolvedPathActivity(changes, filePath, "edit", ts, cwd, workdir, {
8222
- linesAdded: countTextLines(stringField(edit, "new_string")),
8223
- linesRemoved: countTextLines(stringField(edit, "old_string"))
8224
- });
8225
- }
8226
- const command = stringField(input, "command");
8227
- if (command) {
8228
- for (const item of fileActivitiesFromShellCommand(command, ts, cwd, workdir)) {
8229
- changes.set(item.path, mergeFileActivity(changes.get(item.path), item));
8230
- }
8231
- }
8232
- return [...changes.values()];
8233
- }
8234
- function fileMetricsFromQoderCnToolInput(tool, input) {
8235
- const normalized = tool.toLowerCase();
8236
- if (normalized === "read") {
8237
- return { linesRead: numberField(input, "limit") };
8238
- }
8239
- if (normalized === "write") {
8240
- const content = stringField(input, "content");
8241
- return {
8242
- linesAdded: countTextLines(content),
8243
- charsWritten: content?.length
8244
- };
8245
- }
8246
- if (normalized === "edit" || normalized === "multiedit") {
8247
- const oldString = stringField(input, "old_string");
8248
- const newString = stringField(input, "new_string");
8249
- return {
8250
- linesAdded: countTextLines(newString),
8251
- linesRemoved: countTextLines(oldString),
8252
- charsWritten: newString?.length
8253
- };
8254
- }
8255
- return {};
8256
- }
8257
8364
  function arrayField4(object, key) {
8258
8365
  if (!isPlainObject(object) || !Array.isArray(object[key])) {
8259
8366
  return [];
@@ -8414,7 +8521,7 @@ function qoderCnProjectRootFromCwds(projectDir, cwds) {
8414
8521
  return void 0;
8415
8522
  }
8416
8523
  function encodeQoderCnProjectPath(value) {
8417
- return path17.resolve(value).split(path17.sep).join("-").replace(/_/g, "-");
8524
+ return path17.resolve(value).split(path17.sep).join("-").replaceAll("_", "-");
8418
8525
  }
8419
8526
  async function qoderCnProjectFromFilePath(filePath, options) {
8420
8527
  const projectDir = path17.basename(path17.dirname(filePath));
@@ -8895,7 +9002,7 @@ async function parseQoderSessionFile(filePath, options) {
8895
9002
  const toolUseId = stringField(toolUse, "id") || `tool_${createStableHash([filePath, lineNumber, tool]).slice(0, 24)}`;
8896
9003
  const input = objectField(toolUse, "input");
8897
9004
  const command = stringField(input, "command");
8898
- const fileActivities = fileActivitiesFromQoderToolUse(tool, input, ts, cwd);
9005
+ const fileActivities = claudeStyleToolFileActivities(tool, input, ts, cwd);
8899
9006
  const agentInstanceId = tool === "Agent" ? `agent_${createStableHash([sessionId, toolUseId]).slice(0, 24)}` : void 0;
8900
9007
  pendingTools.set(toolUseId, {
8901
9008
  id: toolUseId,
@@ -8976,7 +9083,11 @@ async function parseQoderSessionFile(filePath, options) {
8976
9083
  const mapped = {
8977
9084
  ...event,
8978
9085
  sessionId: parentSessionId,
8979
- refs: { ...event.refs, sourcePathHash: parentSourcePathHash }
9086
+ refs: {
9087
+ ...event.refs,
9088
+ sourcePathHash: parentSourcePathHash,
9089
+ ...event.sessionId && event.sessionId !== parentSessionId ? { supersededSessionId: event.sessionId } : {}
9090
+ }
8980
9091
  };
8981
9092
  return rebuildEventIdentity3(mapped);
8982
9093
  });
@@ -8989,7 +9100,11 @@ async function parseQoderSessionFile(filePath, options) {
8989
9100
  return validEvents.map((event) => rebuildEventIdentity3({
8990
9101
  ...event,
8991
9102
  sessionId: dbModelCalls.rootSessionId,
8992
- refs: { ...event.refs, sourcePathHash: parentSourcePathHash }
9103
+ refs: {
9104
+ ...event.refs,
9105
+ sourcePathHash: parentSourcePathHash,
9106
+ ...event.sessionId && event.sessionId !== dbModelCalls.rootSessionId ? { supersededSessionId: event.sessionId } : {}
9107
+ }
8993
9108
  }));
8994
9109
  }
8995
9110
  return validEvents;
@@ -9024,62 +9139,6 @@ function qoderSubagentMetrics(toolResult, fallbackDurationMs) {
9024
9139
  tokensTotal: numberField(toolResult, "totalTokens") || totalInputTokens + outputTokens || void 0
9025
9140
  };
9026
9141
  }
9027
- function fileActivitiesFromQoderToolUse(tool, input, ts, cwd) {
9028
- const changes = /* @__PURE__ */ new Map();
9029
- const operation = operationForTool(tool);
9030
- const workdir = stringField(input, "cwd") || cwd;
9031
- for (const key of ["file_path", "path", "notebook_path"]) {
9032
- const filePath = stringField(input, key);
9033
- if (!filePath) {
9034
- continue;
9035
- }
9036
- const metrics = fileMetricsFromQoderToolInput(tool, input);
9037
- addResolvedPathActivity(changes, filePath, operation, ts, cwd, workdir, metrics);
9038
- }
9039
- for (const edit of arrayField5(input, "edits")) {
9040
- if (!isPlainObject(edit)) {
9041
- continue;
9042
- }
9043
- const filePath = stringField(input, "file_path") || stringField(input, "path");
9044
- if (!filePath) {
9045
- continue;
9046
- }
9047
- addResolvedPathActivity(changes, filePath, "edit", ts, cwd, workdir, {
9048
- linesAdded: countTextLines(stringField(edit, "new_string")),
9049
- linesRemoved: countTextLines(stringField(edit, "old_string"))
9050
- });
9051
- }
9052
- const command = stringField(input, "command");
9053
- if (command) {
9054
- for (const item of fileActivitiesFromShellCommand(command, ts, cwd, workdir)) {
9055
- changes.set(item.path, mergeFileActivity(changes.get(item.path), item));
9056
- }
9057
- }
9058
- return [...changes.values()];
9059
- }
9060
- function fileMetricsFromQoderToolInput(tool, input) {
9061
- const normalized = tool.toLowerCase();
9062
- if (normalized === "read") {
9063
- return { linesRead: numberField(input, "limit") };
9064
- }
9065
- if (normalized === "write") {
9066
- const content = stringField(input, "content");
9067
- return {
9068
- linesAdded: countTextLines(content),
9069
- charsWritten: content?.length
9070
- };
9071
- }
9072
- if (normalized === "edit" || normalized === "multiedit") {
9073
- const oldString = stringField(input, "old_string");
9074
- const newString = stringField(input, "new_string");
9075
- return {
9076
- linesAdded: countTextLines(newString),
9077
- linesRemoved: countTextLines(oldString),
9078
- charsWritten: newString?.length
9079
- };
9080
- }
9081
- return {};
9082
- }
9083
9142
  function arrayField5(object, key) {
9084
9143
  if (!isPlainObject(object) || !Array.isArray(object[key])) {
9085
9144
  return [];
@@ -9206,14 +9265,14 @@ function qoderProjectRootFromCwds(projectDir, cwds) {
9206
9265
  return void 0;
9207
9266
  }
9208
9267
  function encodeQoderProjectPath(value) {
9209
- return path18.resolve(value).split(path18.sep).join("-").replace(/_/g, "-");
9268
+ return path18.resolve(value).split(path18.sep).join("-").replaceAll("_", "-");
9210
9269
  }
9211
9270
  function rawQoderProjectPath(value) {
9212
9271
  return path18.resolve(value).split(path18.sep).join("-");
9213
9272
  }
9214
9273
  function qoderEncodedVariants(value) {
9215
9274
  const raw = rawQoderProjectPath(value);
9216
- const normalized = raw.replace(/_/g, "-");
9275
+ const normalized = raw.replaceAll("_", "-");
9217
9276
  return raw === normalized ? [raw] : [raw, normalized];
9218
9277
  }
9219
9278
  function qoderEncodedProjectSuffix(projectDir, home) {
@@ -9369,7 +9428,7 @@ function normalizeId(id) {
9369
9428
  }
9370
9429
 
9371
9430
  // src/adapters/workbuddy.ts
9372
- import { readFile as readFile12, readdir as readdir9, stat as stat10 } from "node:fs/promises";
9431
+ import { readdir as readdir9, readFile as readFile12, stat as stat10 } from "node:fs/promises";
9373
9432
  import path19 from "node:path";
9374
9433
  function workbuddyProjectsDir(home, env) {
9375
9434
  const override = env?.WORKBUDDY_PROJECTS_DIR || env?.WORKBUDDY_HOME;
@@ -9575,7 +9634,9 @@ async function parseWorkbuddySessionFile(filePath, options) {
9575
9634
  confidence: "derived",
9576
9635
  refs: { sourceId: `${sessionPrefix}:session`, ...subagentRefs }
9577
9636
  }, lines[0], filePath, options);
9578
- if (event) events.push(event);
9637
+ if (event) {
9638
+ events.push(event);
9639
+ }
9579
9640
  }
9580
9641
  for (const line of lines) {
9581
9642
  const record = line.record;
@@ -9590,7 +9651,9 @@ async function parseWorkbuddySessionFile(filePath, options) {
9590
9651
  continue;
9591
9652
  }
9592
9653
  const ended = closeTurn(line);
9593
- if (ended) events.push(ended);
9654
+ if (ended) {
9655
+ events.push(ended);
9656
+ }
9594
9657
  turnIndex += 1;
9595
9658
  currentTurnId = `${sessionPrefix}:turn:${turnIndex}`;
9596
9659
  turnLastTs = ts;
@@ -9628,7 +9691,9 @@ async function parseWorkbuddySessionFile(filePath, options) {
9628
9691
  refs: { sourceId: `${sourceId}:prompt` }
9629
9692
  }, line, filePath, options)
9630
9693
  ]) {
9631
- if (event) events.push(event);
9694
+ if (event) {
9695
+ events.push(event);
9696
+ }
9632
9697
  }
9633
9698
  continue;
9634
9699
  }
@@ -9655,7 +9720,9 @@ async function parseWorkbuddySessionFile(filePath, options) {
9655
9720
  confidence: "partial",
9656
9721
  refs: { sourceId: `${sourceId}:usage` }
9657
9722
  }, line, filePath, options);
9658
- if (usage) events.push(usage);
9723
+ if (usage) {
9724
+ events.push(usage);
9725
+ }
9659
9726
  }
9660
9727
  continue;
9661
9728
  }
@@ -9682,7 +9749,40 @@ async function parseWorkbuddySessionFile(filePath, options) {
9682
9749
  confidence: "exact",
9683
9750
  refs: { sourceId: `${callId}:start` }
9684
9751
  }, line, filePath, options);
9685
- if (started) events.push(started);
9752
+ if (started) {
9753
+ events.push(started);
9754
+ }
9755
+ let toolInput;
9756
+ try {
9757
+ toolInput = JSON.parse(stringField(record, "arguments") || "");
9758
+ } catch {
9759
+ toolInput = void 0;
9760
+ }
9761
+ if (isPlainObject(toolInput)) {
9762
+ const fileActivities = claudeStyleToolFileActivities(tool, toolInput, ts, cwd);
9763
+ if (fileActivities.length > 0) {
9764
+ const fileEvent = makeEvent({
9765
+ schemaVersion: AGENT_TIME_SCHEMA_VERSION,
9766
+ ts,
9767
+ type: eventTypeFromFileActivities(fileActivities),
9768
+ source: "workbuddy",
9769
+ workspaceId,
9770
+ project,
9771
+ cwd,
9772
+ sessionId: sessionPrefix,
9773
+ turnId: currentTurnId,
9774
+ agent: "workbuddy",
9775
+ tool,
9776
+ confidence: "derived",
9777
+ fileActivities,
9778
+ metrics: summarizeFileActivities(fileActivities),
9779
+ refs: { sourceId: `${callId}:files` }
9780
+ }, line, filePath, options);
9781
+ if (fileEvent) {
9782
+ events.push(fileEvent);
9783
+ }
9784
+ }
9785
+ }
9686
9786
  }
9687
9787
  const metrics = workbuddyUsageMetrics(record);
9688
9788
  const model = stringField(objectField(record, "providerData"), "model");
@@ -9703,7 +9803,9 @@ async function parseWorkbuddySessionFile(filePath, options) {
9703
9803
  confidence: metrics ? "partial" : "derived",
9704
9804
  refs: { sourceId: `${sourceId}:usage:${callId || line.lineNumber}` }
9705
9805
  }, line, filePath, options);
9706
- if (usage) events.push(usage);
9806
+ if (usage) {
9807
+ events.push(usage);
9808
+ }
9707
9809
  }
9708
9810
  continue;
9709
9811
  }
@@ -9732,13 +9834,17 @@ async function parseWorkbuddySessionFile(filePath, options) {
9732
9834
  confidence: start ? "derived" : "partial",
9733
9835
  refs: { sourceId: `${callId}:result` }
9734
9836
  }, line, filePath, options);
9735
- if (completed) events.push(completed);
9837
+ if (completed) {
9838
+ events.push(completed);
9839
+ }
9736
9840
  }
9737
9841
  }
9738
9842
  const lastLine = lines.at(-1);
9739
9843
  if (lastLine) {
9740
9844
  const ended = closeTurn(lastLine);
9741
- if (ended) events.push(ended);
9845
+ if (ended) {
9846
+ events.push(ended);
9847
+ }
9742
9848
  }
9743
9849
  const lastTs = lastLine ? timestampFrom(numberField(lastLine.record, "timestamp")) : void 0;
9744
9850
  if (lastLine && lastTs) {
@@ -9755,7 +9861,9 @@ async function parseWorkbuddySessionFile(filePath, options) {
9755
9861
  confidence: "derived",
9756
9862
  refs: { sourceId: `${sessionPrefix}:ended` }
9757
9863
  }, lastLine, filePath, options);
9758
- if (event) events.push(event);
9864
+ if (event) {
9865
+ events.push(event);
9866
+ }
9759
9867
  }
9760
9868
  return events;
9761
9869
  }
@@ -10758,6 +10866,7 @@ function buildSessionRollup(rollupKey, events) {
10758
10866
  const agent = first.agent || ordered.find((event) => event.agent)?.agent;
10759
10867
  const startedAt = ordered[0]?.ts || (/* @__PURE__ */ new Date()).toISOString();
10760
10868
  const lastEventAt = ordered.at(-1)?.ts || startedAt;
10869
+ const supersededSessionIds = [...new Set(ordered.map((event) => event.refs?.supersededSessionId).filter((id) => Boolean(id) && id !== sessionId))];
10761
10870
  const timeBuckets = /* @__PURE__ */ new Map();
10762
10871
  const modelRollups = /* @__PURE__ */ new Map();
10763
10872
  const toolRollups = /* @__PURE__ */ new Map();
@@ -11040,7 +11149,8 @@ function buildSessionRollup(rollupKey, events) {
11040
11149
  startedAt: startedAt2,
11041
11150
  durationMs: timing?.lastCallAt ? Math.max(0, Date.parse(timing.lastCallAt) - Date.parse(startedAt2)) : 0
11042
11151
  };
11043
- }).sort((a, b) => a.startedAt.localeCompare(b.startedAt))
11152
+ }).sort((a, b) => a.startedAt.localeCompare(b.startedAt)),
11153
+ ...supersededSessionIds.length > 0 ? { supersededSessionIds } : {}
11044
11154
  };
11045
11155
  return {
11046
11156
  ...baseRollup,
@@ -11454,7 +11564,7 @@ async function postRollupBatch(remote, rollups, options = {}) {
11454
11564
  };
11455
11565
  }
11456
11566
  async function deleteRollupsBySource(remote, source, machine, options = {}) {
11457
- const query = `source=${encodeURIComponent(source)}${options.preserveTokens ? "&preserveTokens=1" : ""}${options.allowHistoricalRewrite ? "&allowHistoricalRewrite=1" : ""}`;
11567
+ const query = `source=${encodeURIComponent(source)}${options.rollupKey ? `&rollupKey=${encodeURIComponent(options.rollupKey)}` : ""}${options.preserveTokens ? "&preserveTokens=1" : ""}${options.allowHistoricalRewrite ? "&allowHistoricalRewrite=1" : ""}`;
11458
11568
  const response = await remote.fetchImpl(
11459
11569
  joinUrl(remote.baseUrl, `/v3/agent/sessions?${query}`),
11460
11570
  { method: "DELETE", headers: buildHeaders(remote.token, machine) }
@@ -12311,6 +12421,21 @@ async function sendSessionRollupBatch(rollups, options, ctx) {
12311
12421
  machine,
12312
12422
  allowHistoricalRewrite: options["purge-all"] === true
12313
12423
  });
12424
+ if (options.force && result.conflicts === 0) {
12425
+ for (const rollup of rollups) {
12426
+ for (const sessionId of rollup.supersededSessionIds ?? []) {
12427
+ try {
12428
+ await deleteRollupsBySource(remote, rollup.source, machine, {
12429
+ rollupKey: createImportKey(["rollup", rollup.source, sessionId]),
12430
+ allowHistoricalRewrite: options["purge-all"] === true
12431
+ });
12432
+ } catch (error) {
12433
+ debug(ctx, `Failed to delete superseded ${rollup.source} session ${sessionId}: ${error.message}
12434
+ `);
12435
+ }
12436
+ }
12437
+ }
12438
+ }
12314
12439
  return result;
12315
12440
  }
12316
12441
  async function deleteSessionRollupsBySourceAPI(source, options, ctx, preserveTokens) {
@@ -12392,8 +12517,8 @@ function selectBackfillFilesForImport(files, watermarkTs) {
12392
12517
  }
12393
12518
  }
12394
12519
  const order = /* @__PURE__ */ new Map();
12395
- for (let i = 0; i < files.length; i += 1) {
12396
- order.set(files[i].path, i);
12520
+ for (const [i, file] of files.entries()) {
12521
+ order.set(file.path, i);
12397
12522
  }
12398
12523
  picked.sort((a, b) => (order.get(a.path) ?? 0) - (order.get(b.path) ?? 0));
12399
12524
  return picked;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@yhong91/vibetime",
3
3
  "type": "module",
4
- "version": "0.1.43",
4
+ "version": "0.1.45",
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": {