@yhong91/vibetime 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/vibetime.mjs +864 -62
- package/package.json +1 -1
package/bin/vibetime.mjs
CHANGED
|
@@ -159,9 +159,9 @@ var init_fs = __esm({
|
|
|
159
159
|
|
|
160
160
|
// src/cli.ts
|
|
161
161
|
import { spawn } from "node:child_process";
|
|
162
|
-
import { mkdir as mkdir4, rename as rename2, rm, stat as
|
|
162
|
+
import { mkdir as mkdir4, rename as rename2, rm, stat as stat10, writeFile as writeFile3 } from "node:fs/promises";
|
|
163
163
|
import os7 from "node:os";
|
|
164
|
-
import
|
|
164
|
+
import path18 from "node:path";
|
|
165
165
|
import { fileURLToPath } from "node:url";
|
|
166
166
|
|
|
167
167
|
// ../shared/src/index.ts
|
|
@@ -197,7 +197,7 @@ var TELEMETRY_EVENT_TYPES = [
|
|
|
197
197
|
"agent.operation"
|
|
198
198
|
];
|
|
199
199
|
var FILE_ACTIVITY_OPERATIONS = ["read", "search", "create", "write", "edit", "delete"];
|
|
200
|
-
var BACKFILL_SOURCE_IDS = ["codex", "claude-code", "claude-cowork", "opencode", "pi", "agy", "codebuddy", "qoder", "qoder-cn"];
|
|
200
|
+
var BACKFILL_SOURCE_IDS = ["codex", "claude-code", "claude-cowork", "opencode", "pi", "agy", "codebuddy", "qoder", "qoder-cn", "workbuddy", "zcode"];
|
|
201
201
|
function createWorkspaceId(input) {
|
|
202
202
|
const basis = input.repoUrl || input.repoRoot || input.projectName || "unknown";
|
|
203
203
|
return `workspace_${fnv1a(basis)}`;
|
|
@@ -349,9 +349,9 @@ function removeVolatileHashFields(value) {
|
|
|
349
349
|
continue;
|
|
350
350
|
}
|
|
351
351
|
if (key === "refs" && isRecord(item)) {
|
|
352
|
-
const
|
|
353
|
-
delete
|
|
354
|
-
result[key] = removeVolatileHashFields(
|
|
352
|
+
const refs2 = { ...item };
|
|
353
|
+
delete refs2.payloadHash;
|
|
354
|
+
result[key] = removeVolatileHashFields(refs2);
|
|
355
355
|
continue;
|
|
356
356
|
}
|
|
357
357
|
result[key] = removeVolatileHashFields(item);
|
|
@@ -1195,7 +1195,7 @@ function countTextLines(text) {
|
|
|
1195
1195
|
}
|
|
1196
1196
|
|
|
1197
1197
|
// src/lib/constants.ts
|
|
1198
|
-
var PACKAGE_VERSION = true ? "0.1.
|
|
1198
|
+
var PACKAGE_VERSION = true ? "0.1.4" : "0.1.1";
|
|
1199
1199
|
var DEFAULT_API_URL = "http://121.196.224.82:3001";
|
|
1200
1200
|
var DEFAULT_BACKFILL_BATCH_SIZE = 50;
|
|
1201
1201
|
var DEFAULT_BACKFILL_BATCH_BYTES = 800 * 1024;
|
|
@@ -1224,13 +1224,13 @@ function isPlainObject(value) {
|
|
|
1224
1224
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1225
1225
|
}
|
|
1226
1226
|
function stringRefs(value) {
|
|
1227
|
-
const
|
|
1227
|
+
const refs2 = {};
|
|
1228
1228
|
for (const [key, item] of Object.entries(value || {})) {
|
|
1229
1229
|
if (typeof item === "string" && item.length > 0) {
|
|
1230
|
-
|
|
1230
|
+
refs2[key] = item;
|
|
1231
1231
|
}
|
|
1232
1232
|
}
|
|
1233
|
-
return
|
|
1233
|
+
return refs2;
|
|
1234
1234
|
}
|
|
1235
1235
|
function stringOption(value) {
|
|
1236
1236
|
if (Array.isArray(value)) {
|
|
@@ -1308,7 +1308,7 @@ function withBackfillRefs(event, context) {
|
|
|
1308
1308
|
event.type,
|
|
1309
1309
|
event.refs?.sourceId
|
|
1310
1310
|
]);
|
|
1311
|
-
const
|
|
1311
|
+
const refs2 = {
|
|
1312
1312
|
...stringRefs(event.refs),
|
|
1313
1313
|
importKey,
|
|
1314
1314
|
sourcePathHash: context.sourcePathHash,
|
|
@@ -1318,12 +1318,12 @@ function withBackfillRefs(event, context) {
|
|
|
1318
1318
|
parserVersion: PACKAGE_VERSION
|
|
1319
1319
|
};
|
|
1320
1320
|
if (context.options.includeSourcePath) {
|
|
1321
|
-
|
|
1321
|
+
refs2.transcriptPath = context.filePath;
|
|
1322
1322
|
}
|
|
1323
1323
|
return {
|
|
1324
1324
|
...event,
|
|
1325
1325
|
id: createStableEventId(importKey),
|
|
1326
|
-
refs
|
|
1326
|
+
refs: refs2
|
|
1327
1327
|
};
|
|
1328
1328
|
}
|
|
1329
1329
|
function matchesBackfillFilters(event, options) {
|
|
@@ -2956,9 +2956,9 @@ async function parseClaudeCoworkSessionFile(filePath, options) {
|
|
|
2956
2956
|
const sessionId = prefixCoworkId(event.sessionId);
|
|
2957
2957
|
const turnId = prefixCoworkId(event.turnId);
|
|
2958
2958
|
const workspaceId = createWorkspaceId({ projectName: project, repoRoot: cwd });
|
|
2959
|
-
const
|
|
2959
|
+
const refs2 = { ...event.refs };
|
|
2960
2960
|
if (metadata.title) {
|
|
2961
|
-
|
|
2961
|
+
refs2.coworkTitleHash = `sha256:${createStableHash(metadata.title)}`;
|
|
2962
2962
|
}
|
|
2963
2963
|
const mapped = {
|
|
2964
2964
|
...event,
|
|
@@ -2970,7 +2970,7 @@ async function parseClaudeCoworkSessionFile(filePath, options) {
|
|
|
2970
2970
|
project,
|
|
2971
2971
|
sessionId,
|
|
2972
2972
|
turnId,
|
|
2973
|
-
refs
|
|
2973
|
+
refs: refs2
|
|
2974
2974
|
};
|
|
2975
2975
|
return rebuildEventIdentity(mapped);
|
|
2976
2976
|
});
|
|
@@ -3507,8 +3507,8 @@ async function codebuddyBackfillFiles(sourceRoot, home, env) {
|
|
|
3507
3507
|
}
|
|
3508
3508
|
const filePath = path8.join(traceDir, entry);
|
|
3509
3509
|
try {
|
|
3510
|
-
const
|
|
3511
|
-
files.push({ path: filePath, modifiedAt:
|
|
3510
|
+
const stat11 = await import("node:fs/promises").then((fs) => fs.stat(filePath));
|
|
3511
|
+
files.push({ path: filePath, modifiedAt: stat11.mtime.toISOString() });
|
|
3512
3512
|
} catch {
|
|
3513
3513
|
}
|
|
3514
3514
|
}
|
|
@@ -4513,19 +4513,19 @@ function opencodeDataCandidates(home, env) {
|
|
|
4513
4513
|
return [primary, path10.join(home, ".opencode", "opencode.db")];
|
|
4514
4514
|
}
|
|
4515
4515
|
async function opencodeBackfillFiles(sourceRoot, home = os4.homedir(), env) {
|
|
4516
|
-
const { stat:
|
|
4516
|
+
const { stat: stat11 } = await import("node:fs/promises");
|
|
4517
4517
|
if (sourceRoot) {
|
|
4518
4518
|
if (!sourceRoot.endsWith(".db")) {
|
|
4519
4519
|
return [];
|
|
4520
4520
|
}
|
|
4521
|
-
const info = await
|
|
4521
|
+
const info = await stat11(sourceRoot).catch(() => null);
|
|
4522
4522
|
if (!info) {
|
|
4523
4523
|
return [];
|
|
4524
4524
|
}
|
|
4525
4525
|
return [{ path: sourceRoot, modifiedAt: info.mtime.toISOString() }];
|
|
4526
4526
|
}
|
|
4527
4527
|
for (const candidatePath of opencodeDataCandidates(home, env)) {
|
|
4528
|
-
const info = await
|
|
4528
|
+
const info = await stat11(candidatePath).catch(() => null);
|
|
4529
4529
|
if (info) {
|
|
4530
4530
|
return [{ path: candidatePath, modifiedAt: info.mtime.toISOString() }];
|
|
4531
4531
|
}
|
|
@@ -6580,6 +6580,758 @@ function normalizeId(id) {
|
|
|
6580
6580
|
return id;
|
|
6581
6581
|
}
|
|
6582
6582
|
|
|
6583
|
+
// src/adapters/workbuddy.ts
|
|
6584
|
+
import { readFile as readFile10, readdir as readdir7, stat as stat7 } from "node:fs/promises";
|
|
6585
|
+
import path14 from "node:path";
|
|
6586
|
+
init_fs();
|
|
6587
|
+
function workbuddyProjectsDir(home, env) {
|
|
6588
|
+
const override = env?.WORKBUDDY_PROJECTS_DIR || env?.WORKBUDDY_HOME;
|
|
6589
|
+
if (override && override.trim()) {
|
|
6590
|
+
return path14.resolve(override, override.endsWith("projects") ? "" : "projects");
|
|
6591
|
+
}
|
|
6592
|
+
return path14.join(home, ".workbuddy", "projects");
|
|
6593
|
+
}
|
|
6594
|
+
function projectFromCwd(cwd, fallback) {
|
|
6595
|
+
if (!cwd) {
|
|
6596
|
+
return fallback;
|
|
6597
|
+
}
|
|
6598
|
+
return path14.basename(cwd) || fallback;
|
|
6599
|
+
}
|
|
6600
|
+
function sourceHash(filePath) {
|
|
6601
|
+
return `sha256:${createStableHash(filePath)}`;
|
|
6602
|
+
}
|
|
6603
|
+
function makeEvent(event, line, filePath, options) {
|
|
6604
|
+
const withRefs = withBackfillRefs(event, {
|
|
6605
|
+
filePath,
|
|
6606
|
+
sourcePathHash: sourceHash(filePath),
|
|
6607
|
+
lineNumber: line.lineNumber,
|
|
6608
|
+
topType: stringField(line.record, "type"),
|
|
6609
|
+
payloadType: stringField(line.record, "role") || stringField(line.record, "name"),
|
|
6610
|
+
options
|
|
6611
|
+
});
|
|
6612
|
+
return validateCanonicalEvent(withRefs).valid ? withRefs : void 0;
|
|
6613
|
+
}
|
|
6614
|
+
function workbuddyUsageMetrics(record) {
|
|
6615
|
+
const providerData = objectField(record, "providerData");
|
|
6616
|
+
const usage = objectField(providerData, "usage");
|
|
6617
|
+
const rawUsage = objectField(providerData, "rawUsage");
|
|
6618
|
+
if (Object.keys(usage).length === 0 && Object.keys(rawUsage).length === 0) {
|
|
6619
|
+
return void 0;
|
|
6620
|
+
}
|
|
6621
|
+
const input = numberField(usage, "inputTokens") ?? numberField(rawUsage, "prompt_tokens") ?? 0;
|
|
6622
|
+
const output = numberField(usage, "outputTokens") ?? numberField(rawUsage, "completion_tokens") ?? 0;
|
|
6623
|
+
const total = numberField(usage, "totalTokens") ?? numberField(rawUsage, "total_tokens") ?? 0;
|
|
6624
|
+
const cacheRead = workbuddyCachedTokens(usage, rawUsage);
|
|
6625
|
+
const cacheCreate = numberField(rawUsage, "cache_creation_input_tokens") ?? numberField(rawUsage, "prompt_cache_write_tokens") ?? 0;
|
|
6626
|
+
const reasoning = workbuddyReasoningTokens(usage, rawUsage);
|
|
6627
|
+
if (!input && !output && !total && !cacheRead && !cacheCreate && !reasoning) {
|
|
6628
|
+
return void 0;
|
|
6629
|
+
}
|
|
6630
|
+
return {
|
|
6631
|
+
tokensInput: input,
|
|
6632
|
+
tokensOutput: output,
|
|
6633
|
+
tokensCachedInput: cacheRead + cacheCreate,
|
|
6634
|
+
tokensCacheCreationInput: cacheCreate,
|
|
6635
|
+
tokensCacheReadInput: cacheRead,
|
|
6636
|
+
tokensReasoningOutput: reasoning,
|
|
6637
|
+
tokensTotal: total || input + output,
|
|
6638
|
+
modelCalls: 1
|
|
6639
|
+
};
|
|
6640
|
+
}
|
|
6641
|
+
function workbuddyCachedTokens(usage, rawUsage) {
|
|
6642
|
+
const rawCache = numberField(rawUsage, "cache_read_input_tokens") ?? numberField(rawUsage, "prompt_cache_hit_tokens") ?? numberField(rawUsage, "cached_tokens");
|
|
6643
|
+
if (rawCache !== void 0) {
|
|
6644
|
+
return rawCache;
|
|
6645
|
+
}
|
|
6646
|
+
const details = usage.inputTokensDetails;
|
|
6647
|
+
if (Array.isArray(details) && isPlainObject(details[0])) {
|
|
6648
|
+
return numberField(details[0], "cached_tokens") || 0;
|
|
6649
|
+
}
|
|
6650
|
+
const rawDetails = objectField(rawUsage, "prompt_tokens_details");
|
|
6651
|
+
return numberField(rawDetails, "cached_tokens") || 0;
|
|
6652
|
+
}
|
|
6653
|
+
function workbuddyReasoningTokens(usage, rawUsage) {
|
|
6654
|
+
const completionDetails = objectField(rawUsage, "completion_tokens_details");
|
|
6655
|
+
const raw = numberField(completionDetails, "reasoning_tokens") ?? numberField(rawUsage, "completion_thinking_tokens");
|
|
6656
|
+
if (raw !== void 0) {
|
|
6657
|
+
return raw;
|
|
6658
|
+
}
|
|
6659
|
+
const details = usage.outputTokensDetails;
|
|
6660
|
+
if (Array.isArray(details) && isPlainObject(details[0])) {
|
|
6661
|
+
return numberField(details[0], "reasoning_tokens") || 0;
|
|
6662
|
+
}
|
|
6663
|
+
return 0;
|
|
6664
|
+
}
|
|
6665
|
+
function contentLength(value) {
|
|
6666
|
+
if (typeof value === "string") {
|
|
6667
|
+
return value.length;
|
|
6668
|
+
}
|
|
6669
|
+
if (Array.isArray(value)) {
|
|
6670
|
+
return value.reduce((total, item) => {
|
|
6671
|
+
if (!isPlainObject(item)) {
|
|
6672
|
+
return total;
|
|
6673
|
+
}
|
|
6674
|
+
return total + (stringField(item, "text") || stringField(item, "input_text") || stringField(item, "output_text") || "").length;
|
|
6675
|
+
}, 0);
|
|
6676
|
+
}
|
|
6677
|
+
return 0;
|
|
6678
|
+
}
|
|
6679
|
+
async function readWorkbuddyLines(filePath) {
|
|
6680
|
+
const text = await readFile10(filePath, "utf8");
|
|
6681
|
+
return text.split(/\r?\n/).map((line, index) => {
|
|
6682
|
+
if (!line.trim()) {
|
|
6683
|
+
return void 0;
|
|
6684
|
+
}
|
|
6685
|
+
try {
|
|
6686
|
+
const record = JSON.parse(line);
|
|
6687
|
+
return isPlainObject(record) ? { lineNumber: index + 1, record } : void 0;
|
|
6688
|
+
} catch {
|
|
6689
|
+
return void 0;
|
|
6690
|
+
}
|
|
6691
|
+
}).filter((line) => Boolean(line));
|
|
6692
|
+
}
|
|
6693
|
+
async function parseWorkbuddySessionFile(filePath, options) {
|
|
6694
|
+
const lines = await readWorkbuddyLines(filePath);
|
|
6695
|
+
if (lines.length === 0) {
|
|
6696
|
+
return [];
|
|
6697
|
+
}
|
|
6698
|
+
const events = [];
|
|
6699
|
+
const first = lines[0].record;
|
|
6700
|
+
const sessionId = stringField(first, "sessionId") || path14.basename(filePath, ".jsonl");
|
|
6701
|
+
const fallbackProject = path14.basename(path14.dirname(filePath));
|
|
6702
|
+
const cwd = lines.map((line) => stringField(line.record, "cwd")).find(Boolean);
|
|
6703
|
+
const project = projectFromCwd(cwd, fallbackProject);
|
|
6704
|
+
const workspaceId = createWorkspaceId({ projectName: project, repoRoot: cwd });
|
|
6705
|
+
const sessionPrefix = `workbuddy:${sessionId}`;
|
|
6706
|
+
const toolStarts = /* @__PURE__ */ new Map();
|
|
6707
|
+
let currentTurnId = `${sessionPrefix}:turn:0`;
|
|
6708
|
+
let turnIndex = 0;
|
|
6709
|
+
const firstTs = lines.map((line) => timestampFrom(numberField(line.record, "timestamp"))).find(Boolean);
|
|
6710
|
+
if (firstTs) {
|
|
6711
|
+
const event = makeEvent({
|
|
6712
|
+
schemaVersion: AGENT_TIME_SCHEMA_VERSION,
|
|
6713
|
+
ts: firstTs,
|
|
6714
|
+
type: "session.started",
|
|
6715
|
+
source: "workbuddy",
|
|
6716
|
+
workspaceId,
|
|
6717
|
+
project,
|
|
6718
|
+
cwd,
|
|
6719
|
+
sessionId: sessionPrefix,
|
|
6720
|
+
agent: "workbuddy",
|
|
6721
|
+
confidence: "derived",
|
|
6722
|
+
refs: { sourceId: `${sessionPrefix}:session` }
|
|
6723
|
+
}, lines[0], filePath, options);
|
|
6724
|
+
if (event) events.push(event);
|
|
6725
|
+
}
|
|
6726
|
+
for (const line of lines) {
|
|
6727
|
+
const record = line.record;
|
|
6728
|
+
const ts = timestampFrom(numberField(record, "timestamp"));
|
|
6729
|
+
if (!ts) {
|
|
6730
|
+
continue;
|
|
6731
|
+
}
|
|
6732
|
+
const type = stringField(record, "type");
|
|
6733
|
+
const sourceId = stringField(record, "id") || `${line.lineNumber}`;
|
|
6734
|
+
if (type === "message" && stringField(record, "role") === "user") {
|
|
6735
|
+
turnIndex += 1;
|
|
6736
|
+
currentTurnId = `${sessionPrefix}:turn:${turnIndex}`;
|
|
6737
|
+
const promptChars = contentLength(record.content);
|
|
6738
|
+
for (const event of [
|
|
6739
|
+
makeEvent({
|
|
6740
|
+
schemaVersion: AGENT_TIME_SCHEMA_VERSION,
|
|
6741
|
+
ts,
|
|
6742
|
+
type: "turn.started",
|
|
6743
|
+
source: "workbuddy",
|
|
6744
|
+
workspaceId,
|
|
6745
|
+
project,
|
|
6746
|
+
cwd,
|
|
6747
|
+
sessionId: sessionPrefix,
|
|
6748
|
+
turnId: currentTurnId,
|
|
6749
|
+
agent: "workbuddy",
|
|
6750
|
+
confidence: "derived",
|
|
6751
|
+
refs: { sourceId: `${sourceId}:turn` }
|
|
6752
|
+
}, line, filePath, options),
|
|
6753
|
+
makeEvent({
|
|
6754
|
+
schemaVersion: AGENT_TIME_SCHEMA_VERSION,
|
|
6755
|
+
ts,
|
|
6756
|
+
type: "prompt.submitted",
|
|
6757
|
+
source: "workbuddy",
|
|
6758
|
+
workspaceId,
|
|
6759
|
+
project,
|
|
6760
|
+
cwd,
|
|
6761
|
+
sessionId: sessionPrefix,
|
|
6762
|
+
turnId: currentTurnId,
|
|
6763
|
+
agent: "workbuddy",
|
|
6764
|
+
metrics: { promptChars, prompts: 1 },
|
|
6765
|
+
confidence: "partial",
|
|
6766
|
+
refs: { sourceId: `${sourceId}:prompt` }
|
|
6767
|
+
}, line, filePath, options)
|
|
6768
|
+
]) {
|
|
6769
|
+
if (event) events.push(event);
|
|
6770
|
+
}
|
|
6771
|
+
continue;
|
|
6772
|
+
}
|
|
6773
|
+
if (type === "function_call") {
|
|
6774
|
+
const tool = stringField(record, "name");
|
|
6775
|
+
const callId = stringField(record, "callId");
|
|
6776
|
+
if (tool && callId) {
|
|
6777
|
+
toolStarts.set(`${sessionId}:${callId}`, { line, ts, tool, callId });
|
|
6778
|
+
const started = makeEvent({
|
|
6779
|
+
schemaVersion: AGENT_TIME_SCHEMA_VERSION,
|
|
6780
|
+
ts,
|
|
6781
|
+
type: "tool.started",
|
|
6782
|
+
source: "workbuddy",
|
|
6783
|
+
workspaceId,
|
|
6784
|
+
project,
|
|
6785
|
+
cwd,
|
|
6786
|
+
sessionId: sessionPrefix,
|
|
6787
|
+
turnId: currentTurnId,
|
|
6788
|
+
spanId: `${sessionPrefix}:tool:${callId}`,
|
|
6789
|
+
agent: "workbuddy",
|
|
6790
|
+
tool,
|
|
6791
|
+
confidence: "exact",
|
|
6792
|
+
refs: { sourceId: `${callId}:start` }
|
|
6793
|
+
}, line, filePath, options);
|
|
6794
|
+
if (started) events.push(started);
|
|
6795
|
+
}
|
|
6796
|
+
const metrics = workbuddyUsageMetrics(record);
|
|
6797
|
+
const model = stringField(objectField(record, "providerData"), "model");
|
|
6798
|
+
if (metrics || model) {
|
|
6799
|
+
const usage = makeEvent({
|
|
6800
|
+
schemaVersion: AGENT_TIME_SCHEMA_VERSION,
|
|
6801
|
+
ts,
|
|
6802
|
+
type: "model.usage",
|
|
6803
|
+
source: "workbuddy",
|
|
6804
|
+
workspaceId,
|
|
6805
|
+
project,
|
|
6806
|
+
cwd,
|
|
6807
|
+
sessionId: sessionPrefix,
|
|
6808
|
+
turnId: currentTurnId,
|
|
6809
|
+
agent: "workbuddy",
|
|
6810
|
+
model,
|
|
6811
|
+
metrics,
|
|
6812
|
+
confidence: metrics ? "partial" : "derived",
|
|
6813
|
+
refs: { sourceId: `${sourceId}:usage:${callId || line.lineNumber}` }
|
|
6814
|
+
}, line, filePath, options);
|
|
6815
|
+
if (usage) events.push(usage);
|
|
6816
|
+
}
|
|
6817
|
+
continue;
|
|
6818
|
+
}
|
|
6819
|
+
if (type === "function_call_result") {
|
|
6820
|
+
const tool = stringField(record, "name") || "tool";
|
|
6821
|
+
const callId = stringField(record, "callId") || `${line.lineNumber}`;
|
|
6822
|
+
const start = toolStarts.get(`${sessionId}:${callId}`);
|
|
6823
|
+
const durationMs = start?.ts ? Math.max(0, Date.parse(ts) - Date.parse(start.ts)) : void 0;
|
|
6824
|
+
const status = stringField(record, "status");
|
|
6825
|
+
const completed = makeEvent({
|
|
6826
|
+
schemaVersion: AGENT_TIME_SCHEMA_VERSION,
|
|
6827
|
+
ts,
|
|
6828
|
+
type: status === "failed" ? "tool.failed" : "tool.completed",
|
|
6829
|
+
source: "workbuddy",
|
|
6830
|
+
workspaceId,
|
|
6831
|
+
project,
|
|
6832
|
+
cwd,
|
|
6833
|
+
sessionId: sessionPrefix,
|
|
6834
|
+
turnId: currentTurnId,
|
|
6835
|
+
spanId: `${sessionPrefix}:tool:${callId}`,
|
|
6836
|
+
agent: "workbuddy",
|
|
6837
|
+
tool,
|
|
6838
|
+
success: status !== "failed",
|
|
6839
|
+
metrics: { toolDurationMs: durationMs, durationMs },
|
|
6840
|
+
confidence: start ? "derived" : "partial",
|
|
6841
|
+
refs: { sourceId: `${callId}:result` }
|
|
6842
|
+
}, line, filePath, options);
|
|
6843
|
+
if (completed) events.push(completed);
|
|
6844
|
+
}
|
|
6845
|
+
}
|
|
6846
|
+
const lastLine = lines.at(-1);
|
|
6847
|
+
const lastTs = lastLine ? timestampFrom(numberField(lastLine.record, "timestamp")) : void 0;
|
|
6848
|
+
if (lastLine && lastTs) {
|
|
6849
|
+
const event = makeEvent({
|
|
6850
|
+
schemaVersion: AGENT_TIME_SCHEMA_VERSION,
|
|
6851
|
+
ts: lastTs,
|
|
6852
|
+
type: "session.ended",
|
|
6853
|
+
source: "workbuddy",
|
|
6854
|
+
workspaceId,
|
|
6855
|
+
project,
|
|
6856
|
+
cwd,
|
|
6857
|
+
sessionId: sessionPrefix,
|
|
6858
|
+
agent: "workbuddy",
|
|
6859
|
+
confidence: "derived",
|
|
6860
|
+
refs: { sourceId: `${sessionPrefix}:ended` }
|
|
6861
|
+
}, lastLine, filePath, options);
|
|
6862
|
+
if (event) events.push(event);
|
|
6863
|
+
}
|
|
6864
|
+
return events;
|
|
6865
|
+
}
|
|
6866
|
+
async function workbuddyBackfillFiles(sourceRoot, home, env) {
|
|
6867
|
+
const base = sourceRoot || workbuddyProjectsDir(home, env);
|
|
6868
|
+
const files = [];
|
|
6869
|
+
try {
|
|
6870
|
+
const projects = await readdir7(base, { withFileTypes: true });
|
|
6871
|
+
for (const project of projects) {
|
|
6872
|
+
if (!project.isDirectory()) {
|
|
6873
|
+
continue;
|
|
6874
|
+
}
|
|
6875
|
+
const projectDir = path14.join(base, project.name);
|
|
6876
|
+
const entries = await readdir7(projectDir, { withFileTypes: true });
|
|
6877
|
+
for (const entry of entries) {
|
|
6878
|
+
if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
6879
|
+
const filePath = path14.join(projectDir, entry.name);
|
|
6880
|
+
const info = await stat7(filePath);
|
|
6881
|
+
files.push({ path: filePath, modifiedAt: info.mtime.toISOString() });
|
|
6882
|
+
}
|
|
6883
|
+
}
|
|
6884
|
+
}
|
|
6885
|
+
} catch {
|
|
6886
|
+
return [];
|
|
6887
|
+
}
|
|
6888
|
+
return files.sort((a, b) => a.path.localeCompare(b.path));
|
|
6889
|
+
}
|
|
6890
|
+
function createWorkbuddyAdapter() {
|
|
6891
|
+
return {
|
|
6892
|
+
id: "workbuddy",
|
|
6893
|
+
label: "WorkBuddy",
|
|
6894
|
+
agentName: "workbuddy",
|
|
6895
|
+
kind: "agent",
|
|
6896
|
+
detectPath(home, env) {
|
|
6897
|
+
return workbuddyProjectsDir(home, env);
|
|
6898
|
+
},
|
|
6899
|
+
installedPath(home, env) {
|
|
6900
|
+
return workbuddyProjectsDir(home, env);
|
|
6901
|
+
},
|
|
6902
|
+
async isInstalled(home, env) {
|
|
6903
|
+
return pathExists(workbuddyProjectsDir(home, env));
|
|
6904
|
+
},
|
|
6905
|
+
installEntries() {
|
|
6906
|
+
return [];
|
|
6907
|
+
},
|
|
6908
|
+
sourcePaths(home, env) {
|
|
6909
|
+
return [workbuddyProjectsDir(home, env)];
|
|
6910
|
+
},
|
|
6911
|
+
parseSessionFile: parseWorkbuddySessionFile
|
|
6912
|
+
};
|
|
6913
|
+
}
|
|
6914
|
+
|
|
6915
|
+
// src/adapters/zcode.ts
|
|
6916
|
+
import { execFile } from "node:child_process";
|
|
6917
|
+
import { stat as stat8 } from "node:fs/promises";
|
|
6918
|
+
import path15 from "node:path";
|
|
6919
|
+
import { promisify as promisify2 } from "node:util";
|
|
6920
|
+
init_fs();
|
|
6921
|
+
var execFileAsync = promisify2(execFile);
|
|
6922
|
+
function zcodeCliDir(home, env) {
|
|
6923
|
+
const override = env?.ZCODE_CLI_DIR || env?.ZCODE_HOME;
|
|
6924
|
+
if (override && override.trim()) {
|
|
6925
|
+
return path15.resolve(override, override.endsWith("cli") ? "" : "cli");
|
|
6926
|
+
}
|
|
6927
|
+
return path15.join(home, ".zcode", "cli");
|
|
6928
|
+
}
|
|
6929
|
+
function zcodeDbPath(home, env) {
|
|
6930
|
+
return path15.join(zcodeCliDir(home, env), "db", "db.sqlite");
|
|
6931
|
+
}
|
|
6932
|
+
function sourceHash2(filePath) {
|
|
6933
|
+
return `sha256:${createStableHash(filePath)}`;
|
|
6934
|
+
}
|
|
6935
|
+
function projectFromDirectory(directory) {
|
|
6936
|
+
return directory ? path15.basename(directory) || "zcode" : "zcode";
|
|
6937
|
+
}
|
|
6938
|
+
function isoFromMs(value) {
|
|
6939
|
+
return timestampFrom(typeof value === "number" ? value : Number(value));
|
|
6940
|
+
}
|
|
6941
|
+
function makeEvent2(event, row, rowNumber, filePath, options) {
|
|
6942
|
+
const withRefs = withBackfillRefs(event, {
|
|
6943
|
+
filePath,
|
|
6944
|
+
sourcePathHash: sourceHash2(filePath),
|
|
6945
|
+
lineNumber: rowNumber,
|
|
6946
|
+
topType: row.kind,
|
|
6947
|
+
payloadType: stringField(row, "status") || stringField(row, "query_source") || stringField(row, "tool_name"),
|
|
6948
|
+
options
|
|
6949
|
+
});
|
|
6950
|
+
return validateCanonicalEvent(withRefs).valid ? withRefs : void 0;
|
|
6951
|
+
}
|
|
6952
|
+
function refs(values) {
|
|
6953
|
+
const out = {};
|
|
6954
|
+
for (const [key, value] of Object.entries(values)) {
|
|
6955
|
+
if (value !== void 0 && value !== "") {
|
|
6956
|
+
out[key] = String(value);
|
|
6957
|
+
}
|
|
6958
|
+
}
|
|
6959
|
+
return out;
|
|
6960
|
+
}
|
|
6961
|
+
async function sqliteJsonRows(dbPath, sql) {
|
|
6962
|
+
try {
|
|
6963
|
+
const { stdout } = await execFileAsync("sqlite3", ["-readonly", dbPath, sql], {
|
|
6964
|
+
maxBuffer: 32 * 1024 * 1024
|
|
6965
|
+
});
|
|
6966
|
+
return stdout.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => {
|
|
6967
|
+
try {
|
|
6968
|
+
const parsed = JSON.parse(line);
|
|
6969
|
+
return isPlainObject(parsed) ? parsed : void 0;
|
|
6970
|
+
} catch {
|
|
6971
|
+
return void 0;
|
|
6972
|
+
}
|
|
6973
|
+
}).filter((row) => Boolean(row));
|
|
6974
|
+
} catch {
|
|
6975
|
+
return [];
|
|
6976
|
+
}
|
|
6977
|
+
}
|
|
6978
|
+
async function readZCodeRows(dbPath) {
|
|
6979
|
+
const queries = [
|
|
6980
|
+
`select json_object(
|
|
6981
|
+
'kind','session',
|
|
6982
|
+
'id',id,
|
|
6983
|
+
'parent_id',parent_id,
|
|
6984
|
+
'directory',directory,
|
|
6985
|
+
'title',title,
|
|
6986
|
+
'task_type',task_type,
|
|
6987
|
+
'time_created',time_created,
|
|
6988
|
+
'time_updated',time_updated
|
|
6989
|
+
) from session order by time_created, id;`,
|
|
6990
|
+
`select json_object(
|
|
6991
|
+
'kind','turn',
|
|
6992
|
+
'session_id',session_id,
|
|
6993
|
+
'turn_id',turn_id,
|
|
6994
|
+
'status',status,
|
|
6995
|
+
'started_at',started_at,
|
|
6996
|
+
'completed_at',completed_at,
|
|
6997
|
+
'duration_ms',duration_ms,
|
|
6998
|
+
'model_request_count',model_request_count,
|
|
6999
|
+
'tool_call_count',tool_call_count,
|
|
7000
|
+
'input_tokens',input_tokens,
|
|
7001
|
+
'output_tokens',output_tokens,
|
|
7002
|
+
'reasoning_tokens',reasoning_tokens,
|
|
7003
|
+
'cache_creation_input_tokens',cache_creation_input_tokens,
|
|
7004
|
+
'cache_read_input_tokens',cache_read_input_tokens,
|
|
7005
|
+
'computed_total_tokens',computed_total_tokens
|
|
7006
|
+
) from turn_usage order by started_at, session_id, turn_id;`,
|
|
7007
|
+
`select json_object(
|
|
7008
|
+
'kind','model',
|
|
7009
|
+
'id',id,
|
|
7010
|
+
'session_id',session_id,
|
|
7011
|
+
'turn_id',turn_id,
|
|
7012
|
+
'query_source',query_source,
|
|
7013
|
+
'provider_id',provider_id,
|
|
7014
|
+
'model_id',model_id,
|
|
7015
|
+
'agent',agent,
|
|
7016
|
+
'mode',mode,
|
|
7017
|
+
'status',status,
|
|
7018
|
+
'started_at',started_at,
|
|
7019
|
+
'completed_at',completed_at,
|
|
7020
|
+
'duration_ms',duration_ms,
|
|
7021
|
+
'input_tokens',input_tokens,
|
|
7022
|
+
'output_tokens',output_tokens,
|
|
7023
|
+
'reasoning_tokens',reasoning_tokens,
|
|
7024
|
+
'cache_creation_input_tokens',cache_creation_input_tokens,
|
|
7025
|
+
'cache_read_input_tokens',cache_read_input_tokens,
|
|
7026
|
+
'computed_total_tokens',computed_total_tokens,
|
|
7027
|
+
'tool_call_count',tool_call_count
|
|
7028
|
+
) from model_usage order by started_at, id;`,
|
|
7029
|
+
`select json_object(
|
|
7030
|
+
'kind','tool',
|
|
7031
|
+
'id',id,
|
|
7032
|
+
'session_id',session_id,
|
|
7033
|
+
'turn_id',turn_id,
|
|
7034
|
+
'tool_call_id',tool_call_id,
|
|
7035
|
+
'tool_name',tool_name,
|
|
7036
|
+
'side_effect_scope',side_effect_scope,
|
|
7037
|
+
'read_only',read_only,
|
|
7038
|
+
'destructive',destructive,
|
|
7039
|
+
'approval_status',approval_status,
|
|
7040
|
+
'status',status,
|
|
7041
|
+
'started_at',started_at,
|
|
7042
|
+
'completed_at',completed_at,
|
|
7043
|
+
'duration_ms',duration_ms,
|
|
7044
|
+
'output_bytes',output_bytes,
|
|
7045
|
+
'exit_code',exit_code
|
|
7046
|
+
) from tool_usage order by started_at, id;`
|
|
7047
|
+
];
|
|
7048
|
+
const results = await Promise.all(queries.map((query) => sqliteJsonRows(dbPath, query)));
|
|
7049
|
+
return results.flat();
|
|
7050
|
+
}
|
|
7051
|
+
function modelMetrics(row) {
|
|
7052
|
+
const input = numberField(row, "input_tokens") || 0;
|
|
7053
|
+
const output = numberField(row, "output_tokens") || 0;
|
|
7054
|
+
const reasoning = numberField(row, "reasoning_tokens") || 0;
|
|
7055
|
+
const cacheCreation = numberField(row, "cache_creation_input_tokens") || 0;
|
|
7056
|
+
const cacheRead = numberField(row, "cache_read_input_tokens") || 0;
|
|
7057
|
+
const total = numberField(row, "computed_total_tokens") || input + output + reasoning;
|
|
7058
|
+
const durationMs = numberField(row, "duration_ms");
|
|
7059
|
+
return {
|
|
7060
|
+
tokensInput: input,
|
|
7061
|
+
tokensOutput: output,
|
|
7062
|
+
tokensReasoningOutput: reasoning,
|
|
7063
|
+
tokensCachedInput: cacheCreation + cacheRead,
|
|
7064
|
+
tokensCacheCreationInput: cacheCreation,
|
|
7065
|
+
tokensCacheReadInput: cacheRead,
|
|
7066
|
+
tokensTotal: total,
|
|
7067
|
+
modelDurationMs: durationMs,
|
|
7068
|
+
durationMs,
|
|
7069
|
+
modelCalls: 1,
|
|
7070
|
+
toolCalls: numberField(row, "tool_call_count")
|
|
7071
|
+
};
|
|
7072
|
+
}
|
|
7073
|
+
function sessionContext(row, sessions) {
|
|
7074
|
+
const sessionId = stringField(row, "session_id") || stringField(row, "id") || "unknown";
|
|
7075
|
+
const session = sessions.get(sessionId);
|
|
7076
|
+
const cwd = stringField(session, "directory");
|
|
7077
|
+
const project = projectFromDirectory(cwd);
|
|
7078
|
+
return {
|
|
7079
|
+
rawSessionId: sessionId,
|
|
7080
|
+
sessionId: `zcode:${sessionId}`,
|
|
7081
|
+
cwd,
|
|
7082
|
+
project,
|
|
7083
|
+
workspaceId: createWorkspaceId({ projectName: project, repoRoot: cwd }),
|
|
7084
|
+
parentSessionId: stringField(session, "parent_id") ? `zcode:${stringField(session, "parent_id")}` : void 0
|
|
7085
|
+
};
|
|
7086
|
+
}
|
|
7087
|
+
async function parseZCodeDb(filePath, options) {
|
|
7088
|
+
const rows = await readZCodeRows(filePath);
|
|
7089
|
+
if (rows.length === 0) {
|
|
7090
|
+
return [];
|
|
7091
|
+
}
|
|
7092
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
7093
|
+
for (const row of rows) {
|
|
7094
|
+
if (row.kind === "session") {
|
|
7095
|
+
const id = stringField(row, "id");
|
|
7096
|
+
if (id) {
|
|
7097
|
+
sessions.set(id, row);
|
|
7098
|
+
}
|
|
7099
|
+
}
|
|
7100
|
+
}
|
|
7101
|
+
const events = [];
|
|
7102
|
+
rows.forEach((row, index) => {
|
|
7103
|
+
const rowNumber = index + 1;
|
|
7104
|
+
if (row.kind === "session") {
|
|
7105
|
+
const id = stringField(row, "id");
|
|
7106
|
+
const ts = isoFromMs(numberField(row, "time_created"));
|
|
7107
|
+
if (!id || !ts) {
|
|
7108
|
+
return;
|
|
7109
|
+
}
|
|
7110
|
+
const ctx = sessionContext(row, sessions);
|
|
7111
|
+
const event = makeEvent2({
|
|
7112
|
+
schemaVersion: AGENT_TIME_SCHEMA_VERSION,
|
|
7113
|
+
ts,
|
|
7114
|
+
type: "session.started",
|
|
7115
|
+
source: "zcode",
|
|
7116
|
+
workspaceId: ctx.workspaceId,
|
|
7117
|
+
project: ctx.project,
|
|
7118
|
+
cwd: ctx.cwd,
|
|
7119
|
+
sessionId: ctx.sessionId,
|
|
7120
|
+
parentAgentInstanceId: ctx.parentSessionId,
|
|
7121
|
+
agent: "zcode",
|
|
7122
|
+
confidence: "exact",
|
|
7123
|
+
refs: refs({
|
|
7124
|
+
sourceId: `${id}:session`,
|
|
7125
|
+
zcodeTaskType: stringField(row, "task_type"),
|
|
7126
|
+
zcodeTitleHash: stringField(row, "title") ? `sha256:${createStableHash(stringField(row, "title"))}` : void 0
|
|
7127
|
+
})
|
|
7128
|
+
}, row, rowNumber, filePath, options);
|
|
7129
|
+
if (event) events.push(event);
|
|
7130
|
+
const completedTs = isoFromMs(numberField(row, "time_updated"));
|
|
7131
|
+
if (completedTs && completedTs !== ts) {
|
|
7132
|
+
const completed = makeEvent2({
|
|
7133
|
+
schemaVersion: AGENT_TIME_SCHEMA_VERSION,
|
|
7134
|
+
ts: completedTs,
|
|
7135
|
+
type: "session.ended",
|
|
7136
|
+
source: "zcode",
|
|
7137
|
+
workspaceId: ctx.workspaceId,
|
|
7138
|
+
project: ctx.project,
|
|
7139
|
+
cwd: ctx.cwd,
|
|
7140
|
+
sessionId: ctx.sessionId,
|
|
7141
|
+
parentAgentInstanceId: ctx.parentSessionId,
|
|
7142
|
+
agent: "zcode",
|
|
7143
|
+
confidence: "exact",
|
|
7144
|
+
refs: { sourceId: `${id}:session:ended` }
|
|
7145
|
+
}, row, rowNumber, filePath, options);
|
|
7146
|
+
if (completed) events.push(completed);
|
|
7147
|
+
}
|
|
7148
|
+
return;
|
|
7149
|
+
}
|
|
7150
|
+
if (row.kind === "turn") {
|
|
7151
|
+
const rawTurnId = stringField(row, "turn_id");
|
|
7152
|
+
const ts = isoFromMs(numberField(row, "started_at"));
|
|
7153
|
+
if (!rawTurnId || !ts) {
|
|
7154
|
+
return;
|
|
7155
|
+
}
|
|
7156
|
+
const ctx = sessionContext(row, sessions);
|
|
7157
|
+
const turnId = `zcode:${rawTurnId}`;
|
|
7158
|
+
const started = makeEvent2({
|
|
7159
|
+
schemaVersion: AGENT_TIME_SCHEMA_VERSION,
|
|
7160
|
+
ts,
|
|
7161
|
+
type: "turn.started",
|
|
7162
|
+
source: "zcode",
|
|
7163
|
+
workspaceId: ctx.workspaceId,
|
|
7164
|
+
project: ctx.project,
|
|
7165
|
+
cwd: ctx.cwd,
|
|
7166
|
+
sessionId: ctx.sessionId,
|
|
7167
|
+
turnId,
|
|
7168
|
+
agent: "zcode",
|
|
7169
|
+
confidence: "exact",
|
|
7170
|
+
refs: { sourceId: `${rawTurnId}:start` }
|
|
7171
|
+
}, row, rowNumber, filePath, options);
|
|
7172
|
+
if (started) events.push(started);
|
|
7173
|
+
const completedTs = isoFromMs(numberField(row, "completed_at"));
|
|
7174
|
+
if (completedTs) {
|
|
7175
|
+
const status = stringField(row, "status");
|
|
7176
|
+
const completed = makeEvent2({
|
|
7177
|
+
schemaVersion: AGENT_TIME_SCHEMA_VERSION,
|
|
7178
|
+
ts: completedTs,
|
|
7179
|
+
type: "turn.completed",
|
|
7180
|
+
source: "zcode",
|
|
7181
|
+
workspaceId: ctx.workspaceId,
|
|
7182
|
+
project: ctx.project,
|
|
7183
|
+
cwd: ctx.cwd,
|
|
7184
|
+
sessionId: ctx.sessionId,
|
|
7185
|
+
turnId,
|
|
7186
|
+
agent: "zcode",
|
|
7187
|
+
success: status === "completed",
|
|
7188
|
+
metrics: {
|
|
7189
|
+
durationMs: numberField(row, "duration_ms"),
|
|
7190
|
+
turns: 1,
|
|
7191
|
+
modelCalls: numberField(row, "model_request_count"),
|
|
7192
|
+
toolCalls: numberField(row, "tool_call_count")
|
|
7193
|
+
},
|
|
7194
|
+
confidence: "exact",
|
|
7195
|
+
refs: refs({ sourceId: `${rawTurnId}:completed`, zcodeStatus: status })
|
|
7196
|
+
}, row, rowNumber, filePath, options);
|
|
7197
|
+
if (completed) events.push(completed);
|
|
7198
|
+
}
|
|
7199
|
+
return;
|
|
7200
|
+
}
|
|
7201
|
+
if (row.kind === "model") {
|
|
7202
|
+
const ts = isoFromMs(numberField(row, "completed_at")) || isoFromMs(numberField(row, "started_at"));
|
|
7203
|
+
if (!ts) {
|
|
7204
|
+
return;
|
|
7205
|
+
}
|
|
7206
|
+
const ctx = sessionContext(row, sessions);
|
|
7207
|
+
const modelId = stringField(row, "model_id");
|
|
7208
|
+
const usage = makeEvent2({
|
|
7209
|
+
schemaVersion: AGENT_TIME_SCHEMA_VERSION,
|
|
7210
|
+
ts,
|
|
7211
|
+
type: "model.usage",
|
|
7212
|
+
source: "zcode",
|
|
7213
|
+
workspaceId: ctx.workspaceId,
|
|
7214
|
+
project: ctx.project,
|
|
7215
|
+
cwd: ctx.cwd,
|
|
7216
|
+
sessionId: ctx.sessionId,
|
|
7217
|
+
turnId: stringField(row, "turn_id") ? `zcode:${stringField(row, "turn_id")}` : void 0,
|
|
7218
|
+
agent: stringField(row, "agent") || "zcode",
|
|
7219
|
+
provider: stringField(row, "provider_id"),
|
|
7220
|
+
model: modelId,
|
|
7221
|
+
success: stringField(row, "status") === "completed",
|
|
7222
|
+
metrics: modelMetrics(row),
|
|
7223
|
+
confidence: "exact",
|
|
7224
|
+
refs: refs({
|
|
7225
|
+
sourceId: stringField(row, "id") || `${ctx.rawSessionId}:model:${rowNumber}`,
|
|
7226
|
+
zcodeQuerySource: stringField(row, "query_source"),
|
|
7227
|
+
zcodeMode: stringField(row, "mode"),
|
|
7228
|
+
zcodeStatus: stringField(row, "status")
|
|
7229
|
+
})
|
|
7230
|
+
}, row, rowNumber, filePath, options);
|
|
7231
|
+
if (usage) events.push(usage);
|
|
7232
|
+
return;
|
|
7233
|
+
}
|
|
7234
|
+
if (row.kind === "tool") {
|
|
7235
|
+
const startedTs = isoFromMs(numberField(row, "started_at"));
|
|
7236
|
+
const completedTs = isoFromMs(numberField(row, "completed_at")) || startedTs;
|
|
7237
|
+
const tool = stringField(row, "tool_name") || "tool";
|
|
7238
|
+
const toolCallId = stringField(row, "tool_call_id") || stringField(row, "id") || `${rowNumber}`;
|
|
7239
|
+
if (!startedTs) {
|
|
7240
|
+
return;
|
|
7241
|
+
}
|
|
7242
|
+
const ctx = sessionContext(row, sessions);
|
|
7243
|
+
const turnId = stringField(row, "turn_id") ? `zcode:${stringField(row, "turn_id")}` : void 0;
|
|
7244
|
+
const spanId = `${ctx.sessionId}:tool:${toolCallId}`;
|
|
7245
|
+
const started = makeEvent2({
|
|
7246
|
+
schemaVersion: AGENT_TIME_SCHEMA_VERSION,
|
|
7247
|
+
ts: startedTs,
|
|
7248
|
+
type: "tool.started",
|
|
7249
|
+
source: "zcode",
|
|
7250
|
+
workspaceId: ctx.workspaceId,
|
|
7251
|
+
project: ctx.project,
|
|
7252
|
+
cwd: ctx.cwd,
|
|
7253
|
+
sessionId: ctx.sessionId,
|
|
7254
|
+
turnId,
|
|
7255
|
+
spanId,
|
|
7256
|
+
agent: "zcode",
|
|
7257
|
+
tool,
|
|
7258
|
+
confidence: "exact",
|
|
7259
|
+
refs: refs({
|
|
7260
|
+
sourceId: `${toolCallId}:start`,
|
|
7261
|
+
zcodeSideEffectScope: stringField(row, "side_effect_scope"),
|
|
7262
|
+
zcodeApprovalStatus: stringField(row, "approval_status")
|
|
7263
|
+
})
|
|
7264
|
+
}, row, rowNumber, filePath, options);
|
|
7265
|
+
if (started) events.push(started);
|
|
7266
|
+
if (completedTs) {
|
|
7267
|
+
const status = stringField(row, "status");
|
|
7268
|
+
const completed = makeEvent2({
|
|
7269
|
+
schemaVersion: AGENT_TIME_SCHEMA_VERSION,
|
|
7270
|
+
ts: completedTs,
|
|
7271
|
+
type: status === "completed" ? "tool.completed" : "tool.failed",
|
|
7272
|
+
source: "zcode",
|
|
7273
|
+
workspaceId: ctx.workspaceId,
|
|
7274
|
+
project: ctx.project,
|
|
7275
|
+
cwd: ctx.cwd,
|
|
7276
|
+
sessionId: ctx.sessionId,
|
|
7277
|
+
turnId,
|
|
7278
|
+
spanId,
|
|
7279
|
+
agent: "zcode",
|
|
7280
|
+
tool,
|
|
7281
|
+
success: status === "completed",
|
|
7282
|
+
metrics: {
|
|
7283
|
+
toolDurationMs: numberField(row, "duration_ms"),
|
|
7284
|
+
durationMs: numberField(row, "duration_ms"),
|
|
7285
|
+
bytesRead: numberField(row, "output_bytes")
|
|
7286
|
+
},
|
|
7287
|
+
confidence: "exact",
|
|
7288
|
+
refs: refs({
|
|
7289
|
+
sourceId: `${toolCallId}:completed`,
|
|
7290
|
+
zcodeStatus: status,
|
|
7291
|
+
zcodeExitCode: numberField(row, "exit_code")
|
|
7292
|
+
})
|
|
7293
|
+
}, row, rowNumber, filePath, options);
|
|
7294
|
+
if (completed) events.push(completed);
|
|
7295
|
+
}
|
|
7296
|
+
}
|
|
7297
|
+
});
|
|
7298
|
+
return events.sort((a, b) => a.ts.localeCompare(b.ts) || (a.id || "").localeCompare(b.id || ""));
|
|
7299
|
+
}
|
|
7300
|
+
async function zcodeBackfillFiles(sourceRoot, home, env) {
|
|
7301
|
+
const candidate = sourceRoot || zcodeDbPath(home, env);
|
|
7302
|
+
const filePath = candidate.endsWith(".sqlite") ? candidate : path15.join(candidate, "db", "db.sqlite");
|
|
7303
|
+
try {
|
|
7304
|
+
const info = await stat8(filePath);
|
|
7305
|
+
return [{ path: filePath, modifiedAt: info.mtime.toISOString() }];
|
|
7306
|
+
} catch {
|
|
7307
|
+
return [];
|
|
7308
|
+
}
|
|
7309
|
+
}
|
|
7310
|
+
function createZCodeAdapter() {
|
|
7311
|
+
return {
|
|
7312
|
+
id: "zcode",
|
|
7313
|
+
label: "ZCode",
|
|
7314
|
+
agentName: "zcode",
|
|
7315
|
+
kind: "agent",
|
|
7316
|
+
detectPath(home, env) {
|
|
7317
|
+
return zcodeCliDir(home, env);
|
|
7318
|
+
},
|
|
7319
|
+
installedPath(home, env) {
|
|
7320
|
+
return zcodeDbPath(home, env);
|
|
7321
|
+
},
|
|
7322
|
+
async isInstalled(home, env) {
|
|
7323
|
+
return pathExists(zcodeDbPath(home, env));
|
|
7324
|
+
},
|
|
7325
|
+
installEntries() {
|
|
7326
|
+
return [];
|
|
7327
|
+
},
|
|
7328
|
+
sourcePaths(home, env) {
|
|
7329
|
+
return [zcodeDbPath(home, env)];
|
|
7330
|
+
},
|
|
7331
|
+
parseSessionFile: parseZCodeDb
|
|
7332
|
+
};
|
|
7333
|
+
}
|
|
7334
|
+
|
|
6583
7335
|
// src/lib/pricing.ts
|
|
6584
7336
|
function estimateEventCostUsd(event) {
|
|
6585
7337
|
if (event.type !== "model.usage") {
|
|
@@ -7016,15 +7768,15 @@ function hookCommandFromGroup(group) {
|
|
|
7016
7768
|
import { randomUUID } from "node:crypto";
|
|
7017
7769
|
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
7018
7770
|
import { homedir, hostname } from "node:os";
|
|
7019
|
-
import
|
|
7771
|
+
import path16 from "node:path";
|
|
7020
7772
|
function configDir(home = homedir()) {
|
|
7021
|
-
return
|
|
7773
|
+
return path16.join(home, ".vibetime");
|
|
7022
7774
|
}
|
|
7023
7775
|
function configPath(home = homedir()) {
|
|
7024
|
-
return
|
|
7776
|
+
return path16.join(configDir(home), "config.json");
|
|
7025
7777
|
}
|
|
7026
7778
|
function machineIdPath(home = homedir()) {
|
|
7027
|
-
return
|
|
7779
|
+
return path16.join(configDir(home), "machine-id");
|
|
7028
7780
|
}
|
|
7029
7781
|
function readConfig(home = homedir()) {
|
|
7030
7782
|
const file = configPath(home);
|
|
@@ -7071,15 +7823,15 @@ function defaultMachineName() {
|
|
|
7071
7823
|
init_fs();
|
|
7072
7824
|
|
|
7073
7825
|
// src/lib/logger.ts
|
|
7074
|
-
import { appendFile, mkdir as mkdir3, rename, stat as
|
|
7826
|
+
import { appendFile, mkdir as mkdir3, rename, stat as stat9 } from "node:fs/promises";
|
|
7075
7827
|
import { homedir as homedir2 } from "node:os";
|
|
7076
|
-
import
|
|
7828
|
+
import path17 from "node:path";
|
|
7077
7829
|
var MAX_BYTES = 1 * 1024 * 1024;
|
|
7078
7830
|
function logDir(home = homedir2()) {
|
|
7079
|
-
return
|
|
7831
|
+
return path17.join(home, ".vibetime", "logs");
|
|
7080
7832
|
}
|
|
7081
7833
|
function logPath(home = homedir2(), name = "cli.log") {
|
|
7082
|
-
return
|
|
7834
|
+
return path17.join(logDir(home), name);
|
|
7083
7835
|
}
|
|
7084
7836
|
function serializeError(error) {
|
|
7085
7837
|
if (error instanceof Error) {
|
|
@@ -7089,7 +7841,7 @@ function serializeError(error) {
|
|
|
7089
7841
|
}
|
|
7090
7842
|
async function rotateIfNeeded(file) {
|
|
7091
7843
|
try {
|
|
7092
|
-
const info = await
|
|
7844
|
+
const info = await stat9(file);
|
|
7093
7845
|
if (info.size > MAX_BYTES) {
|
|
7094
7846
|
await rename(file, `${file}.1`).catch(() => {
|
|
7095
7847
|
});
|
|
@@ -7254,8 +8006,8 @@ function buildHeaders(token, machine) {
|
|
|
7254
8006
|
...machine?.platform ? { "x-machine-platform": machine.platform } : {}
|
|
7255
8007
|
};
|
|
7256
8008
|
}
|
|
7257
|
-
function joinUrl(base,
|
|
7258
|
-
return new URL(
|
|
8009
|
+
function joinUrl(base, path19) {
|
|
8010
|
+
return new URL(path19, base.endsWith("/") ? base : `${base}/`).toString();
|
|
7259
8011
|
}
|
|
7260
8012
|
async function postRollupBatch(remote, rollups, options = {}) {
|
|
7261
8013
|
const response = await remote.fetchImpl(joinUrl(remote.baseUrl, "/v3/agent/ingest"), {
|
|
@@ -7320,7 +8072,7 @@ async function deleteMachine(remote, id) {
|
|
|
7320
8072
|
}
|
|
7321
8073
|
|
|
7322
8074
|
// src/lib/types.ts
|
|
7323
|
-
var BACKFILL_STATE_SCHEMA_VERSION =
|
|
8075
|
+
var BACKFILL_STATE_SCHEMA_VERSION = 5;
|
|
7324
8076
|
|
|
7325
8077
|
// src/cli.ts
|
|
7326
8078
|
function createRegistry() {
|
|
@@ -7334,6 +8086,8 @@ function createRegistry() {
|
|
|
7334
8086
|
registry.register(createCodebuddyAdapter());
|
|
7335
8087
|
registry.register(createQoderAdapter());
|
|
7336
8088
|
registry.register(createQoderCnAdapter());
|
|
8089
|
+
registry.register(createWorkbuddyAdapter());
|
|
8090
|
+
registry.register(createZCodeAdapter());
|
|
7337
8091
|
return registry;
|
|
7338
8092
|
}
|
|
7339
8093
|
var defaultContext = {
|
|
@@ -7745,11 +8499,17 @@ async function listBackfillSourceFiles(source, options, ctx) {
|
|
|
7745
8499
|
if (source.id === "codebuddy") {
|
|
7746
8500
|
return codebuddyBackfillFiles(stringOption(options["source-root"]), resolveHome2(options, ctx), ctx.env);
|
|
7747
8501
|
}
|
|
8502
|
+
if (source.id === "workbuddy") {
|
|
8503
|
+
return workbuddyBackfillFiles(stringOption(options["source-root"]), resolveHome2(options, ctx), ctx.env);
|
|
8504
|
+
}
|
|
8505
|
+
if (source.id === "zcode") {
|
|
8506
|
+
return zcodeBackfillFiles(stringOption(options["source-root"]), resolveHome2(options, ctx), ctx.env);
|
|
8507
|
+
}
|
|
7748
8508
|
const roots = stringOption(options["source-root"]) ? [requiredOption(options, "source-root")] : source.paths;
|
|
7749
8509
|
const fileLists = await Promise.all(roots.map((r) => listJsonlFiles(r)));
|
|
7750
8510
|
const files = fileLists.flat().sort().slice(0, numberOption(options.limit) || void 0);
|
|
7751
8511
|
return Promise.all(files.map(async (filePath) => {
|
|
7752
|
-
const info = await
|
|
8512
|
+
const info = await stat10(filePath);
|
|
7753
8513
|
return { path: filePath, modifiedAt: info.mtime.toISOString() };
|
|
7754
8514
|
}));
|
|
7755
8515
|
}
|
|
@@ -7833,10 +8593,12 @@ async function importBackfillPlan(plan, options, ctx, registry) {
|
|
|
7833
8593
|
return 1;
|
|
7834
8594
|
}
|
|
7835
8595
|
const sourceDefs = registry.all().filter((a) => supportedSources.has(a.id) && (source === "all" || a.id === source)).map((a) => ({ id: a.id, label: a.label, paths: a.sourcePaths(home, ctx.env) }));
|
|
8596
|
+
const remote = resolveRemoteFromOptions(options, ctx);
|
|
8597
|
+
const remoteKey = backfillRemoteKey(remote?.baseUrl ?? DEFAULT_API_URL);
|
|
7836
8598
|
if (options.force) {
|
|
7837
|
-
await purgeForcedSources(sourceDefs, home, options, ctx);
|
|
8599
|
+
await purgeForcedSources(sourceDefs, home, remoteKey, options, ctx);
|
|
7838
8600
|
}
|
|
7839
|
-
const incrementalState = shouldUseIncrementalBackfill(options) ? await readBackfillIncrementalState(home, ctx) : void 0;
|
|
8601
|
+
const incrementalState = shouldUseIncrementalBackfill(options) ? await readBackfillIncrementalState(home, remoteKey, ctx) : void 0;
|
|
7840
8602
|
if (!options.json) {
|
|
7841
8603
|
write(ctx.stdout, `importRun ${plan.importRun.importRunId}
|
|
7842
8604
|
`);
|
|
@@ -7864,13 +8626,15 @@ async function importBackfillPlan(plan, options, ctx, registry) {
|
|
|
7864
8626
|
`);
|
|
7865
8627
|
}
|
|
7866
8628
|
if (counts.failed === 0 && counts.conflicts === 0 && incrementalState) {
|
|
7867
|
-
await updateBackfillIncrementalState(home, incrementalState, selectedFilesBySource);
|
|
8629
|
+
await updateBackfillIncrementalState(home, remoteKey, incrementalState, selectedFilesBySource);
|
|
7868
8630
|
}
|
|
7869
8631
|
return counts.failed > 0 || counts.conflicts > 0 && !options["skip-conflicts"] ? 1 : 0;
|
|
7870
8632
|
}
|
|
7871
|
-
async function purgeForcedSources(sourceDefs, home, options, ctx) {
|
|
8633
|
+
async function purgeForcedSources(sourceDefs, home, remoteKey, options, ctx) {
|
|
7872
8634
|
try {
|
|
7873
|
-
await
|
|
8635
|
+
const file = await readBackfillIncrementalStateFile(home);
|
|
8636
|
+
delete file.remotes[remoteKey];
|
|
8637
|
+
await writeBackfillIncrementalStateFile(home, file);
|
|
7874
8638
|
} catch (error) {
|
|
7875
8639
|
debug(ctx, `Failed to clear backfill watermark: ${error.message}
|
|
7876
8640
|
`);
|
|
@@ -8058,44 +8822,83 @@ function selectBackfillFilesForImport(files, watermarkTs) {
|
|
|
8058
8822
|
});
|
|
8059
8823
|
}
|
|
8060
8824
|
function backfillIncrementalStatePath(home) {
|
|
8061
|
-
return
|
|
8825
|
+
return path18.join(home, ".vibetime", "backfill-state.json");
|
|
8062
8826
|
}
|
|
8063
8827
|
function syncLocalTriggerStatePath(home) {
|
|
8064
|
-
return
|
|
8828
|
+
return path18.join(home, ".vibetime", "sync-local-trigger.json");
|
|
8065
8829
|
}
|
|
8066
8830
|
function syncLocalTriggerLockPath(home) {
|
|
8067
|
-
return
|
|
8831
|
+
return path18.join(home, ".vibetime", "sync-local-trigger.lock");
|
|
8832
|
+
}
|
|
8833
|
+
function backfillRemoteKey(baseUrl) {
|
|
8834
|
+
try {
|
|
8835
|
+
const url = new URL(baseUrl);
|
|
8836
|
+
return `${url.protocol}//${url.host}${url.pathname.replace(/\/+$/, "")}`;
|
|
8837
|
+
} catch {
|
|
8838
|
+
return baseUrl.replace(/\/+$/, "");
|
|
8839
|
+
}
|
|
8840
|
+
}
|
|
8841
|
+
function sanitizeBackfillSources(raw) {
|
|
8842
|
+
const sources = {};
|
|
8843
|
+
if (!isPlainObject(raw)) {
|
|
8844
|
+
return sources;
|
|
8845
|
+
}
|
|
8846
|
+
for (const source of BACKFILL_SOURCE_IDS) {
|
|
8847
|
+
const item = raw[source];
|
|
8848
|
+
if (isPlainObject(item) && typeof item.watermarkTs === "string" && !Number.isNaN(Date.parse(item.watermarkTs))) {
|
|
8849
|
+
sources[source] = { watermarkTs: item.watermarkTs };
|
|
8850
|
+
}
|
|
8851
|
+
}
|
|
8852
|
+
return sources;
|
|
8068
8853
|
}
|
|
8069
|
-
async function
|
|
8854
|
+
async function readBackfillIncrementalStateFile(home, ctx) {
|
|
8855
|
+
const empty = { version: BACKFILL_STATE_SCHEMA_VERSION, remotes: {} };
|
|
8070
8856
|
const statePath = backfillIncrementalStatePath(home);
|
|
8071
8857
|
const state = await readJsonIfExists(statePath);
|
|
8072
8858
|
if (state === null) {
|
|
8073
|
-
return
|
|
8859
|
+
return empty;
|
|
8074
8860
|
}
|
|
8075
|
-
if (!isPlainObject(state)
|
|
8861
|
+
if (!isPlainObject(state)) {
|
|
8076
8862
|
if (ctx) {
|
|
8077
8863
|
debug(ctx, `backfill-state malformed at ${statePath}; ignoring watermarks
|
|
8078
8864
|
`);
|
|
8079
8865
|
}
|
|
8080
|
-
return
|
|
8866
|
+
return empty;
|
|
8081
8867
|
}
|
|
8082
8868
|
if (state.version !== BACKFILL_STATE_SCHEMA_VERSION) {
|
|
8083
8869
|
if (ctx) {
|
|
8084
8870
|
debug(ctx, `backfill-state version ${String(state.version)} at ${statePath} differs from current v${BACKFILL_STATE_SCHEMA_VERSION}; dropping watermarks so the next sync re-imports under the new parser
|
|
8085
8871
|
`);
|
|
8086
8872
|
}
|
|
8087
|
-
return
|
|
8873
|
+
return empty;
|
|
8088
8874
|
}
|
|
8089
|
-
|
|
8090
|
-
|
|
8091
|
-
|
|
8092
|
-
|
|
8093
|
-
sources[source] = { watermarkTs: item.watermarkTs };
|
|
8875
|
+
if (!isPlainObject(state.remotes)) {
|
|
8876
|
+
if (ctx) {
|
|
8877
|
+
debug(ctx, `backfill-state malformed at ${statePath}; ignoring watermarks
|
|
8878
|
+
`);
|
|
8094
8879
|
}
|
|
8880
|
+
return empty;
|
|
8095
8881
|
}
|
|
8096
|
-
|
|
8882
|
+
const remotes = {};
|
|
8883
|
+
for (const [remoteKey, entry] of Object.entries(state.remotes)) {
|
|
8884
|
+
if (!isPlainObject(entry)) {
|
|
8885
|
+
continue;
|
|
8886
|
+
}
|
|
8887
|
+
remotes[remoteKey] = { sources: sanitizeBackfillSources(entry.sources) };
|
|
8888
|
+
}
|
|
8889
|
+
return { version: BACKFILL_STATE_SCHEMA_VERSION, remotes };
|
|
8890
|
+
}
|
|
8891
|
+
async function writeBackfillIncrementalStateFile(home, file) {
|
|
8892
|
+
const statePath = backfillIncrementalStatePath(home);
|
|
8893
|
+
await mkdir4(path18.dirname(statePath), { recursive: true });
|
|
8894
|
+
await writeFile3(statePath, `${JSON.stringify(file, null, 2)}
|
|
8895
|
+
`, "utf8");
|
|
8896
|
+
}
|
|
8897
|
+
async function readBackfillIncrementalState(home, remoteKey, ctx) {
|
|
8898
|
+
const file = await readBackfillIncrementalStateFile(home, ctx);
|
|
8899
|
+
return { version: BACKFILL_STATE_SCHEMA_VERSION, sources: file.remotes[remoteKey]?.sources ?? {} };
|
|
8097
8900
|
}
|
|
8098
|
-
async function updateBackfillIncrementalState(home, state, selectedFilesBySource) {
|
|
8901
|
+
async function updateBackfillIncrementalState(home, remoteKey, state, selectedFilesBySource) {
|
|
8099
8902
|
for (const [source, files] of selectedFilesBySource.entries()) {
|
|
8100
8903
|
const latest = maxTimestamp(files.map((f) => f.modifiedAt));
|
|
8101
8904
|
if (!latest) {
|
|
@@ -8103,10 +8906,9 @@ async function updateBackfillIncrementalState(home, state, selectedFilesBySource
|
|
|
8103
8906
|
}
|
|
8104
8907
|
state.sources[source] = { watermarkTs: latest };
|
|
8105
8908
|
}
|
|
8106
|
-
const
|
|
8107
|
-
|
|
8108
|
-
await
|
|
8109
|
-
`, "utf8");
|
|
8909
|
+
const file = await readBackfillIncrementalStateFile(home);
|
|
8910
|
+
file.remotes[remoteKey] = { sources: state.sources };
|
|
8911
|
+
await writeBackfillIncrementalStateFile(home, file);
|
|
8110
8912
|
}
|
|
8111
8913
|
async function readSyncLocalTriggerState(statePath) {
|
|
8112
8914
|
let state;
|
|
@@ -8137,7 +8939,7 @@ async function readSyncLocalTriggerState(statePath) {
|
|
|
8137
8939
|
return nextState;
|
|
8138
8940
|
}
|
|
8139
8941
|
async function writeSyncLocalTriggerState(statePath, state) {
|
|
8140
|
-
await mkdir4(
|
|
8942
|
+
await mkdir4(path18.dirname(statePath), { recursive: true });
|
|
8141
8943
|
const tmpPath = `${statePath}.tmp`;
|
|
8142
8944
|
await writeFile3(tmpPath, `${JSON.stringify(state, null, 2)}
|
|
8143
8945
|
`, "utf8");
|
|
@@ -8154,7 +8956,7 @@ async function readSyncLocalLock(lockPath) {
|
|
|
8154
8956
|
return { pid: lock.pid, startedAt: lock.startedAt };
|
|
8155
8957
|
}
|
|
8156
8958
|
async function writeSyncLocalLock(lockPath, lock) {
|
|
8157
|
-
await mkdir4(
|
|
8959
|
+
await mkdir4(path18.dirname(lockPath), { recursive: true });
|
|
8158
8960
|
await writeFile3(lockPath, `${JSON.stringify(lock, null, 2)}
|
|
8159
8961
|
`, "utf8");
|
|
8160
8962
|
}
|
|
@@ -8214,10 +9016,10 @@ function syncLocalRunnerEntryArgs(cliPath) {
|
|
|
8214
9016
|
if (cliPath.endsWith(".ts")) {
|
|
8215
9017
|
return ["--import", "tsx", cliPath];
|
|
8216
9018
|
}
|
|
8217
|
-
return [
|
|
9019
|
+
return [path18.resolve(path18.dirname(cliPath), "../bin/vibetime.mjs")];
|
|
8218
9020
|
}
|
|
8219
9021
|
function resolveHome2(options, ctx) {
|
|
8220
|
-
return
|
|
9022
|
+
return path18.resolve(stringOption(options.home) || ctx.env.HOME || os7.homedir());
|
|
8221
9023
|
}
|
|
8222
9024
|
function requestedTargets(options) {
|
|
8223
9025
|
const value = options.target || options.targets;
|
package/package.json
CHANGED