@yhong91/vibetime 0.1.55 → 0.1.57
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 +1656 -331
- package/package.json +2 -2
package/bin/vibetime.mjs
CHANGED
|
@@ -884,9 +884,9 @@ var init_esm = __esm({
|
|
|
884
884
|
|
|
885
885
|
// src/cli.ts
|
|
886
886
|
import { spawn as spawn2, spawnSync } from "node:child_process";
|
|
887
|
-
import { mkdir as mkdir5, open, rm, stat as
|
|
888
|
-
import
|
|
889
|
-
import
|
|
887
|
+
import { mkdir as mkdir5, open, rm, stat as stat15, writeFile as writeFile4 } from "node:fs/promises";
|
|
888
|
+
import os13 from "node:os";
|
|
889
|
+
import path26 from "node:path";
|
|
890
890
|
import { fileURLToPath } from "node:url";
|
|
891
891
|
|
|
892
892
|
// ../shared/src/index.ts
|
|
@@ -924,7 +924,7 @@ var TELEMETRY_EVENT_TYPES = [
|
|
|
924
924
|
"agent.operation"
|
|
925
925
|
];
|
|
926
926
|
var FILE_ACTIVITY_OPERATIONS = ["read", "search", "create", "write", "edit", "delete"];
|
|
927
|
-
var BACKFILL_SOURCE_IDS = ["codex", "claude-code", "claude-cowork", "copilot", "opencode", "pi", "agy", "codebuddy", "qoder", "qoder-cn", "workbuddy", "zcode", "grok-build", "zed", "kimi-code"];
|
|
927
|
+
var BACKFILL_SOURCE_IDS = ["codex", "claude-code", "claude-cowork", "copilot", "opencode", "pi", "agy", "codebuddy", "qoder", "qoder-cn", "workbuddy", "zcode", "grok-build", "zed", "kimi-code", "cursor"];
|
|
928
928
|
function createWorkspaceId(input) {
|
|
929
929
|
const basis = input.repoUrl || input.repoRoot || input.projectName || "unknown";
|
|
930
930
|
return `workspace_${fnv1a(basis)}`;
|
|
@@ -2047,7 +2047,7 @@ function claudeStyleFileMetrics(tool, input) {
|
|
|
2047
2047
|
}
|
|
2048
2048
|
|
|
2049
2049
|
// src/lib/constants.ts
|
|
2050
|
-
var PACKAGE_VERSION = true ? "0.1.
|
|
2050
|
+
var PACKAGE_VERSION = true ? "0.1.57" : "0.1.1";
|
|
2051
2051
|
var GENERATED_MARKER = "Generated by vibetime.";
|
|
2052
2052
|
var DEFAULT_API_URL = "http://121.196.224.82:3001";
|
|
2053
2053
|
var DEFAULT_BACKFILL_BATCH_SIZE = 50;
|
|
@@ -5011,8 +5011,8 @@ async function codebuddyBackfillFiles(sourceRoot, home, env) {
|
|
|
5011
5011
|
}
|
|
5012
5012
|
const filePath = path9.join(traceDir, entry);
|
|
5013
5013
|
try {
|
|
5014
|
-
const
|
|
5015
|
-
files.push({ path: filePath, modifiedAt:
|
|
5014
|
+
const stat16 = await import("node:fs/promises").then((fs) => fs.stat(filePath));
|
|
5015
|
+
files.push({ path: filePath, modifiedAt: stat16.mtime.toISOString(), groupId: pidDir.name });
|
|
5016
5016
|
} catch {
|
|
5017
5017
|
}
|
|
5018
5018
|
}
|
|
@@ -6177,86 +6177,1292 @@ async function copilotBackfillFiles(sourceRoot, home = os5.homedir(), _env) {
|
|
|
6177
6177
|
} catch {
|
|
6178
6178
|
return [];
|
|
6179
6179
|
}
|
|
6180
|
-
for (const entry of entries) {
|
|
6181
|
-
if (entry.startsWith("pending-session")) {
|
|
6180
|
+
for (const entry of entries) {
|
|
6181
|
+
if (entry.startsWith("pending-session")) {
|
|
6182
|
+
continue;
|
|
6183
|
+
}
|
|
6184
|
+
const eventsPath = path12.join(sessionDir, entry, "events.jsonl");
|
|
6185
|
+
const info = await stat6(eventsPath).catch(() => null);
|
|
6186
|
+
if (info) {
|
|
6187
|
+
results.push({ path: eventsPath, modifiedAt: info.mtime.toISOString() });
|
|
6188
|
+
}
|
|
6189
|
+
}
|
|
6190
|
+
return results;
|
|
6191
|
+
}
|
|
6192
|
+
function copilotPluginContent() {
|
|
6193
|
+
return `// Agent Time plugin for GitHub Copilot CLI
|
|
6194
|
+
// Generated by vibetime.
|
|
6195
|
+
// Copilot does not support hooks \u2014 this file is a placeholder for detection.
|
|
6196
|
+
// Backfill reads session data from ~/.copilot/session-state/*/events.jsonl.
|
|
6197
|
+
`;
|
|
6198
|
+
}
|
|
6199
|
+
function copilotHome(home, env) {
|
|
6200
|
+
const override = env?.COPILOT_HOME;
|
|
6201
|
+
if (override && override.trim()) {
|
|
6202
|
+
return path12.resolve(override);
|
|
6203
|
+
}
|
|
6204
|
+
return path12.join(home, ".copilot");
|
|
6205
|
+
}
|
|
6206
|
+
function createCopilotAdapter() {
|
|
6207
|
+
return {
|
|
6208
|
+
id: "copilot",
|
|
6209
|
+
label: "GitHub Copilot",
|
|
6210
|
+
agentName: "copilot",
|
|
6211
|
+
kind: "agent",
|
|
6212
|
+
detectPath(home, env) {
|
|
6213
|
+
return copilotHome(home, env);
|
|
6214
|
+
},
|
|
6215
|
+
installedPath(home, env) {
|
|
6216
|
+
return path12.join(copilotHome(home, env), ".vibetime");
|
|
6217
|
+
},
|
|
6218
|
+
async isInstalled(home, env) {
|
|
6219
|
+
try {
|
|
6220
|
+
const { pathExists: pathExists2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
6221
|
+
return await pathExists2(path12.join(copilotHome(home, env), ".vibetime"));
|
|
6222
|
+
} catch {
|
|
6223
|
+
return false;
|
|
6224
|
+
}
|
|
6225
|
+
},
|
|
6226
|
+
installEntries(home, env) {
|
|
6227
|
+
return [{
|
|
6228
|
+
kind: "file",
|
|
6229
|
+
path: path12.join(copilotHome(home, env), ".vibetime"),
|
|
6230
|
+
content: copilotPluginContent()
|
|
6231
|
+
}];
|
|
6232
|
+
},
|
|
6233
|
+
sourcePaths(home, env) {
|
|
6234
|
+
return [path12.join(copilotHome(home, env), "session-state")];
|
|
6235
|
+
},
|
|
6236
|
+
parseSessionFile: parseCopilotSessionFile
|
|
6237
|
+
};
|
|
6238
|
+
}
|
|
6239
|
+
|
|
6240
|
+
// src/adapters/cursor.ts
|
|
6241
|
+
import { copyFile, readdir as readdir6, readFile as readFile8, stat as stat7 } from "node:fs/promises";
|
|
6242
|
+
import os6 from "node:os";
|
|
6243
|
+
import path13 from "node:path";
|
|
6244
|
+
|
|
6245
|
+
// src/lib/cursor-hooks.ts
|
|
6246
|
+
function isCursorHooksFile(value) {
|
|
6247
|
+
if (!isPlainObject(value)) {
|
|
6248
|
+
return false;
|
|
6249
|
+
}
|
|
6250
|
+
if (value.version !== 1 && value.version !== void 0) {
|
|
6251
|
+
return false;
|
|
6252
|
+
}
|
|
6253
|
+
return isPlainObject(value.hooks);
|
|
6254
|
+
}
|
|
6255
|
+
function hookCommandFromCursorEntry(entry) {
|
|
6256
|
+
if (!isPlainObject(entry) || typeof entry.command !== "string" || !entry.command) {
|
|
6257
|
+
return void 0;
|
|
6258
|
+
}
|
|
6259
|
+
return entry.command;
|
|
6260
|
+
}
|
|
6261
|
+
function hasCursorHookCommand(config, command) {
|
|
6262
|
+
if (!isCursorHooksFile(config)) {
|
|
6263
|
+
return false;
|
|
6264
|
+
}
|
|
6265
|
+
return Object.values(config.hooks).some(
|
|
6266
|
+
(entries) => Array.isArray(entries) && entries.some((entry) => hookCommandFromCursorEntry(entry) === command)
|
|
6267
|
+
);
|
|
6268
|
+
}
|
|
6269
|
+
function mergeCursorHooksJson(existing, addition) {
|
|
6270
|
+
const mergedHooks = {};
|
|
6271
|
+
const existingHooks = isCursorHooksFile(existing) ? existing.hooks : {};
|
|
6272
|
+
for (const [event, entries] of Object.entries(existingHooks)) {
|
|
6273
|
+
if (Array.isArray(entries)) {
|
|
6274
|
+
mergedHooks[event] = [...entries];
|
|
6275
|
+
}
|
|
6276
|
+
}
|
|
6277
|
+
for (const [event, entries] of Object.entries(addition.hooks)) {
|
|
6278
|
+
const current = mergedHooks[event] ? [...mergedHooks[event]] : [];
|
|
6279
|
+
for (const entry of entries) {
|
|
6280
|
+
const command = hookCommandFromCursorEntry(entry);
|
|
6281
|
+
if (!command) {
|
|
6282
|
+
continue;
|
|
6283
|
+
}
|
|
6284
|
+
const alreadyPresent = current.some(
|
|
6285
|
+
(existingEntry) => hookCommandFromCursorEntry(existingEntry) === command
|
|
6286
|
+
);
|
|
6287
|
+
if (!alreadyPresent) {
|
|
6288
|
+
current.push(entry);
|
|
6289
|
+
}
|
|
6290
|
+
}
|
|
6291
|
+
mergedHooks[event] = current;
|
|
6292
|
+
}
|
|
6293
|
+
const extra = isPlainObject(existing) ? Object.fromEntries(
|
|
6294
|
+
Object.entries(existing).filter(([key]) => key !== "version" && key !== "hooks")
|
|
6295
|
+
) : {};
|
|
6296
|
+
return {
|
|
6297
|
+
...extra,
|
|
6298
|
+
version: 1,
|
|
6299
|
+
hooks: mergedHooks
|
|
6300
|
+
};
|
|
6301
|
+
}
|
|
6302
|
+
function stripCursorHookCommands(existing, commands) {
|
|
6303
|
+
const next = structuredClone(isPlainObject(existing) ? existing : {});
|
|
6304
|
+
if (!isPlainObject(next.hooks)) {
|
|
6305
|
+
return { next, changed: false };
|
|
6306
|
+
}
|
|
6307
|
+
const hooks = { ...next.hooks };
|
|
6308
|
+
let changed = false;
|
|
6309
|
+
for (const [event, entries] of Object.entries(hooks)) {
|
|
6310
|
+
if (!Array.isArray(entries)) {
|
|
6311
|
+
continue;
|
|
6312
|
+
}
|
|
6313
|
+
const stripped = entries.filter((entry) => {
|
|
6314
|
+
const command = hookCommandFromCursorEntry(entry);
|
|
6315
|
+
return !command || !commands.has(command);
|
|
6316
|
+
});
|
|
6317
|
+
if (stripped.length !== entries.length) {
|
|
6318
|
+
changed = true;
|
|
6319
|
+
}
|
|
6320
|
+
if (stripped.length === 0) {
|
|
6321
|
+
delete hooks[event];
|
|
6322
|
+
} else {
|
|
6323
|
+
hooks[event] = stripped;
|
|
6324
|
+
}
|
|
6325
|
+
}
|
|
6326
|
+
if (Object.keys(hooks).length === 0) {
|
|
6327
|
+
delete next.hooks;
|
|
6328
|
+
changed = true;
|
|
6329
|
+
} else {
|
|
6330
|
+
next.hooks = hooks;
|
|
6331
|
+
}
|
|
6332
|
+
return { next, changed };
|
|
6333
|
+
}
|
|
6334
|
+
function collectCursorHookCommands(content) {
|
|
6335
|
+
const commands = /* @__PURE__ */ new Set();
|
|
6336
|
+
for (const entries of Object.values(content.hooks)) {
|
|
6337
|
+
for (const entry of entries) {
|
|
6338
|
+
const command = hookCommandFromCursorEntry(entry);
|
|
6339
|
+
if (command) {
|
|
6340
|
+
commands.add(command);
|
|
6341
|
+
}
|
|
6342
|
+
}
|
|
6343
|
+
}
|
|
6344
|
+
return [...commands];
|
|
6345
|
+
}
|
|
6346
|
+
|
|
6347
|
+
// src/adapters/cursor.ts
|
|
6348
|
+
init_fs();
|
|
6349
|
+
var SOURCE_ID = "cursor";
|
|
6350
|
+
var AGENT_NAME = "cursor";
|
|
6351
|
+
var HOOK_COMMAND = `vibetime hook --agent ${SOURCE_ID}`;
|
|
6352
|
+
var HOOK_TIMEOUT_SECONDS = 10;
|
|
6353
|
+
var BUBBLE_USER = 1;
|
|
6354
|
+
var CAPABILITY_TOOL = 15;
|
|
6355
|
+
var CURSOR_HOOK_EVENTS = [
|
|
6356
|
+
"sessionStart",
|
|
6357
|
+
"sessionEnd",
|
|
6358
|
+
"beforeSubmitPrompt",
|
|
6359
|
+
"preToolUse",
|
|
6360
|
+
"postToolUse",
|
|
6361
|
+
"postToolUseFailure",
|
|
6362
|
+
"subagentStart",
|
|
6363
|
+
"subagentStop",
|
|
6364
|
+
"stop",
|
|
6365
|
+
"afterFileEdit",
|
|
6366
|
+
"afterShellExecution",
|
|
6367
|
+
"preCompact"
|
|
6368
|
+
];
|
|
6369
|
+
function cursorHome(home, env) {
|
|
6370
|
+
const override = env?.CURSOR_HOME;
|
|
6371
|
+
if (override && override.trim()) {
|
|
6372
|
+
return path13.resolve(override);
|
|
6373
|
+
}
|
|
6374
|
+
return path13.join(home, ".cursor");
|
|
6375
|
+
}
|
|
6376
|
+
function cursorHooksPath(home, env) {
|
|
6377
|
+
return path13.join(cursorHome(home, env), "hooks.json");
|
|
6378
|
+
}
|
|
6379
|
+
function cursorProjectsDir(home, env) {
|
|
6380
|
+
return path13.join(cursorHome(home, env), "projects");
|
|
6381
|
+
}
|
|
6382
|
+
function cursorChatsDir(home, env) {
|
|
6383
|
+
return path13.join(cursorHome(home, env), "chats");
|
|
6384
|
+
}
|
|
6385
|
+
function cursorUserDir(home, env) {
|
|
6386
|
+
const override = env?.CURSOR_USER_DIR;
|
|
6387
|
+
if (override && override.trim()) {
|
|
6388
|
+
return path13.resolve(override);
|
|
6389
|
+
}
|
|
6390
|
+
return void 0;
|
|
6391
|
+
}
|
|
6392
|
+
function cursorStateDbCandidates(home, env) {
|
|
6393
|
+
const candidates = [];
|
|
6394
|
+
const userDir = cursorUserDir(home, env);
|
|
6395
|
+
if (userDir) {
|
|
6396
|
+
candidates.push(path13.join(userDir, "globalStorage", "state.vscdb"));
|
|
6397
|
+
}
|
|
6398
|
+
const platform2 = process.platform;
|
|
6399
|
+
if (platform2 === "darwin") {
|
|
6400
|
+
candidates.push(path13.join(home, "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb"));
|
|
6401
|
+
} else if (platform2 === "win32") {
|
|
6402
|
+
const appdata = env?.APPDATA;
|
|
6403
|
+
if (appdata && appdata.trim()) {
|
|
6404
|
+
candidates.push(path13.join(path13.resolve(appdata), "Cursor", "User", "globalStorage", "state.vscdb"));
|
|
6405
|
+
}
|
|
6406
|
+
candidates.push(path13.join(home, "AppData", "Roaming", "Cursor", "User", "globalStorage", "state.vscdb"));
|
|
6407
|
+
} else {
|
|
6408
|
+
const xdgConfig = env?.XDG_CONFIG_HOME;
|
|
6409
|
+
if (xdgConfig && xdgConfig.trim()) {
|
|
6410
|
+
candidates.push(path13.join(path13.resolve(xdgConfig), "Cursor", "User", "globalStorage", "state.vscdb"));
|
|
6411
|
+
}
|
|
6412
|
+
candidates.push(path13.join(home, ".config", "Cursor", "User", "globalStorage", "state.vscdb"));
|
|
6413
|
+
}
|
|
6414
|
+
return candidates;
|
|
6415
|
+
}
|
|
6416
|
+
function decodeKv(value) {
|
|
6417
|
+
if (typeof value === "string") {
|
|
6418
|
+
return value;
|
|
6419
|
+
}
|
|
6420
|
+
if (value instanceof Uint8Array) {
|
|
6421
|
+
return new TextDecoder().decode(value);
|
|
6422
|
+
}
|
|
6423
|
+
return void 0;
|
|
6424
|
+
}
|
|
6425
|
+
function parseJsonObject(text) {
|
|
6426
|
+
if (!text) {
|
|
6427
|
+
return void 0;
|
|
6428
|
+
}
|
|
6429
|
+
try {
|
|
6430
|
+
const parsed = JSON.parse(text);
|
|
6431
|
+
return isPlainObject(parsed) ? parsed : void 0;
|
|
6432
|
+
} catch {
|
|
6433
|
+
return void 0;
|
|
6434
|
+
}
|
|
6435
|
+
}
|
|
6436
|
+
function parseMaybeJsonObject(value) {
|
|
6437
|
+
if (isPlainObject(value)) {
|
|
6438
|
+
return value;
|
|
6439
|
+
}
|
|
6440
|
+
if (typeof value === "string") {
|
|
6441
|
+
return parseJsonObject(value) || {};
|
|
6442
|
+
}
|
|
6443
|
+
return {};
|
|
6444
|
+
}
|
|
6445
|
+
function cleanId(value) {
|
|
6446
|
+
return value.replace(/\s+/g, "_").slice(0, 200);
|
|
6447
|
+
}
|
|
6448
|
+
async function openCursorDb(dbPath) {
|
|
6449
|
+
const { DatabaseSync } = await import("node:sqlite");
|
|
6450
|
+
try {
|
|
6451
|
+
return {
|
|
6452
|
+
db: new DatabaseSync(dbPath, { readOnly: true }),
|
|
6453
|
+
cleanup: async () => {
|
|
6454
|
+
}
|
|
6455
|
+
};
|
|
6456
|
+
} catch {
|
|
6457
|
+
const tmp = path13.join(os6.tmpdir(), `vibetime-cursor-${createStableHash(dbPath).slice(0, 12)}.db`);
|
|
6458
|
+
await copyFile(dbPath, tmp);
|
|
6459
|
+
for (const suffix of ["-wal", "-shm"]) {
|
|
6460
|
+
await copyFile(`${dbPath}${suffix}`, `${tmp}${suffix}`).catch(() => void 0);
|
|
6461
|
+
}
|
|
6462
|
+
return {
|
|
6463
|
+
db: new DatabaseSync(tmp, { readOnly: true }),
|
|
6464
|
+
cleanup: async () => {
|
|
6465
|
+
const { unlink } = await import("node:fs/promises");
|
|
6466
|
+
await unlink(tmp).catch(() => void 0);
|
|
6467
|
+
await unlink(`${tmp}-wal`).catch(() => void 0);
|
|
6468
|
+
await unlink(`${tmp}-shm`).catch(() => void 0);
|
|
6469
|
+
}
|
|
6470
|
+
};
|
|
6471
|
+
}
|
|
6472
|
+
}
|
|
6473
|
+
function listComposers(db) {
|
|
6474
|
+
const dataById = /* @__PURE__ */ new Map();
|
|
6475
|
+
const dataRows = db.prepare(
|
|
6476
|
+
"SELECT key, value FROM cursorDiskKV WHERE key LIKE 'composerData:%'"
|
|
6477
|
+
).all();
|
|
6478
|
+
for (const row of dataRows) {
|
|
6479
|
+
const id = row.key.slice("composerData:".length);
|
|
6480
|
+
const data = parseJsonObject(decodeKv(row.value));
|
|
6481
|
+
if (id && data) {
|
|
6482
|
+
dataById.set(id, data);
|
|
6483
|
+
}
|
|
6484
|
+
}
|
|
6485
|
+
let headers = [];
|
|
6486
|
+
try {
|
|
6487
|
+
headers = db.prepare(
|
|
6488
|
+
"SELECT composerId, createdAt, lastUpdatedAt, isSubagent, value FROM composerHeaders"
|
|
6489
|
+
).all();
|
|
6490
|
+
} catch {
|
|
6491
|
+
headers = [...dataById.entries()].map(([composerId, data]) => ({
|
|
6492
|
+
composerId,
|
|
6493
|
+
createdAt: numberField(data, "createdAt"),
|
|
6494
|
+
lastUpdatedAt: numberField(data, "lastUpdatedAt"),
|
|
6495
|
+
isSubagent: data.isBestOfNSubcomposer || data.isSubagent ? 1 : 0,
|
|
6496
|
+
value: JSON.stringify(data)
|
|
6497
|
+
}));
|
|
6498
|
+
}
|
|
6499
|
+
const composers = [];
|
|
6500
|
+
for (const headerRow of headers) {
|
|
6501
|
+
const composerId = headerRow.composerId;
|
|
6502
|
+
if (!composerId || composerId === "empty-state-draft") {
|
|
6503
|
+
continue;
|
|
6504
|
+
}
|
|
6505
|
+
const header = parseJsonObject(decodeKv(headerRow.value)) || parseMaybeJsonObject(headerRow.value);
|
|
6506
|
+
const data = dataById.get(composerId) || header;
|
|
6507
|
+
if (data.isDraft === true || data.isEphemeral === true || header.isDraft === true || header.isEphemeral === true) {
|
|
6508
|
+
continue;
|
|
6509
|
+
}
|
|
6510
|
+
composers.push({
|
|
6511
|
+
composerId,
|
|
6512
|
+
createdAt: headerRow.createdAt ?? numberField(data, "createdAt") ?? numberField(header, "createdAt"),
|
|
6513
|
+
lastUpdatedAt: headerRow.lastUpdatedAt ?? numberField(data, "lastUpdatedAt") ?? numberField(header, "lastUpdatedAt"),
|
|
6514
|
+
isSubagent: Boolean(headerRow.isSubagent) || data.isBestOfNSubcomposer === true,
|
|
6515
|
+
header,
|
|
6516
|
+
data
|
|
6517
|
+
});
|
|
6518
|
+
}
|
|
6519
|
+
return composers;
|
|
6520
|
+
}
|
|
6521
|
+
function loadBubbles(db, composerId) {
|
|
6522
|
+
const rows = db.prepare(
|
|
6523
|
+
"SELECT key, value FROM cursorDiskKV WHERE key LIKE ?"
|
|
6524
|
+
).all(`bubbleId:${composerId}:%`);
|
|
6525
|
+
const bubbles = [];
|
|
6526
|
+
for (const row of rows) {
|
|
6527
|
+
const bubble = parseJsonObject(decodeKv(row.value));
|
|
6528
|
+
if (bubble) {
|
|
6529
|
+
bubbles.push(bubble);
|
|
6530
|
+
}
|
|
6531
|
+
}
|
|
6532
|
+
bubbles.sort((a, b) => {
|
|
6533
|
+
const aTs = stringField(a, "createdAt") || "";
|
|
6534
|
+
const bTs = stringField(b, "createdAt") || "";
|
|
6535
|
+
return aTs.localeCompare(bTs);
|
|
6536
|
+
});
|
|
6537
|
+
return bubbles;
|
|
6538
|
+
}
|
|
6539
|
+
function workspaceFromComposer(data) {
|
|
6540
|
+
const ident = objectField(data, "workspaceIdentifier");
|
|
6541
|
+
const uri = objectField(ident, "uri");
|
|
6542
|
+
const cwd = stringField(uri, "fsPath") || stringField(uri, "path");
|
|
6543
|
+
return {
|
|
6544
|
+
cwd,
|
|
6545
|
+
project: cwd ? path13.basename(cwd) : void 0
|
|
6546
|
+
};
|
|
6547
|
+
}
|
|
6548
|
+
function modelFromComposer(data) {
|
|
6549
|
+
const config = objectField(data, "modelConfig");
|
|
6550
|
+
return stringField(config, "modelName") || stringField(config, "modelId");
|
|
6551
|
+
}
|
|
6552
|
+
function bubbleTs(bubble, fallback) {
|
|
6553
|
+
return timestampFrom(bubble.createdAt) || stringField(bubble, "createdAt") || fallback;
|
|
6554
|
+
}
|
|
6555
|
+
function toolNameOf(raw) {
|
|
6556
|
+
if (!raw) {
|
|
6557
|
+
return "unknown";
|
|
6558
|
+
}
|
|
6559
|
+
return raw.replace(/_v\d+$/i, "");
|
|
6560
|
+
}
|
|
6561
|
+
function isShellTool(name) {
|
|
6562
|
+
const lower = name.toLowerCase();
|
|
6563
|
+
return lower === "shell" || lower === "bash" || lower === "run_terminal_cmd" || lower === "run_terminal_command";
|
|
6564
|
+
}
|
|
6565
|
+
function fileActivitiesFromCursorTool(toolName, input, ts, cwd) {
|
|
6566
|
+
const lower = toolName.toLowerCase();
|
|
6567
|
+
if (isShellTool(toolName)) {
|
|
6568
|
+
const command = stringField(input, "command") || stringField(input, "cmd");
|
|
6569
|
+
if (command) {
|
|
6570
|
+
return fileActivitiesFromShellCommand(command, ts, cwd, cwd);
|
|
6571
|
+
}
|
|
6572
|
+
return [];
|
|
6573
|
+
}
|
|
6574
|
+
const target = stringField(input, "path") || stringField(input, "targetFile") || stringField(input, "target_file") || stringField(input, "file_path") || stringField(input, "filePath");
|
|
6575
|
+
const display = target ? displayFilePath(target, cwd) : void 0;
|
|
6576
|
+
if (lower === "read" || lower === "read_file" || lower === "readfile") {
|
|
6577
|
+
if (!display) {
|
|
6578
|
+
return [];
|
|
6579
|
+
}
|
|
6580
|
+
return [{ ts, path: display, operation: "read", confidence: "exact" }];
|
|
6581
|
+
}
|
|
6582
|
+
if (lower === "grep" || lower === "grep_search" || lower === "ripgrep" || lower === "glob" || lower === "glob_file_search" || lower === "globfilesearch") {
|
|
6583
|
+
if (!display) {
|
|
6584
|
+
return [];
|
|
6585
|
+
}
|
|
6586
|
+
return [{ ts, path: display, operation: "search", confidence: "exact" }];
|
|
6587
|
+
}
|
|
6588
|
+
if (lower === "delete" || lower === "delete_file") {
|
|
6589
|
+
if (!display) {
|
|
6590
|
+
return [];
|
|
6591
|
+
}
|
|
6592
|
+
return [{ ts, path: display, operation: "delete", confidence: "exact" }];
|
|
6593
|
+
}
|
|
6594
|
+
if (lower === "write" || lower === "write_file") {
|
|
6595
|
+
if (!display) {
|
|
6596
|
+
return [];
|
|
6597
|
+
}
|
|
6598
|
+
const content = stringField(input, "contents") || stringField(input, "content");
|
|
6599
|
+
return [{
|
|
6600
|
+
ts,
|
|
6601
|
+
path: display,
|
|
6602
|
+
operation: "write",
|
|
6603
|
+
confidence: "exact",
|
|
6604
|
+
linesAdded: countTextLines(content),
|
|
6605
|
+
charsWritten: content?.length
|
|
6606
|
+
}];
|
|
6607
|
+
}
|
|
6608
|
+
if (lower === "search_replace" || lower === "strreplace" || lower === "edit" || lower === "edit_file" || lower === "apply_patch" || lower === "applypatch") {
|
|
6609
|
+
if (!display) {
|
|
6610
|
+
return [];
|
|
6611
|
+
}
|
|
6612
|
+
const oldString = stringField(input, "old_string") || stringField(input, "oldString") || stringField(input, "old_str");
|
|
6613
|
+
const newString = stringField(input, "new_string") || stringField(input, "newString") || stringField(input, "new_str") || stringField(input, "contents") || stringField(input, "content");
|
|
6614
|
+
return [{
|
|
6615
|
+
ts,
|
|
6616
|
+
path: display,
|
|
6617
|
+
operation: "edit",
|
|
6618
|
+
confidence: "exact",
|
|
6619
|
+
linesAdded: countTextLines(newString),
|
|
6620
|
+
linesRemoved: countTextLines(oldString),
|
|
6621
|
+
charsWritten: newString?.length
|
|
6622
|
+
}];
|
|
6623
|
+
}
|
|
6624
|
+
return [];
|
|
6625
|
+
}
|
|
6626
|
+
function usageMetrics(tokenCount) {
|
|
6627
|
+
const inputTokens = numberField(tokenCount, "inputTokens") || 0;
|
|
6628
|
+
const outputTokens = numberField(tokenCount, "outputTokens") || 0;
|
|
6629
|
+
const cached = numberField(tokenCount, "cacheReadTokens") || numberField(tokenCount, "cachedInputTokens") || 0;
|
|
6630
|
+
const cacheCreation = numberField(tokenCount, "cacheWriteTokens") || numberField(tokenCount, "cacheCreationTokens") || 0;
|
|
6631
|
+
if (inputTokens <= 0 && outputTokens <= 0 && cached <= 0 && cacheCreation <= 0) {
|
|
6632
|
+
return void 0;
|
|
6633
|
+
}
|
|
6634
|
+
return {
|
|
6635
|
+
tokensInput: inputTokens || void 0,
|
|
6636
|
+
tokensOutput: outputTokens || void 0,
|
|
6637
|
+
tokensCachedInput: cached + cacheCreation || void 0,
|
|
6638
|
+
tokensCacheReadInput: cached || void 0,
|
|
6639
|
+
tokensCacheCreationInput: cacheCreation || void 0,
|
|
6640
|
+
tokensTotal: inputTokens + outputTokens || void 0,
|
|
6641
|
+
modelCalls: 1
|
|
6642
|
+
};
|
|
6643
|
+
}
|
|
6644
|
+
var CURSOR_TIMESTAMP_RE = /<timestamp>\s*([^<]+?)\s*<\/timestamp>/i;
|
|
6645
|
+
var CURSOR_USER_QUERY_RE = /<user_query>\s*([\s\S]*?)\s*<\/user_query>/i;
|
|
6646
|
+
var chatMetaIndexCache = /* @__PURE__ */ new Map();
|
|
6647
|
+
function isCursorTranscriptPath(filePath) {
|
|
6648
|
+
return filePath.split(path13.sep).includes("agent-transcripts") && filePath.endsWith(".jsonl");
|
|
6649
|
+
}
|
|
6650
|
+
function cursorHomeFromTranscript(filePath) {
|
|
6651
|
+
const parts = filePath.split(path13.sep);
|
|
6652
|
+
const at = parts.lastIndexOf("agent-transcripts");
|
|
6653
|
+
if (at >= 2 && parts[at - 2] === "projects") {
|
|
6654
|
+
return parts.slice(0, at - 2).join(path13.sep);
|
|
6655
|
+
}
|
|
6656
|
+
return void 0;
|
|
6657
|
+
}
|
|
6658
|
+
function parseCursorClock(raw) {
|
|
6659
|
+
const trimmed = raw.trim();
|
|
6660
|
+
const direct = timestampFrom(trimmed);
|
|
6661
|
+
if (direct) {
|
|
6662
|
+
return direct;
|
|
6663
|
+
}
|
|
6664
|
+
const withoutWeekday = trimmed.replace(/^[A-Za-z]{3,9},\s*/, "");
|
|
6665
|
+
const normalized = withoutWeekday.replace(/\((UTC[+-]\d{1,2}(?::\d{2})?)\)/i, "$1");
|
|
6666
|
+
for (const candidate of [normalized, withoutWeekday, trimmed]) {
|
|
6667
|
+
const parsed = Date.parse(candidate);
|
|
6668
|
+
if (!Number.isNaN(parsed)) {
|
|
6669
|
+
return new Date(parsed).toISOString();
|
|
6670
|
+
}
|
|
6671
|
+
}
|
|
6672
|
+
return void 0;
|
|
6673
|
+
}
|
|
6674
|
+
function latestTimestamp(...values) {
|
|
6675
|
+
let best;
|
|
6676
|
+
for (const value of values) {
|
|
6677
|
+
if (!value || Number.isNaN(Date.parse(value))) {
|
|
6678
|
+
continue;
|
|
6679
|
+
}
|
|
6680
|
+
if (!best || value > best) {
|
|
6681
|
+
best = value;
|
|
6682
|
+
}
|
|
6683
|
+
}
|
|
6684
|
+
return best;
|
|
6685
|
+
}
|
|
6686
|
+
function timestampBetween(start, end, index, count) {
|
|
6687
|
+
if (count <= 0 || index < 0) {
|
|
6688
|
+
return start;
|
|
6689
|
+
}
|
|
6690
|
+
const startMs = Date.parse(start);
|
|
6691
|
+
const endMs = Date.parse(end);
|
|
6692
|
+
if (!Number.isFinite(startMs) || !Number.isFinite(endMs) || endMs <= startMs) {
|
|
6693
|
+
return start;
|
|
6694
|
+
}
|
|
6695
|
+
return new Date(startMs + Math.round((endMs - startMs) * (index + 1) / (count + 1))).toISOString();
|
|
6696
|
+
}
|
|
6697
|
+
function parseCursorUserText(text) {
|
|
6698
|
+
const tsMatch = text.match(CURSOR_TIMESTAMP_RE);
|
|
6699
|
+
const queryMatch = text.match(CURSOR_USER_QUERY_RE);
|
|
6700
|
+
return {
|
|
6701
|
+
ts: tsMatch?.[1] ? parseCursorClock(tsMatch[1]) : void 0,
|
|
6702
|
+
prompt: (queryMatch?.[1] || text.replace(CURSOR_TIMESTAMP_RE, "")).trim()
|
|
6703
|
+
};
|
|
6704
|
+
}
|
|
6705
|
+
function messageText(message) {
|
|
6706
|
+
const content = message.content;
|
|
6707
|
+
if (typeof content === "string") {
|
|
6708
|
+
return content;
|
|
6709
|
+
}
|
|
6710
|
+
if (!Array.isArray(content)) {
|
|
6711
|
+
return stringField(message, "text") || "";
|
|
6712
|
+
}
|
|
6713
|
+
const parts = [];
|
|
6714
|
+
for (const item of content) {
|
|
6715
|
+
if (isPlainObject(item) && stringField(item, "type") === "text") {
|
|
6716
|
+
const text = stringField(item, "text");
|
|
6717
|
+
if (text) {
|
|
6718
|
+
parts.push(text);
|
|
6719
|
+
}
|
|
6720
|
+
}
|
|
6721
|
+
}
|
|
6722
|
+
return parts.join("\n");
|
|
6723
|
+
}
|
|
6724
|
+
function messageToolUses(message) {
|
|
6725
|
+
const content = message.content;
|
|
6726
|
+
if (!Array.isArray(content)) {
|
|
6727
|
+
return [];
|
|
6728
|
+
}
|
|
6729
|
+
const tools = [];
|
|
6730
|
+
for (const item of content) {
|
|
6731
|
+
if (!isPlainObject(item) || stringField(item, "type") !== "tool_use") {
|
|
6732
|
+
continue;
|
|
6733
|
+
}
|
|
6734
|
+
tools.push({
|
|
6735
|
+
name: stringField(item, "name") || "unknown",
|
|
6736
|
+
id: stringField(item, "id"),
|
|
6737
|
+
input: isPlainObject(item.input) ? item.input : parseMaybeJsonObject(item.input)
|
|
6738
|
+
});
|
|
6739
|
+
}
|
|
6740
|
+
return tools;
|
|
6741
|
+
}
|
|
6742
|
+
async function indexCursorChatMeta(cursorHome2) {
|
|
6743
|
+
const cached = chatMetaIndexCache.get(cursorHome2);
|
|
6744
|
+
if (cached) {
|
|
6745
|
+
return cached;
|
|
6746
|
+
}
|
|
6747
|
+
const pending = buildCursorChatMetaIndex(cursorHome2);
|
|
6748
|
+
chatMetaIndexCache.set(cursorHome2, pending);
|
|
6749
|
+
return pending;
|
|
6750
|
+
}
|
|
6751
|
+
async function buildCursorChatMetaIndex(cursorHome2) {
|
|
6752
|
+
const index = /* @__PURE__ */ new Map();
|
|
6753
|
+
const chatsDir = path13.join(cursorHome2, "chats");
|
|
6754
|
+
const projectDirs = await readdir6(chatsDir, { withFileTypes: true }).catch(() => []);
|
|
6755
|
+
for (const project of projectDirs) {
|
|
6756
|
+
if (!project.isDirectory()) {
|
|
6757
|
+
continue;
|
|
6758
|
+
}
|
|
6759
|
+
const sessions = await readdir6(path13.join(chatsDir, project.name), { withFileTypes: true }).catch(() => []);
|
|
6760
|
+
for (const session of sessions) {
|
|
6761
|
+
if (!session.isDirectory()) {
|
|
6762
|
+
continue;
|
|
6763
|
+
}
|
|
6764
|
+
const sessionDir = path13.join(chatsDir, project.name, session.name);
|
|
6765
|
+
const meta = await readJsonIfExists(path13.join(sessionDir, "meta.json"));
|
|
6766
|
+
const fromMeta = isPlainObject(meta) ? meta : {};
|
|
6767
|
+
const fromStore = await readCursorStoreMeta(path13.join(sessionDir, "store.db"));
|
|
6768
|
+
const cwd = stringField(fromMeta, "cwd") || fromStore.cwd;
|
|
6769
|
+
index.set(session.name, {
|
|
6770
|
+
cwd,
|
|
6771
|
+
project: cwd ? path13.basename(cwd) : void 0,
|
|
6772
|
+
model: fromStore.model,
|
|
6773
|
+
startedAt: timestampFrom(fromMeta.createdAtMs) || fromStore.startedAt,
|
|
6774
|
+
endedAt: timestampFrom(fromMeta.updatedAtMs) || fromStore.endedAt
|
|
6775
|
+
});
|
|
6776
|
+
}
|
|
6777
|
+
}
|
|
6778
|
+
return index;
|
|
6779
|
+
}
|
|
6780
|
+
async function readCursorStoreMeta(storePath) {
|
|
6781
|
+
const info = await stat7(storePath).catch(() => null);
|
|
6782
|
+
if (!info) {
|
|
6783
|
+
return {};
|
|
6784
|
+
}
|
|
6785
|
+
try {
|
|
6786
|
+
const { DatabaseSync } = await import("node:sqlite");
|
|
6787
|
+
const db = new DatabaseSync(storePath, { readOnly: true });
|
|
6788
|
+
try {
|
|
6789
|
+
const row = db.prepare("SELECT value FROM meta LIMIT 1").get();
|
|
6790
|
+
const decoded = decodeStoreMetaValue(row?.value);
|
|
6791
|
+
if (!decoded) {
|
|
6792
|
+
return {};
|
|
6793
|
+
}
|
|
6794
|
+
const cwd = stringField(decoded, "cwd") || stringField(objectField(decoded, "workspace"), "path");
|
|
6795
|
+
return {
|
|
6796
|
+
cwd,
|
|
6797
|
+
project: cwd ? path13.basename(cwd) : void 0,
|
|
6798
|
+
model: stringField(decoded, "lastUsedModel") || stringField(decoded, "model"),
|
|
6799
|
+
startedAt: timestampFrom(decoded.createdAt),
|
|
6800
|
+
endedAt: timestampFrom(decoded.lastUpdatedAt) || timestampFrom(decoded.updatedAt)
|
|
6801
|
+
};
|
|
6802
|
+
} finally {
|
|
6803
|
+
db.close();
|
|
6804
|
+
}
|
|
6805
|
+
} catch {
|
|
6806
|
+
return {};
|
|
6807
|
+
}
|
|
6808
|
+
}
|
|
6809
|
+
function decodeStoreMetaValue(value) {
|
|
6810
|
+
if (typeof value !== "string" || !value) {
|
|
6811
|
+
return void 0;
|
|
6812
|
+
}
|
|
6813
|
+
try {
|
|
6814
|
+
const asJson = parseJsonObject(value);
|
|
6815
|
+
if (asJson) {
|
|
6816
|
+
return asJson;
|
|
6817
|
+
}
|
|
6818
|
+
return parseJsonObject(Buffer.from(value, "hex").toString("utf8"));
|
|
6819
|
+
} catch {
|
|
6820
|
+
return void 0;
|
|
6821
|
+
}
|
|
6822
|
+
}
|
|
6823
|
+
async function resolveTranscriptContext(filePath, sessionId) {
|
|
6824
|
+
const sibling = await readJsonIfExists(path13.join(path13.dirname(filePath), "meta.json"));
|
|
6825
|
+
if (isPlainObject(sibling)) {
|
|
6826
|
+
const cwd = stringField(sibling, "cwd");
|
|
6827
|
+
return {
|
|
6828
|
+
cwd,
|
|
6829
|
+
project: cwd ? path13.basename(cwd) : void 0,
|
|
6830
|
+
startedAt: timestampFrom(sibling.createdAtMs),
|
|
6831
|
+
endedAt: timestampFrom(sibling.updatedAtMs)
|
|
6832
|
+
};
|
|
6833
|
+
}
|
|
6834
|
+
const cursorHomePath = cursorHomeFromTranscript(filePath);
|
|
6835
|
+
if (!cursorHomePath) {
|
|
6836
|
+
return {};
|
|
6837
|
+
}
|
|
6838
|
+
const index = await indexCursorChatMeta(cursorHomePath);
|
|
6839
|
+
return index.get(sessionId) || {};
|
|
6840
|
+
}
|
|
6841
|
+
async function parseCursorTranscriptFile(filePath, options) {
|
|
6842
|
+
const text = await readFile8(filePath, "utf8").catch(() => "");
|
|
6843
|
+
if (!text) {
|
|
6844
|
+
return [];
|
|
6845
|
+
}
|
|
6846
|
+
const lines = text.split("\n").filter(Boolean);
|
|
6847
|
+
const parsed = lines.map(parseJsonLine);
|
|
6848
|
+
const hasConversation = parsed.some((raw) => raw && (raw.role === "user" || raw.role === "assistant"));
|
|
6849
|
+
if (!hasConversation) {
|
|
6850
|
+
return [];
|
|
6851
|
+
}
|
|
6852
|
+
const sessionId = path13.basename(filePath, ".jsonl");
|
|
6853
|
+
const context = await resolveTranscriptContext(filePath, sessionId);
|
|
6854
|
+
const fileInfo = await stat7(filePath).catch(() => null);
|
|
6855
|
+
let cwd = context.cwd;
|
|
6856
|
+
let project = context.project;
|
|
6857
|
+
let model = context.model;
|
|
6858
|
+
let lastTs = context.startedAt || (fileInfo ? fileInfo.mtime.toISOString() : void 0);
|
|
6859
|
+
const fileMtime = fileInfo ? fileInfo.mtime.toISOString() : void 0;
|
|
6860
|
+
const workspaceId = createWorkspaceId({ projectName: project, repoRoot: cwd });
|
|
6861
|
+
const sourcePathHash = `sha256:${createStableHash(filePath)}`;
|
|
6862
|
+
const events = [];
|
|
6863
|
+
let lineNumber = 0;
|
|
6864
|
+
let currentTurnId;
|
|
6865
|
+
let currentTurnStartedAt;
|
|
6866
|
+
let turnIndex = 0;
|
|
6867
|
+
let sessionStarted = false;
|
|
6868
|
+
let pendingTurnSuccess = true;
|
|
6869
|
+
const pendingTools = [];
|
|
6870
|
+
const push = (partial, topType) => {
|
|
6871
|
+
lineNumber += 1;
|
|
6872
|
+
const event = {
|
|
6873
|
+
schemaVersion: AGENT_TIME_SCHEMA_VERSION,
|
|
6874
|
+
source: SOURCE_ID,
|
|
6875
|
+
agent: AGENT_NAME,
|
|
6876
|
+
workspaceId,
|
|
6877
|
+
project,
|
|
6878
|
+
cwd,
|
|
6879
|
+
model,
|
|
6880
|
+
sessionId,
|
|
6881
|
+
...partial
|
|
6882
|
+
};
|
|
6883
|
+
events.push(withBackfillRefs(event, {
|
|
6884
|
+
filePath,
|
|
6885
|
+
sourcePathHash,
|
|
6886
|
+
lineNumber,
|
|
6887
|
+
topType,
|
|
6888
|
+
payloadType: event.type,
|
|
6889
|
+
options
|
|
6890
|
+
}));
|
|
6891
|
+
};
|
|
6892
|
+
const ensureSessionStarted = (ts) => {
|
|
6893
|
+
if (sessionStarted) {
|
|
6894
|
+
return;
|
|
6895
|
+
}
|
|
6896
|
+
sessionStarted = true;
|
|
6897
|
+
push({
|
|
6898
|
+
ts,
|
|
6899
|
+
type: "session.started",
|
|
6900
|
+
confidence: "partial",
|
|
6901
|
+
refs: stringRefs({ sourceId: `${sessionId}:started` })
|
|
6902
|
+
}, "transcript");
|
|
6903
|
+
};
|
|
6904
|
+
const closeTurn = (endTs, success = pendingTurnSuccess) => {
|
|
6905
|
+
if (!currentTurnId || !currentTurnStartedAt) {
|
|
6906
|
+
pendingTools.length = 0;
|
|
6907
|
+
pendingTurnSuccess = true;
|
|
6908
|
+
return;
|
|
6909
|
+
}
|
|
6910
|
+
const turnId = currentTurnId;
|
|
6911
|
+
const startedAt = currentTurnStartedAt;
|
|
6912
|
+
const finishedAt = latestTimestamp(startedAt, endTs) || startedAt;
|
|
6913
|
+
const toolCount = pendingTools.length;
|
|
6914
|
+
for (const [index, item] of pendingTools.entries()) {
|
|
6915
|
+
const ts = timestampBetween(startedAt, finishedAt, index, toolCount);
|
|
6916
|
+
const fileActivities = fileActivitiesFromCursorTool(item.tool, item.input, ts, cwd);
|
|
6917
|
+
push({
|
|
6918
|
+
ts,
|
|
6919
|
+
type: "tool.started",
|
|
6920
|
+
turnId,
|
|
6921
|
+
tool: item.tool,
|
|
6922
|
+
operation: `${item.tool} started`,
|
|
6923
|
+
model,
|
|
6924
|
+
confidence: "exact",
|
|
6925
|
+
fileActivities: fileActivities.length > 0 ? fileActivities : void 0,
|
|
6926
|
+
refs: stringRefs({ sourceId: `${item.toolCallId}:started` })
|
|
6927
|
+
}, "tool");
|
|
6928
|
+
push({
|
|
6929
|
+
ts,
|
|
6930
|
+
type: "tool.completed",
|
|
6931
|
+
turnId,
|
|
6932
|
+
tool: item.tool,
|
|
6933
|
+
operation: `${item.tool} completed`,
|
|
6934
|
+
success,
|
|
6935
|
+
model,
|
|
6936
|
+
confidence: "exact",
|
|
6937
|
+
metrics: { toolCalls: 1 },
|
|
6938
|
+
refs: stringRefs({ sourceId: `${item.toolCallId}:completed` })
|
|
6939
|
+
}, "tool");
|
|
6940
|
+
if (item.command) {
|
|
6941
|
+
push({
|
|
6942
|
+
ts,
|
|
6943
|
+
type: "command.started",
|
|
6944
|
+
turnId,
|
|
6945
|
+
tool: item.tool,
|
|
6946
|
+
operation: "command started",
|
|
6947
|
+
confidence: "derived",
|
|
6948
|
+
refs: stringRefs({
|
|
6949
|
+
sourceId: `${item.toolCallId}:command:started`,
|
|
6950
|
+
commandHash: `sha256:${createStableHash(item.command)}`
|
|
6951
|
+
})
|
|
6952
|
+
}, "tool");
|
|
6953
|
+
push({
|
|
6954
|
+
ts,
|
|
6955
|
+
type: "command.completed",
|
|
6956
|
+
turnId,
|
|
6957
|
+
tool: item.tool,
|
|
6958
|
+
operation: "command completed",
|
|
6959
|
+
success,
|
|
6960
|
+
confidence: "derived",
|
|
6961
|
+
metrics: { commandCalls: 1 },
|
|
6962
|
+
refs: stringRefs({ sourceId: `${item.toolCallId}:command:completed` })
|
|
6963
|
+
}, "tool");
|
|
6964
|
+
}
|
|
6965
|
+
}
|
|
6966
|
+
pendingTools.length = 0;
|
|
6967
|
+
push({
|
|
6968
|
+
ts: finishedAt,
|
|
6969
|
+
type: "turn.completed",
|
|
6970
|
+
turnId,
|
|
6971
|
+
model,
|
|
6972
|
+
success,
|
|
6973
|
+
confidence: "derived",
|
|
6974
|
+
metrics: { durationMs: durationMsBetween(startedAt, finishedAt) },
|
|
6975
|
+
refs: stringRefs({ sourceId: `${sessionId}:${turnId}:completed` })
|
|
6976
|
+
}, "transcript");
|
|
6977
|
+
currentTurnId = void 0;
|
|
6978
|
+
currentTurnStartedAt = void 0;
|
|
6979
|
+
pendingTurnSuccess = true;
|
|
6980
|
+
};
|
|
6981
|
+
for (const raw of parsed) {
|
|
6982
|
+
if (!raw) {
|
|
6983
|
+
continue;
|
|
6984
|
+
}
|
|
6985
|
+
const role = stringField(raw, "role");
|
|
6986
|
+
const topType = stringField(raw, "type");
|
|
6987
|
+
const message = objectField(raw, "message");
|
|
6988
|
+
const body = Object.keys(message).length > 0 ? message : raw;
|
|
6989
|
+
const rawText = messageText(body);
|
|
6990
|
+
if (!cwd) {
|
|
6991
|
+
const workspaceMatch = rawText.match(/Workspace Path:\s*(.+)/);
|
|
6992
|
+
const found = workspaceMatch?.[1]?.trim();
|
|
6993
|
+
if (found) {
|
|
6994
|
+
cwd = found;
|
|
6995
|
+
project = path13.basename(found);
|
|
6996
|
+
}
|
|
6997
|
+
}
|
|
6998
|
+
if (role === "user") {
|
|
6999
|
+
const userBits = parseCursorUserText(rawText);
|
|
7000
|
+
const ts = timestampFrom(raw.timestamp) || userBits.ts;
|
|
7001
|
+
if (!ts) {
|
|
7002
|
+
continue;
|
|
7003
|
+
}
|
|
7004
|
+
lastTs = latestTimestamp(lastTs, ts) || ts;
|
|
7005
|
+
ensureSessionStarted(ts);
|
|
7006
|
+
closeTurn(ts);
|
|
7007
|
+
turnIndex += 1;
|
|
7008
|
+
currentTurnId = `turn_${turnIndex}`;
|
|
7009
|
+
currentTurnStartedAt = ts;
|
|
7010
|
+
pendingTurnSuccess = true;
|
|
7011
|
+
push({
|
|
7012
|
+
ts,
|
|
7013
|
+
type: "turn.started",
|
|
7014
|
+
turnId: currentTurnId,
|
|
7015
|
+
model,
|
|
7016
|
+
confidence: "partial",
|
|
7017
|
+
refs: stringRefs({ sourceId: `${sessionId}:${currentTurnId}:started` })
|
|
7018
|
+
}, "transcript");
|
|
7019
|
+
if (userBits.prompt) {
|
|
7020
|
+
push({
|
|
7021
|
+
ts,
|
|
7022
|
+
type: "prompt.submitted",
|
|
7023
|
+
turnId: currentTurnId,
|
|
7024
|
+
model,
|
|
7025
|
+
confidence: "partial",
|
|
7026
|
+
metrics: { prompts: 1, promptChars: userBits.prompt.length },
|
|
7027
|
+
refs: stringRefs({
|
|
7028
|
+
sourceId: `${sessionId}:${currentTurnId}:prompt`,
|
|
7029
|
+
promptHash: `sha256:${createStableHash(userBits.prompt)}`
|
|
7030
|
+
})
|
|
7031
|
+
}, "transcript");
|
|
7032
|
+
}
|
|
7033
|
+
continue;
|
|
7034
|
+
}
|
|
7035
|
+
if (role === "assistant") {
|
|
7036
|
+
if (!currentTurnId && lastTs) {
|
|
7037
|
+
ensureSessionStarted(lastTs);
|
|
7038
|
+
turnIndex += 1;
|
|
7039
|
+
currentTurnId = `turn_${turnIndex}`;
|
|
7040
|
+
currentTurnStartedAt = lastTs;
|
|
7041
|
+
}
|
|
7042
|
+
if (!currentTurnId) {
|
|
7043
|
+
continue;
|
|
7044
|
+
}
|
|
7045
|
+
for (const [index, toolUse] of messageToolUses(body).entries()) {
|
|
7046
|
+
const tool = toolNameOf(toolUse.name);
|
|
7047
|
+
const command = isShellTool(tool) ? stringField(toolUse.input, "command") || stringField(toolUse.input, "cmd") : void 0;
|
|
7048
|
+
pendingTools.push({
|
|
7049
|
+
tool,
|
|
7050
|
+
toolCallId: cleanId(
|
|
7051
|
+
toolUse.id || `${sessionId}:${currentTurnId}:${pendingTools.length}:${index}`
|
|
7052
|
+
),
|
|
7053
|
+
input: toolUse.input,
|
|
7054
|
+
command
|
|
7055
|
+
});
|
|
7056
|
+
}
|
|
7057
|
+
continue;
|
|
7058
|
+
}
|
|
7059
|
+
if (topType === "turn_ended") {
|
|
7060
|
+
const status = (stringField(raw, "status") || "success").toLowerCase();
|
|
7061
|
+
pendingTurnSuccess = status !== "error" && status !== "failed" && status !== "cancelled";
|
|
7062
|
+
}
|
|
7063
|
+
}
|
|
7064
|
+
const endedAt = latestTimestamp(lastTs, context.endedAt, fileMtime);
|
|
7065
|
+
if (endedAt) {
|
|
7066
|
+
closeTurn(endedAt);
|
|
7067
|
+
if (sessionStarted) {
|
|
7068
|
+
push({
|
|
7069
|
+
ts: endedAt,
|
|
7070
|
+
type: "session.ended",
|
|
7071
|
+
confidence: "derived",
|
|
7072
|
+
refs: stringRefs({ sourceId: `${sessionId}:ended` })
|
|
7073
|
+
}, "transcript");
|
|
7074
|
+
}
|
|
7075
|
+
}
|
|
7076
|
+
return events;
|
|
7077
|
+
}
|
|
7078
|
+
async function listCursorComposerIds(dbPath) {
|
|
7079
|
+
const ids = /* @__PURE__ */ new Set();
|
|
7080
|
+
const info = await stat7(dbPath).catch(() => null);
|
|
7081
|
+
if (!info) {
|
|
7082
|
+
return ids;
|
|
7083
|
+
}
|
|
7084
|
+
try {
|
|
7085
|
+
const opened = await openCursorDb(dbPath);
|
|
7086
|
+
try {
|
|
7087
|
+
for (const composer of listComposers(opened.db)) {
|
|
7088
|
+
ids.add(composer.composerId);
|
|
7089
|
+
}
|
|
7090
|
+
} finally {
|
|
7091
|
+
opened.db.close();
|
|
7092
|
+
await opened.cleanup();
|
|
7093
|
+
}
|
|
7094
|
+
} catch {
|
|
7095
|
+
return ids;
|
|
7096
|
+
}
|
|
7097
|
+
return ids;
|
|
7098
|
+
}
|
|
7099
|
+
async function collectCursorTranscriptFiles(root, skipSessionIds) {
|
|
7100
|
+
const files = [];
|
|
7101
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7102
|
+
for (const filePath of await listJsonlFiles(root)) {
|
|
7103
|
+
if (!isCursorTranscriptPath(filePath) && path13.basename(path13.dirname(filePath)) !== "agent-transcripts") {
|
|
7104
|
+
continue;
|
|
7105
|
+
}
|
|
7106
|
+
const sessionId = path13.basename(filePath, ".jsonl");
|
|
7107
|
+
if (!sessionId || seen.has(sessionId) || skipSessionIds?.has(sessionId)) {
|
|
7108
|
+
continue;
|
|
7109
|
+
}
|
|
7110
|
+
seen.add(sessionId);
|
|
7111
|
+
const info = await stat7(filePath).catch(() => null);
|
|
7112
|
+
if (!info) {
|
|
7113
|
+
continue;
|
|
7114
|
+
}
|
|
7115
|
+
files.push({ path: filePath, modifiedAt: info.mtime.toISOString() });
|
|
7116
|
+
}
|
|
7117
|
+
return files;
|
|
7118
|
+
}
|
|
7119
|
+
async function parseCursorSessionFile(filePath, options) {
|
|
7120
|
+
const base = path13.basename(filePath);
|
|
7121
|
+
if (base.endsWith(".jsonl")) {
|
|
7122
|
+
return parseCursorTranscriptFile(filePath, options);
|
|
7123
|
+
}
|
|
7124
|
+
if (base === "store.db") {
|
|
7125
|
+
return [];
|
|
7126
|
+
}
|
|
7127
|
+
if (base !== "state.vscdb" && !base.endsWith(".vscdb") && !base.endsWith(".db")) {
|
|
7128
|
+
return [];
|
|
7129
|
+
}
|
|
7130
|
+
const opened = await openCursorDb(filePath);
|
|
7131
|
+
const events = [];
|
|
7132
|
+
const sourcePathHash = `sha256:${createStableHash(filePath)}`;
|
|
7133
|
+
try {
|
|
7134
|
+
const composers = listComposers(opened.db);
|
|
7135
|
+
for (const composer of composers) {
|
|
7136
|
+
events.push(...parseComposer(opened.db, composer, filePath, sourcePathHash, options));
|
|
7137
|
+
}
|
|
7138
|
+
} finally {
|
|
7139
|
+
opened.db.close();
|
|
7140
|
+
await opened.cleanup();
|
|
7141
|
+
}
|
|
7142
|
+
return events;
|
|
7143
|
+
}
|
|
7144
|
+
function parseComposer(db, composer, filePath, sourcePathHash, options) {
|
|
7145
|
+
const sessionId = composer.composerId;
|
|
7146
|
+
const data = composer.data;
|
|
7147
|
+
const { cwd, project } = workspaceFromComposer(data);
|
|
7148
|
+
const model = modelFromComposer(data);
|
|
7149
|
+
const workspaceId = createWorkspaceId({ projectName: project, repoRoot: cwd });
|
|
7150
|
+
const startedAt = timestampFrom(composer.createdAt) || timestampFrom(data.createdAt);
|
|
7151
|
+
const endedAt = timestampFrom(composer.lastUpdatedAt) || timestampFrom(data.lastUpdatedAt) || startedAt;
|
|
7152
|
+
if (!startedAt) {
|
|
7153
|
+
return [];
|
|
7154
|
+
}
|
|
7155
|
+
const bubbles = loadBubbles(db, sessionId);
|
|
7156
|
+
const events = [];
|
|
7157
|
+
let lineNumber = 0;
|
|
7158
|
+
let currentTurnId;
|
|
7159
|
+
let currentTurnStartedAt;
|
|
7160
|
+
let turnIndex = 0;
|
|
7161
|
+
let hasFileLineData = false;
|
|
7162
|
+
const push = (partial, topType) => {
|
|
7163
|
+
lineNumber += 1;
|
|
7164
|
+
const event = {
|
|
7165
|
+
schemaVersion: AGENT_TIME_SCHEMA_VERSION,
|
|
7166
|
+
source: SOURCE_ID,
|
|
7167
|
+
agent: AGENT_NAME,
|
|
7168
|
+
workspaceId,
|
|
7169
|
+
project,
|
|
7170
|
+
cwd,
|
|
7171
|
+
model,
|
|
7172
|
+
sessionId,
|
|
7173
|
+
...partial
|
|
7174
|
+
};
|
|
7175
|
+
events.push(withBackfillRefs(event, {
|
|
7176
|
+
filePath,
|
|
7177
|
+
sourcePathHash,
|
|
7178
|
+
lineNumber,
|
|
7179
|
+
topType,
|
|
7180
|
+
payloadType: event.type,
|
|
7181
|
+
options
|
|
7182
|
+
}));
|
|
7183
|
+
};
|
|
7184
|
+
push({
|
|
7185
|
+
ts: startedAt,
|
|
7186
|
+
type: "session.started",
|
|
7187
|
+
confidence: "partial",
|
|
7188
|
+
refs: stringRefs({ sourceId: `${sessionId}:started` })
|
|
7189
|
+
}, "composer");
|
|
7190
|
+
for (const bubble of bubbles) {
|
|
7191
|
+
const ts = bubbleTs(bubble, startedAt);
|
|
7192
|
+
if (!ts) {
|
|
7193
|
+
continue;
|
|
7194
|
+
}
|
|
7195
|
+
const bubbleId = stringField(bubble, "bubbleId") || `bubble_${lineNumber}`;
|
|
7196
|
+
const bubbleType = numberField(bubble, "type");
|
|
7197
|
+
const capabilityType = numberField(bubble, "capabilityType");
|
|
7198
|
+
const toolFormer = objectField(bubble, "toolFormerData");
|
|
7199
|
+
const bubbleModel = stringField(objectField(bubble, "modelInfo"), "modelName") || model;
|
|
7200
|
+
if (bubbleType === BUBBLE_USER) {
|
|
7201
|
+
if (currentTurnId && currentTurnStartedAt) {
|
|
7202
|
+
push({
|
|
7203
|
+
ts,
|
|
7204
|
+
type: "turn.completed",
|
|
7205
|
+
turnId: currentTurnId,
|
|
7206
|
+
model: bubbleModel,
|
|
7207
|
+
success: true,
|
|
7208
|
+
confidence: "derived",
|
|
7209
|
+
metrics: { durationMs: durationMsBetween(currentTurnStartedAt, ts) },
|
|
7210
|
+
refs: stringRefs({ sourceId: `${sessionId}:${currentTurnId}:completed` })
|
|
7211
|
+
}, "bubble");
|
|
7212
|
+
}
|
|
7213
|
+
turnIndex += 1;
|
|
7214
|
+
currentTurnId = `turn_${turnIndex}`;
|
|
7215
|
+
currentTurnStartedAt = ts;
|
|
7216
|
+
const text = stringField(bubble, "text") || "";
|
|
7217
|
+
push({
|
|
7218
|
+
ts,
|
|
7219
|
+
type: "turn.started",
|
|
7220
|
+
turnId: currentTurnId,
|
|
7221
|
+
model: bubbleModel,
|
|
7222
|
+
confidence: "partial",
|
|
7223
|
+
refs: stringRefs({ sourceId: `${sessionId}:${currentTurnId}:started` })
|
|
7224
|
+
}, "bubble");
|
|
7225
|
+
if (text) {
|
|
7226
|
+
push({
|
|
7227
|
+
ts,
|
|
7228
|
+
type: "prompt.submitted",
|
|
7229
|
+
turnId: currentTurnId,
|
|
7230
|
+
model: bubbleModel,
|
|
7231
|
+
confidence: "partial",
|
|
7232
|
+
metrics: { prompts: 1, promptChars: text.length },
|
|
7233
|
+
refs: stringRefs({
|
|
7234
|
+
sourceId: `${sessionId}:${cleanId(bubbleId)}:prompt`,
|
|
7235
|
+
promptHash: `sha256:${createStableHash(text)}`
|
|
7236
|
+
})
|
|
7237
|
+
}, "bubble");
|
|
7238
|
+
}
|
|
7239
|
+
continue;
|
|
7240
|
+
}
|
|
7241
|
+
if (capabilityType === CAPABILITY_TOOL && Object.keys(toolFormer).length > 0) {
|
|
7242
|
+
const rawName = stringField(toolFormer, "name") || "unknown";
|
|
7243
|
+
const tool = toolNameOf(rawName);
|
|
7244
|
+
const toolCallId = cleanId(
|
|
7245
|
+
stringField(toolFormer, "toolCallId") || `${sessionId}:${cleanId(bubbleId)}`
|
|
7246
|
+
);
|
|
7247
|
+
const status = (stringField(toolFormer, "status") || "completed").toLowerCase();
|
|
7248
|
+
const success = status !== "error" && status !== "failed" && status !== "cancelled";
|
|
7249
|
+
const input = parseMaybeJsonObject(toolFormer.rawArgs);
|
|
7250
|
+
const params = parseMaybeJsonObject(toolFormer.params);
|
|
7251
|
+
const mergedInput = { ...params, ...input };
|
|
7252
|
+
const fileActivities = fileActivitiesFromCursorTool(tool, mergedInput, ts, cwd);
|
|
7253
|
+
if (fileActivities.some((file) => (file.linesAdded || 0) > 0 || (file.linesRemoved || 0) > 0)) {
|
|
7254
|
+
hasFileLineData = true;
|
|
7255
|
+
}
|
|
7256
|
+
push({
|
|
7257
|
+
ts,
|
|
7258
|
+
type: "tool.started",
|
|
7259
|
+
turnId: currentTurnId,
|
|
7260
|
+
tool,
|
|
7261
|
+
operation: `${tool} started`,
|
|
7262
|
+
model: bubbleModel,
|
|
7263
|
+
confidence: "exact",
|
|
7264
|
+
fileActivities: fileActivities.length > 0 ? fileActivities : void 0,
|
|
7265
|
+
refs: stringRefs({ sourceId: `${toolCallId}:started` })
|
|
7266
|
+
}, "tool");
|
|
7267
|
+
push({
|
|
7268
|
+
ts,
|
|
7269
|
+
type: success ? "tool.completed" : "tool.failed",
|
|
7270
|
+
turnId: currentTurnId,
|
|
7271
|
+
tool,
|
|
7272
|
+
operation: `${tool} completed`,
|
|
7273
|
+
success,
|
|
7274
|
+
model: bubbleModel,
|
|
7275
|
+
confidence: "exact",
|
|
7276
|
+
metrics: { toolCalls: 1 },
|
|
7277
|
+
refs: stringRefs({ sourceId: `${toolCallId}:completed` })
|
|
7278
|
+
}, "tool");
|
|
7279
|
+
if (isShellTool(tool)) {
|
|
7280
|
+
const command = stringField(mergedInput, "command") || stringField(mergedInput, "cmd");
|
|
7281
|
+
if (command) {
|
|
7282
|
+
push({
|
|
7283
|
+
ts,
|
|
7284
|
+
type: "command.started",
|
|
7285
|
+
turnId: currentTurnId,
|
|
7286
|
+
tool,
|
|
7287
|
+
operation: "command started",
|
|
7288
|
+
confidence: "derived",
|
|
7289
|
+
refs: stringRefs({
|
|
7290
|
+
sourceId: `${toolCallId}:command:started`,
|
|
7291
|
+
commandHash: `sha256:${createStableHash(command)}`
|
|
7292
|
+
})
|
|
7293
|
+
}, "tool");
|
|
7294
|
+
push({
|
|
7295
|
+
ts,
|
|
7296
|
+
type: success ? "command.completed" : "command.failed",
|
|
7297
|
+
turnId: currentTurnId,
|
|
7298
|
+
tool,
|
|
7299
|
+
operation: "command completed",
|
|
7300
|
+
success,
|
|
7301
|
+
confidence: "derived",
|
|
7302
|
+
metrics: { commandCalls: 1 },
|
|
7303
|
+
refs: stringRefs({ sourceId: `${toolCallId}:command:completed` })
|
|
7304
|
+
}, "tool");
|
|
7305
|
+
}
|
|
7306
|
+
}
|
|
7307
|
+
}
|
|
7308
|
+
const metrics = usageMetrics(objectField(bubble, "tokenCount"));
|
|
7309
|
+
if (metrics) {
|
|
7310
|
+
push({
|
|
7311
|
+
ts,
|
|
7312
|
+
type: "model.usage",
|
|
7313
|
+
turnId: currentTurnId,
|
|
7314
|
+
model: bubbleModel,
|
|
7315
|
+
confidence: "partial",
|
|
7316
|
+
metrics,
|
|
7317
|
+
refs: stringRefs({ sourceId: `${sessionId}:${cleanId(bubbleId)}:usage` })
|
|
7318
|
+
}, "bubble");
|
|
7319
|
+
}
|
|
7320
|
+
}
|
|
7321
|
+
if (currentTurnId && endedAt) {
|
|
7322
|
+
push({
|
|
7323
|
+
ts: endedAt,
|
|
7324
|
+
type: "turn.completed",
|
|
7325
|
+
turnId: currentTurnId,
|
|
7326
|
+
success: true,
|
|
7327
|
+
confidence: "derived",
|
|
7328
|
+
metrics: { durationMs: durationMsBetween(currentTurnStartedAt, endedAt) },
|
|
7329
|
+
refs: stringRefs({ sourceId: `${sessionId}:${currentTurnId}:completed` })
|
|
7330
|
+
}, "bubble");
|
|
7331
|
+
}
|
|
7332
|
+
const linesAdded = numberField(data, "totalLinesAdded");
|
|
7333
|
+
const linesRemoved = numberField(data, "totalLinesRemoved");
|
|
7334
|
+
if (!hasFileLineData && endedAt && ((linesAdded || 0) > 0 || (linesRemoved || 0) > 0)) {
|
|
7335
|
+
push({
|
|
7336
|
+
ts: endedAt,
|
|
7337
|
+
type: "file.changed",
|
|
7338
|
+
confidence: "partial",
|
|
7339
|
+
metrics: {
|
|
7340
|
+
linesAdded: linesAdded || void 0,
|
|
7341
|
+
linesRemoved: linesRemoved || void 0
|
|
7342
|
+
},
|
|
7343
|
+
refs: stringRefs({ sourceId: `${sessionId}:lines` })
|
|
7344
|
+
}, "composer");
|
|
7345
|
+
}
|
|
7346
|
+
if (endedAt) {
|
|
7347
|
+
push({
|
|
7348
|
+
ts: endedAt,
|
|
7349
|
+
type: "session.ended",
|
|
7350
|
+
confidence: "derived",
|
|
7351
|
+
refs: stringRefs({ sourceId: `${sessionId}:ended` })
|
|
7352
|
+
}, "composer");
|
|
7353
|
+
}
|
|
7354
|
+
return events;
|
|
7355
|
+
}
|
|
7356
|
+
async function cursorBackfillFiles(sourceRoot, home = os6.homedir(), env) {
|
|
7357
|
+
if (sourceRoot) {
|
|
7358
|
+
const info = await stat7(sourceRoot).catch(() => null);
|
|
7359
|
+
if (!info) {
|
|
7360
|
+
return [];
|
|
7361
|
+
}
|
|
7362
|
+
if (info.isDirectory()) {
|
|
7363
|
+
const files2 = [];
|
|
7364
|
+
for (const dbPath of await listFilesByExtensions(sourceRoot, [".vscdb", ".db"])) {
|
|
7365
|
+
const base = path13.basename(dbPath);
|
|
7366
|
+
if (base === "store.db" || base !== "state.vscdb" && !base.endsWith(".vscdb")) {
|
|
7367
|
+
continue;
|
|
7368
|
+
}
|
|
7369
|
+
const dbInfo = await stat7(dbPath).catch(() => null);
|
|
7370
|
+
if (dbInfo) {
|
|
7371
|
+
files2.push({ path: dbPath, modifiedAt: dbInfo.mtime.toISOString() });
|
|
7372
|
+
}
|
|
7373
|
+
}
|
|
7374
|
+
const skipIds2 = /* @__PURE__ */ new Set();
|
|
7375
|
+
for (const file of files2) {
|
|
7376
|
+
for (const id of await listCursorComposerIds(file.path)) {
|
|
7377
|
+
skipIds2.add(id);
|
|
7378
|
+
}
|
|
7379
|
+
}
|
|
7380
|
+
files2.push(...await collectCursorTranscriptFiles(sourceRoot, skipIds2));
|
|
7381
|
+
return files2;
|
|
7382
|
+
}
|
|
7383
|
+
if (path13.basename(sourceRoot) === "store.db") {
|
|
7384
|
+
return [];
|
|
7385
|
+
}
|
|
7386
|
+
return [{ path: sourceRoot, modifiedAt: info.mtime.toISOString() }];
|
|
7387
|
+
}
|
|
7388
|
+
const files = [];
|
|
7389
|
+
const skipIds = /* @__PURE__ */ new Set();
|
|
7390
|
+
for (const candidatePath of cursorStateDbCandidates(home, env)) {
|
|
7391
|
+
const info = await stat7(candidatePath).catch(() => null);
|
|
7392
|
+
if (!info) {
|
|
6182
7393
|
continue;
|
|
6183
7394
|
}
|
|
6184
|
-
|
|
6185
|
-
const
|
|
6186
|
-
|
|
6187
|
-
results.push({ path: eventsPath, modifiedAt: info.mtime.toISOString() });
|
|
7395
|
+
files.push({ path: candidatePath, modifiedAt: info.mtime.toISOString() });
|
|
7396
|
+
for (const id of await listCursorComposerIds(candidatePath)) {
|
|
7397
|
+
skipIds.add(id);
|
|
6188
7398
|
}
|
|
7399
|
+
break;
|
|
6189
7400
|
}
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
function copilotPluginContent() {
|
|
6193
|
-
return `// Agent Time plugin for GitHub Copilot CLI
|
|
6194
|
-
// Generated by vibetime.
|
|
6195
|
-
// Copilot does not support hooks \u2014 this file is a placeholder for detection.
|
|
6196
|
-
// Backfill reads session data from ~/.copilot/session-state/*/events.jsonl.
|
|
6197
|
-
`;
|
|
7401
|
+
files.push(...await collectCursorTranscriptFiles(cursorProjectsDir(home, env), skipIds));
|
|
7402
|
+
return files;
|
|
6198
7403
|
}
|
|
6199
|
-
function
|
|
6200
|
-
const
|
|
6201
|
-
|
|
6202
|
-
|
|
7404
|
+
function cursorHookConfig() {
|
|
7405
|
+
const entry = { command: HOOK_COMMAND, timeout: HOOK_TIMEOUT_SECONDS };
|
|
7406
|
+
const hooks = {};
|
|
7407
|
+
for (const event of CURSOR_HOOK_EVENTS) {
|
|
7408
|
+
hooks[event] = [entry];
|
|
6203
7409
|
}
|
|
6204
|
-
return
|
|
7410
|
+
return { version: 1, hooks };
|
|
6205
7411
|
}
|
|
6206
|
-
function
|
|
7412
|
+
function createCursorAdapter() {
|
|
6207
7413
|
return {
|
|
6208
|
-
id:
|
|
6209
|
-
label: "
|
|
6210
|
-
agentName:
|
|
6211
|
-
kind: "
|
|
7414
|
+
id: SOURCE_ID,
|
|
7415
|
+
label: "Cursor",
|
|
7416
|
+
agentName: AGENT_NAME,
|
|
7417
|
+
kind: "ide",
|
|
6212
7418
|
detectPath(home, env) {
|
|
6213
|
-
return
|
|
7419
|
+
return cursorHome(home, env);
|
|
6214
7420
|
},
|
|
6215
7421
|
installedPath(home, env) {
|
|
6216
|
-
return
|
|
7422
|
+
return cursorHooksPath(home, env);
|
|
6217
7423
|
},
|
|
6218
7424
|
async isInstalled(home, env) {
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
return await pathExists2(path12.join(copilotHome(home, env), ".vibetime"));
|
|
6222
|
-
} catch {
|
|
6223
|
-
return false;
|
|
6224
|
-
}
|
|
7425
|
+
const config = await readJsonIfExists(cursorHooksPath(home, env));
|
|
7426
|
+
return hasCursorHookCommand(config, HOOK_COMMAND);
|
|
6225
7427
|
},
|
|
6226
7428
|
installEntries(home, env) {
|
|
6227
7429
|
return [{
|
|
6228
|
-
kind: "
|
|
6229
|
-
path:
|
|
6230
|
-
content:
|
|
7430
|
+
kind: "cursor-hooks-json",
|
|
7431
|
+
path: cursorHooksPath(home, env),
|
|
7432
|
+
content: cursorHookConfig()
|
|
6231
7433
|
}];
|
|
6232
7434
|
},
|
|
6233
7435
|
sourcePaths(home, env) {
|
|
6234
|
-
return [
|
|
7436
|
+
return [
|
|
7437
|
+
...cursorStateDbCandidates(home, env),
|
|
7438
|
+
cursorProjectsDir(home, env),
|
|
7439
|
+
cursorChatsDir(home, env)
|
|
7440
|
+
];
|
|
6235
7441
|
},
|
|
6236
|
-
parseSessionFile:
|
|
7442
|
+
parseSessionFile: parseCursorSessionFile
|
|
6237
7443
|
};
|
|
6238
7444
|
}
|
|
6239
7445
|
|
|
6240
7446
|
// src/adapters/grok-build.ts
|
|
6241
|
-
import { readdir as
|
|
6242
|
-
import
|
|
7447
|
+
import { readdir as readdir7, readFile as readFile9, stat as stat8 } from "node:fs/promises";
|
|
7448
|
+
import path14 from "node:path";
|
|
6243
7449
|
init_fs();
|
|
6244
7450
|
var GROK_COST_TICKS_PER_USD = 1e9;
|
|
6245
|
-
var
|
|
6246
|
-
var
|
|
6247
|
-
var
|
|
7451
|
+
var SOURCE_ID2 = "grok-build";
|
|
7452
|
+
var AGENT_NAME2 = "grok-build";
|
|
7453
|
+
var HOOK_COMMAND2 = `vibetime hook --agent ${SOURCE_ID2}`;
|
|
6248
7454
|
function grokHome(home, env) {
|
|
6249
7455
|
const override = env?.GROK_HOME;
|
|
6250
7456
|
if (override && override.trim()) {
|
|
6251
|
-
return
|
|
7457
|
+
return path14.resolve(override);
|
|
6252
7458
|
}
|
|
6253
|
-
return
|
|
7459
|
+
return path14.join(home, ".grok");
|
|
6254
7460
|
}
|
|
6255
7461
|
function grokHooksPath(home, env) {
|
|
6256
|
-
return
|
|
7462
|
+
return path14.join(grokHome(home, env), "hooks", "vibetime.json");
|
|
6257
7463
|
}
|
|
6258
7464
|
function grokSessionsDir(home, env) {
|
|
6259
|
-
return
|
|
7465
|
+
return path14.join(grokHome(home, env), "sessions");
|
|
6260
7466
|
}
|
|
6261
7467
|
async function grokBackfillFiles(sourceRoot, home, env) {
|
|
6262
7468
|
const root = sourceRoot || grokSessionsDir(home, env);
|
|
@@ -6264,12 +7470,12 @@ async function grokBackfillFiles(sourceRoot, home, env) {
|
|
|
6264
7470
|
async function walk(dir) {
|
|
6265
7471
|
let entries;
|
|
6266
7472
|
try {
|
|
6267
|
-
entries = await
|
|
7473
|
+
entries = await readdir7(dir, { withFileTypes: true });
|
|
6268
7474
|
} catch {
|
|
6269
7475
|
return;
|
|
6270
7476
|
}
|
|
6271
7477
|
for (const entry of entries) {
|
|
6272
|
-
const entryPath =
|
|
7478
|
+
const entryPath = path14.join(dir, entry.name);
|
|
6273
7479
|
if (entry.isDirectory()) {
|
|
6274
7480
|
await walk(entryPath);
|
|
6275
7481
|
continue;
|
|
@@ -6278,7 +7484,7 @@ async function grokBackfillFiles(sourceRoot, home, env) {
|
|
|
6278
7484
|
continue;
|
|
6279
7485
|
}
|
|
6280
7486
|
try {
|
|
6281
|
-
const info = await
|
|
7487
|
+
const info = await stat8(entryPath);
|
|
6282
7488
|
files.push({ path: entryPath, modifiedAt: info.mtime.toISOString() });
|
|
6283
7489
|
} catch {
|
|
6284
7490
|
}
|
|
@@ -6292,18 +7498,18 @@ async function parseGrokSessionFile(filePath, options) {
|
|
|
6292
7498
|
if (!sessionDir) {
|
|
6293
7499
|
return [];
|
|
6294
7500
|
}
|
|
6295
|
-
const summaryPath =
|
|
6296
|
-
const eventsPath =
|
|
6297
|
-
const signalsPath =
|
|
6298
|
-
const updatesPath =
|
|
7501
|
+
const summaryPath = path14.join(sessionDir, "summary.json");
|
|
7502
|
+
const eventsPath = path14.join(sessionDir, "events.jsonl");
|
|
7503
|
+
const signalsPath = path14.join(sessionDir, "signals.json");
|
|
7504
|
+
const updatesPath = path14.join(sessionDir, "updates.jsonl");
|
|
6299
7505
|
const summary = await readJsonIfExists(summaryPath);
|
|
6300
7506
|
if (!isPlainObject(summary)) {
|
|
6301
7507
|
return [];
|
|
6302
7508
|
}
|
|
6303
7509
|
const info = objectField(summary, "info");
|
|
6304
|
-
const sessionId = stringField(info, "id") ||
|
|
7510
|
+
const sessionId = stringField(info, "id") || path14.basename(sessionDir);
|
|
6305
7511
|
const cwd = stringField(info, "cwd");
|
|
6306
|
-
const project = cwd ?
|
|
7512
|
+
const project = cwd ? path14.basename(cwd) : void 0;
|
|
6307
7513
|
const workspaceId = createWorkspaceId({ projectName: project, repoRoot: cwd });
|
|
6308
7514
|
const model = stringField(summary, "current_model_id");
|
|
6309
7515
|
const createdAt = timestampFrom(summary.created_at) || timestampFrom(summary.createdAt);
|
|
@@ -6330,8 +7536,8 @@ async function parseGrokSessionFile(filePath, options) {
|
|
|
6330
7536
|
};
|
|
6331
7537
|
const base = (partial) => ({
|
|
6332
7538
|
schemaVersion: AGENT_TIME_SCHEMA_VERSION,
|
|
6333
|
-
source:
|
|
6334
|
-
agent:
|
|
7539
|
+
source: SOURCE_ID2,
|
|
7540
|
+
agent: AGENT_NAME2,
|
|
6335
7541
|
workspaceId,
|
|
6336
7542
|
project,
|
|
6337
7543
|
cwd,
|
|
@@ -6351,7 +7557,7 @@ async function parseGrokSessionFile(filePath, options) {
|
|
|
6351
7557
|
const pendingTools = /* @__PURE__ */ new Map();
|
|
6352
7558
|
let eventsText = "";
|
|
6353
7559
|
try {
|
|
6354
|
-
eventsText = await
|
|
7560
|
+
eventsText = await readFile9(eventsPath, "utf8");
|
|
6355
7561
|
} catch {
|
|
6356
7562
|
}
|
|
6357
7563
|
const lines = eventsText.split("\n").filter(Boolean);
|
|
@@ -6429,7 +7635,7 @@ async function parseGrokSessionFile(filePath, options) {
|
|
|
6429
7635
|
fileActivities: fileActivities.length > 0 ? fileActivities : void 0,
|
|
6430
7636
|
refs: stringRefs({ sourceId: `${toolId}:started` })
|
|
6431
7637
|
}), lineNumber, type, "tool.started");
|
|
6432
|
-
if (
|
|
7638
|
+
if (isShellTool2(toolName)) {
|
|
6433
7639
|
const command = stringField(knownInput?.input || {}, "command");
|
|
6434
7640
|
if (command) {
|
|
6435
7641
|
push(base({
|
|
@@ -6470,7 +7676,7 @@ async function parseGrokSessionFile(filePath, options) {
|
|
|
6470
7676
|
},
|
|
6471
7677
|
refs: stringRefs({ sourceId: `${toolId}:completed` })
|
|
6472
7678
|
}), lineNumber, type, "tool.completed");
|
|
6473
|
-
if (
|
|
7679
|
+
if (isShellTool2(pending?.name || toolName)) {
|
|
6474
7680
|
push(base({
|
|
6475
7681
|
ts,
|
|
6476
7682
|
type: success ? "command.completed" : "command.failed",
|
|
@@ -6527,7 +7733,7 @@ async function parseGrokSessionFile(filePath, options) {
|
|
|
6527
7733
|
nextLine += 1;
|
|
6528
7734
|
}
|
|
6529
7735
|
}
|
|
6530
|
-
const hunkPath =
|
|
7736
|
+
const hunkPath = path14.join(sessionDir, "hunk_records.jsonl");
|
|
6531
7737
|
const hunkActivities = await loadHunkFileActivities(hunkPath, cwd);
|
|
6532
7738
|
if (hunkActivities.length > 0) {
|
|
6533
7739
|
for (const event of events) {
|
|
@@ -6591,9 +7797,9 @@ async function parseGrokSessionFile(filePath, options) {
|
|
|
6591
7797
|
return events;
|
|
6592
7798
|
}
|
|
6593
7799
|
function resolveSessionDir(filePath) {
|
|
6594
|
-
const base =
|
|
7800
|
+
const base = path14.basename(filePath);
|
|
6595
7801
|
if (base === "summary.json" || base === "events.jsonl" || base === "signals.json") {
|
|
6596
|
-
return
|
|
7802
|
+
return path14.dirname(filePath);
|
|
6597
7803
|
}
|
|
6598
7804
|
if (/^[0-9a-f]{8}-[0-9a-f-]{27,}$/i.test(base)) {
|
|
6599
7805
|
return filePath;
|
|
@@ -6616,7 +7822,7 @@ function findPendingToolId(pending, toolName) {
|
|
|
6616
7822
|
}
|
|
6617
7823
|
return void 0;
|
|
6618
7824
|
}
|
|
6619
|
-
function
|
|
7825
|
+
function isShellTool2(name) {
|
|
6620
7826
|
if (!name) {
|
|
6621
7827
|
return false;
|
|
6622
7828
|
}
|
|
@@ -6625,7 +7831,7 @@ function isShellTool(name) {
|
|
|
6625
7831
|
}
|
|
6626
7832
|
function fileActivitiesFromTool(toolName, input, ts, cwd) {
|
|
6627
7833
|
const lower = toolName.toLowerCase();
|
|
6628
|
-
if (
|
|
7834
|
+
if (isShellTool2(toolName)) {
|
|
6629
7835
|
const command = stringField(input, "command");
|
|
6630
7836
|
if (command) {
|
|
6631
7837
|
return fileActivitiesFromShellCommand(command, ts, cwd, cwd);
|
|
@@ -6679,17 +7885,17 @@ function fileActivitiesFromTool(toolName, input, ts, cwd) {
|
|
|
6679
7885
|
return [];
|
|
6680
7886
|
}
|
|
6681
7887
|
function displayPath(filePath, cwd) {
|
|
6682
|
-
if (!cwd || !
|
|
7888
|
+
if (!cwd || !path14.isAbsolute(filePath)) {
|
|
6683
7889
|
return filePath;
|
|
6684
7890
|
}
|
|
6685
|
-
const relative =
|
|
6686
|
-
return relative && !relative.startsWith("..") && !
|
|
7891
|
+
const relative = path14.relative(cwd, filePath);
|
|
7892
|
+
return relative && !relative.startsWith("..") && !path14.isAbsolute(relative) ? relative : filePath;
|
|
6687
7893
|
}
|
|
6688
7894
|
async function loadToolInputsFromUpdates(updatesPath) {
|
|
6689
7895
|
const map = /* @__PURE__ */ new Map();
|
|
6690
7896
|
let text = "";
|
|
6691
7897
|
try {
|
|
6692
|
-
text = await
|
|
7898
|
+
text = await readFile9(updatesPath, "utf8");
|
|
6693
7899
|
} catch {
|
|
6694
7900
|
return map;
|
|
6695
7901
|
}
|
|
@@ -6760,7 +7966,7 @@ async function loadTurnUsagesFromUpdates(updatesPath) {
|
|
|
6760
7966
|
const turns = [];
|
|
6761
7967
|
let text = "";
|
|
6762
7968
|
try {
|
|
6763
|
-
text = await
|
|
7969
|
+
text = await readFile9(updatesPath, "utf8");
|
|
6764
7970
|
} catch {
|
|
6765
7971
|
return turns;
|
|
6766
7972
|
}
|
|
@@ -6849,7 +8055,7 @@ async function loadHunkFileActivities(hunkPath, cwd) {
|
|
|
6849
8055
|
const activities = [];
|
|
6850
8056
|
let text = "";
|
|
6851
8057
|
try {
|
|
6852
|
-
text = await
|
|
8058
|
+
text = await readFile9(hunkPath, "utf8");
|
|
6853
8059
|
} catch {
|
|
6854
8060
|
return activities;
|
|
6855
8061
|
}
|
|
@@ -6891,7 +8097,7 @@ async function loadHunkFileActivities(hunkPath, cwd) {
|
|
|
6891
8097
|
}
|
|
6892
8098
|
return activities;
|
|
6893
8099
|
}
|
|
6894
|
-
var grokHandler = (msg) => hookHandler(
|
|
8100
|
+
var grokHandler = (msg) => hookHandler(SOURCE_ID2, msg);
|
|
6895
8101
|
function hookConfig5() {
|
|
6896
8102
|
const anyTool = [{ matcher: ".*", hooks: [grokHandler("Reporting tool activity")] }];
|
|
6897
8103
|
return {
|
|
@@ -6913,9 +8119,9 @@ function hookConfig5() {
|
|
|
6913
8119
|
}
|
|
6914
8120
|
function createGrokBuildAdapter() {
|
|
6915
8121
|
return {
|
|
6916
|
-
id:
|
|
8122
|
+
id: SOURCE_ID2,
|
|
6917
8123
|
label: "Grok Build",
|
|
6918
|
-
agentName:
|
|
8124
|
+
agentName: AGENT_NAME2,
|
|
6919
8125
|
kind: "agent",
|
|
6920
8126
|
detectPath(home, env) {
|
|
6921
8127
|
return grokHome(home, env);
|
|
@@ -6924,7 +8130,7 @@ function createGrokBuildAdapter() {
|
|
|
6924
8130
|
return grokHooksPath(home, env);
|
|
6925
8131
|
},
|
|
6926
8132
|
async isInstalled(home, env) {
|
|
6927
|
-
return isHooksJsonInstalled(grokHooksPath(home, env),
|
|
8133
|
+
return isHooksJsonInstalled(grokHooksPath(home, env), HOOK_COMMAND2);
|
|
6928
8134
|
},
|
|
6929
8135
|
installEntries(home, env) {
|
|
6930
8136
|
return [{
|
|
@@ -6941,8 +8147,8 @@ function createGrokBuildAdapter() {
|
|
|
6941
8147
|
}
|
|
6942
8148
|
|
|
6943
8149
|
// src/adapters/kimi-code.ts
|
|
6944
|
-
import { readFile as
|
|
6945
|
-
import
|
|
8150
|
+
import { readFile as readFile10 } from "node:fs/promises";
|
|
8151
|
+
import path15 from "node:path";
|
|
6946
8152
|
|
|
6947
8153
|
// src/lib/toml-hooks.ts
|
|
6948
8154
|
init_fs();
|
|
@@ -7089,20 +8295,20 @@ function tomlNumberField(body, key) {
|
|
|
7089
8295
|
}
|
|
7090
8296
|
|
|
7091
8297
|
// src/adapters/kimi-code.ts
|
|
7092
|
-
var
|
|
7093
|
-
var
|
|
8298
|
+
var HOOK_COMMAND3 = "vibetime hook --agent kimi-code";
|
|
8299
|
+
var HOOK_TIMEOUT_SECONDS2 = 10;
|
|
7094
8300
|
function kimiCodeHome(home, env) {
|
|
7095
8301
|
const override = env?.KIMI_CODE_HOME || env?.KIMI_HOME;
|
|
7096
8302
|
if (override && override.trim()) {
|
|
7097
|
-
return
|
|
8303
|
+
return path15.resolve(override);
|
|
7098
8304
|
}
|
|
7099
|
-
return
|
|
8305
|
+
return path15.join(home, ".kimi-code");
|
|
7100
8306
|
}
|
|
7101
8307
|
function kimiCodeSessionsDir(home, env) {
|
|
7102
|
-
return
|
|
8308
|
+
return path15.join(kimiCodeHome(home, env), "sessions");
|
|
7103
8309
|
}
|
|
7104
8310
|
function kimiCodeConfigPath(home, env) {
|
|
7105
|
-
return
|
|
8311
|
+
return path15.join(kimiCodeHome(home, env), "config.toml");
|
|
7106
8312
|
}
|
|
7107
8313
|
function kimiHookRules() {
|
|
7108
8314
|
const events = [
|
|
@@ -7119,8 +8325,8 @@ function kimiHookRules() {
|
|
|
7119
8325
|
];
|
|
7120
8326
|
return events.map((event) => ({
|
|
7121
8327
|
event,
|
|
7122
|
-
command:
|
|
7123
|
-
timeout:
|
|
8328
|
+
command: HOOK_COMMAND3,
|
|
8329
|
+
timeout: HOOK_TIMEOUT_SECONDS2
|
|
7124
8330
|
}));
|
|
7125
8331
|
}
|
|
7126
8332
|
function baseKimiEvent(event) {
|
|
@@ -7179,10 +8385,10 @@ function sessionIdFromWirePath(filePath) {
|
|
|
7179
8385
|
return match?.[1];
|
|
7180
8386
|
}
|
|
7181
8387
|
async function readSessionState(filePath) {
|
|
7182
|
-
const sessionDir =
|
|
7183
|
-
const statePath =
|
|
8388
|
+
const sessionDir = path15.dirname(path15.dirname(path15.dirname(filePath)));
|
|
8389
|
+
const statePath = path15.join(sessionDir, "state.json");
|
|
7184
8390
|
try {
|
|
7185
|
-
const text = await
|
|
8391
|
+
const text = await readFile10(statePath, "utf8");
|
|
7186
8392
|
const raw = JSON.parse(text);
|
|
7187
8393
|
if (!isPlainObject(raw)) {
|
|
7188
8394
|
return {};
|
|
@@ -7197,15 +8403,15 @@ async function readSessionState(filePath) {
|
|
|
7197
8403
|
}
|
|
7198
8404
|
}
|
|
7199
8405
|
async function parseKimiCodeSessionFile(filePath, options) {
|
|
7200
|
-
if (
|
|
8406
|
+
if (path15.basename(filePath) !== "wire.jsonl") {
|
|
7201
8407
|
return [];
|
|
7202
8408
|
}
|
|
7203
|
-
const text = await
|
|
8409
|
+
const text = await readFile10(filePath, "utf8");
|
|
7204
8410
|
const lines = text.split("\n").filter(Boolean);
|
|
7205
8411
|
const stateMeta = await readSessionState(filePath);
|
|
7206
8412
|
let sessionId = stateMeta.sessionId || sessionIdFromWirePath(filePath);
|
|
7207
8413
|
let cwd = stateMeta.cwd;
|
|
7208
|
-
let project = cwd ?
|
|
8414
|
+
let project = cwd ? path15.basename(cwd) : void 0;
|
|
7209
8415
|
let model;
|
|
7210
8416
|
let provider;
|
|
7211
8417
|
let reasoningEffort;
|
|
@@ -7251,7 +8457,7 @@ async function parseKimiCodeSessionFile(filePath, options) {
|
|
|
7251
8457
|
const disclosedCwd = stringField(disclosure, "cwd");
|
|
7252
8458
|
if (disclosedCwd) {
|
|
7253
8459
|
cwd = disclosedCwd;
|
|
7254
|
-
project =
|
|
8460
|
+
project = path15.basename(disclosedCwd);
|
|
7255
8461
|
}
|
|
7256
8462
|
continue;
|
|
7257
8463
|
}
|
|
@@ -7565,7 +8771,7 @@ function createKimiCodeAdapter() {
|
|
|
7565
8771
|
return kimiCodeConfigPath(home, env);
|
|
7566
8772
|
},
|
|
7567
8773
|
async isInstalled(home, env) {
|
|
7568
|
-
return hasTomlHookCommand(kimiCodeConfigPath(home, env),
|
|
8774
|
+
return hasTomlHookCommand(kimiCodeConfigPath(home, env), HOOK_COMMAND3);
|
|
7569
8775
|
},
|
|
7570
8776
|
installEntries(home, env) {
|
|
7571
8777
|
return [{
|
|
@@ -7582,8 +8788,8 @@ function createKimiCodeAdapter() {
|
|
|
7582
8788
|
}
|
|
7583
8789
|
|
|
7584
8790
|
// src/adapters/opencode.ts
|
|
7585
|
-
import
|
|
7586
|
-
import
|
|
8791
|
+
import os7 from "node:os";
|
|
8792
|
+
import path16 from "node:path";
|
|
7587
8793
|
async function parseOpenCodeSessionFile(dbPath, options) {
|
|
7588
8794
|
const { DatabaseSync } = await import("node:sqlite");
|
|
7589
8795
|
if (!dbPath.endsWith(".db")) {
|
|
@@ -7642,7 +8848,7 @@ async function parseOpenCodeSessionFile(dbPath, options) {
|
|
|
7642
8848
|
const isSubagent = sessionId !== rawSessionId;
|
|
7643
8849
|
const sessionEventStart = events.length;
|
|
7644
8850
|
const cwd = session.directory || session.path || void 0;
|
|
7645
|
-
const project = cwd ?
|
|
8851
|
+
const project = cwd ? path16.basename(cwd) : void 0;
|
|
7646
8852
|
const sessionTs = msToIso(session.time_created);
|
|
7647
8853
|
events.push(baseOpenCodeEvent({
|
|
7648
8854
|
ts: sessionTs,
|
|
@@ -7750,7 +8956,7 @@ async function parseOpenCodeSessionFile(dbPath, options) {
|
|
|
7750
8956
|
const provider = currentProvider;
|
|
7751
8957
|
const pathObj = objectField(info, "path");
|
|
7752
8958
|
const assistantCwd = stringField(pathObj, "cwd") || cwd;
|
|
7753
|
-
const assistantProject = assistantCwd ?
|
|
8959
|
+
const assistantProject = assistantCwd ? path16.basename(assistantCwd) : project;
|
|
7754
8960
|
const completedTs = numberField(objectField(info, "time"), "completed");
|
|
7755
8961
|
const createdTs = timeCreated;
|
|
7756
8962
|
const tokens = opencodeUsageFromInfo(info);
|
|
@@ -8024,33 +9230,33 @@ function opencodeUsageFromInfo(info) {
|
|
|
8024
9230
|
function opencodeConfigDir(home, env) {
|
|
8025
9231
|
const override = env?.OPENCODE_CONFIG_DIR;
|
|
8026
9232
|
if (override && override.trim()) {
|
|
8027
|
-
return
|
|
9233
|
+
return path16.resolve(override);
|
|
8028
9234
|
}
|
|
8029
9235
|
const xdgConfig = env?.XDG_CONFIG_HOME;
|
|
8030
9236
|
if (xdgConfig && xdgConfig.trim()) {
|
|
8031
|
-
return
|
|
9237
|
+
return path16.join(path16.resolve(xdgConfig), "opencode");
|
|
8032
9238
|
}
|
|
8033
|
-
return
|
|
9239
|
+
return path16.join(home, ".config", "opencode");
|
|
8034
9240
|
}
|
|
8035
9241
|
function opencodeDataCandidates(home, env) {
|
|
8036
9242
|
const xdgData = env?.XDG_DATA_HOME;
|
|
8037
|
-
const primary = xdgData && xdgData.trim() ?
|
|
8038
|
-
return [primary,
|
|
9243
|
+
const primary = xdgData && xdgData.trim() ? path16.join(path16.resolve(xdgData), "opencode", "opencode.db") : path16.join(home, ".local", "share", "opencode", "opencode.db");
|
|
9244
|
+
return [primary, path16.join(home, ".opencode", "opencode.db")];
|
|
8039
9245
|
}
|
|
8040
|
-
async function opencodeBackfillFiles(sourceRoot, home =
|
|
8041
|
-
const { stat:
|
|
9246
|
+
async function opencodeBackfillFiles(sourceRoot, home = os7.homedir(), env) {
|
|
9247
|
+
const { stat: stat16 } = await import("node:fs/promises");
|
|
8042
9248
|
if (sourceRoot) {
|
|
8043
9249
|
if (!sourceRoot.endsWith(".db")) {
|
|
8044
9250
|
return [];
|
|
8045
9251
|
}
|
|
8046
|
-
const info = await
|
|
9252
|
+
const info = await stat16(sourceRoot).catch(() => null);
|
|
8047
9253
|
if (!info) {
|
|
8048
9254
|
return [];
|
|
8049
9255
|
}
|
|
8050
9256
|
return [{ path: sourceRoot, modifiedAt: info.mtime.toISOString() }];
|
|
8051
9257
|
}
|
|
8052
9258
|
for (const candidatePath of opencodeDataCandidates(home, env)) {
|
|
8053
|
-
const info = await
|
|
9259
|
+
const info = await stat16(candidatePath).catch(() => null);
|
|
8054
9260
|
if (info) {
|
|
8055
9261
|
return [{ path: candidatePath, modifiedAt: info.mtime.toISOString() }];
|
|
8056
9262
|
}
|
|
@@ -8125,12 +9331,12 @@ function createOpenCodeAdapter() {
|
|
|
8125
9331
|
return opencodeConfigDir(home, env);
|
|
8126
9332
|
},
|
|
8127
9333
|
installedPath(home, env) {
|
|
8128
|
-
return
|
|
9334
|
+
return path16.join(opencodeConfigDir(home, env), PLUGIN_PATH);
|
|
8129
9335
|
},
|
|
8130
9336
|
async isInstalled(home, env) {
|
|
8131
9337
|
try {
|
|
8132
9338
|
const { pathExists: pathExists2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
8133
|
-
return await pathExists2(
|
|
9339
|
+
return await pathExists2(path16.join(opencodeConfigDir(home, env), PLUGIN_PATH)) || await pathExists2(path16.join(".opencode", PLUGIN_PATH));
|
|
8134
9340
|
} catch {
|
|
8135
9341
|
return false;
|
|
8136
9342
|
}
|
|
@@ -8138,7 +9344,7 @@ function createOpenCodeAdapter() {
|
|
|
8138
9344
|
installEntries(home, env) {
|
|
8139
9345
|
return [{
|
|
8140
9346
|
kind: "file",
|
|
8141
|
-
path:
|
|
9347
|
+
path: path16.join(opencodeConfigDir(home, env), PLUGIN_PATH),
|
|
8142
9348
|
content: opencodePluginContent()
|
|
8143
9349
|
}];
|
|
8144
9350
|
},
|
|
@@ -8150,14 +9356,37 @@ function createOpenCodeAdapter() {
|
|
|
8150
9356
|
}
|
|
8151
9357
|
|
|
8152
9358
|
// src/adapters/pi.ts
|
|
8153
|
-
import { readdir as
|
|
8154
|
-
import
|
|
8155
|
-
import
|
|
9359
|
+
import { readdir as readdir8, readFile as readFile11, stat as stat9 } from "node:fs/promises";
|
|
9360
|
+
import os8 from "node:os";
|
|
9361
|
+
import path17 from "node:path";
|
|
8156
9362
|
init_fs();
|
|
9363
|
+
function piHostSessionIdFromBasename(basename) {
|
|
9364
|
+
const withoutExt = basename.endsWith(".jsonl") ? basename.slice(0, -".jsonl".length) : basename;
|
|
9365
|
+
if (!withoutExt || withoutExt === "session") {
|
|
9366
|
+
return void 0;
|
|
9367
|
+
}
|
|
9368
|
+
const underscore = withoutExt.indexOf("_");
|
|
9369
|
+
if (underscore <= 0 || underscore === withoutExt.length - 1) {
|
|
9370
|
+
return withoutExt;
|
|
9371
|
+
}
|
|
9372
|
+
return withoutExt.slice(underscore + 1);
|
|
9373
|
+
}
|
|
9374
|
+
function resolvePiBackfillGroupId(filePath) {
|
|
9375
|
+
const normalized = filePath.replaceAll("\\", "/");
|
|
9376
|
+
const base = path17.basename(filePath);
|
|
9377
|
+
const nest = normalized.match(/([^/]+)\/[^/]+\/run-\d+\/session\.jsonl$/);
|
|
9378
|
+
if (nest) {
|
|
9379
|
+
return piHostSessionIdFromBasename(nest[1]);
|
|
9380
|
+
}
|
|
9381
|
+
if (base.endsWith(".jsonl") && base !== "session.jsonl") {
|
|
9382
|
+
return piHostSessionIdFromBasename(base);
|
|
9383
|
+
}
|
|
9384
|
+
return void 0;
|
|
9385
|
+
}
|
|
8157
9386
|
function parsePiSubagentLink(filePath, headerParentSession) {
|
|
8158
9387
|
if (headerParentSession) {
|
|
8159
|
-
const parentFile =
|
|
8160
|
-
const parentSessionId2 = parentFile ?
|
|
9388
|
+
const parentFile = path17.isAbsolute(headerParentSession) ? headerParentSession : void 0;
|
|
9389
|
+
const parentSessionId2 = parentFile ? path17.basename(parentFile, ".jsonl") : headerParentSession;
|
|
8161
9390
|
return { parentSessionId: parentSessionId2, parentSessionFile: parentFile, explicit: true };
|
|
8162
9391
|
}
|
|
8163
9392
|
const normalized = filePath.replaceAll("\\", "/");
|
|
@@ -8170,20 +9399,20 @@ function parsePiSubagentLink(filePath, headerParentSession) {
|
|
|
8170
9399
|
return void 0;
|
|
8171
9400
|
}
|
|
8172
9401
|
const parentSessionId = parentBasename.endsWith(".jsonl") ? parentBasename.slice(0, -".jsonl".length) : parentBasename;
|
|
8173
|
-
const parentDir =
|
|
8174
|
-
const parentSessionFile =
|
|
9402
|
+
const parentDir = path17.dirname(path17.dirname(path17.dirname(filePath)));
|
|
9403
|
+
const parentSessionFile = path17.join(path17.dirname(parentDir), `${parentBasename}.jsonl`);
|
|
8175
9404
|
return { parentSessionId, parentSessionFile, explicit: false };
|
|
8176
9405
|
}
|
|
8177
9406
|
async function resolveParentContext(link, options) {
|
|
8178
9407
|
if (link.parentSessionFile) {
|
|
8179
9408
|
try {
|
|
8180
|
-
const text = await
|
|
9409
|
+
const text = await readFile11(link.parentSessionFile, "utf8");
|
|
8181
9410
|
const firstLine = text.split("\n").find((line) => line.trim().length > 0);
|
|
8182
9411
|
const raw = firstLine ? parseJsonLine(firstLine) : void 0;
|
|
8183
9412
|
if (raw) {
|
|
8184
9413
|
const id = stringField(raw, "id");
|
|
8185
9414
|
const cwd = stringField(raw, "cwd");
|
|
8186
|
-
const project = cwd ?
|
|
9415
|
+
const project = cwd ? path17.basename(cwd) : void 0;
|
|
8187
9416
|
if (id || cwd || project) {
|
|
8188
9417
|
return { sessionId: id, cwd, project };
|
|
8189
9418
|
}
|
|
@@ -8232,7 +9461,7 @@ function rebuildEventIdentity2(event) {
|
|
|
8232
9461
|
};
|
|
8233
9462
|
}
|
|
8234
9463
|
async function parsePiSessionFile(filePath, options) {
|
|
8235
|
-
const text = await
|
|
9464
|
+
const text = await readFile11(filePath, "utf8");
|
|
8236
9465
|
const lines = text.split("\n").filter(Boolean);
|
|
8237
9466
|
let sessionId;
|
|
8238
9467
|
let cwd;
|
|
@@ -8264,7 +9493,7 @@ async function parsePiSessionFile(filePath, options) {
|
|
|
8264
9493
|
sessionId = stringField(raw, "id") || state.sessionId;
|
|
8265
9494
|
state.sessionId = sessionId || state.sessionId;
|
|
8266
9495
|
cwd = stringField(raw, "cwd") || cwd;
|
|
8267
|
-
project = cwd ?
|
|
9496
|
+
project = cwd ? path17.basename(cwd) : project;
|
|
8268
9497
|
headerParentSession = stringField(raw, "parentSession") || headerParentSession;
|
|
8269
9498
|
continue;
|
|
8270
9499
|
}
|
|
@@ -8471,7 +9700,7 @@ async function parsePiFile(filePath, options) {
|
|
|
8471
9700
|
async function parsePiWorkflowRunFile(filePath, options) {
|
|
8472
9701
|
let raw;
|
|
8473
9702
|
try {
|
|
8474
|
-
raw = JSON.parse(await
|
|
9703
|
+
raw = JSON.parse(await readFile11(filePath, "utf8"));
|
|
8475
9704
|
} catch {
|
|
8476
9705
|
return [];
|
|
8477
9706
|
}
|
|
@@ -8539,17 +9768,17 @@ async function parsePiWorkflowRunFile(filePath, options) {
|
|
|
8539
9768
|
return state.events.filter((event) => matchesBackfillFilters(event, options));
|
|
8540
9769
|
}
|
|
8541
9770
|
async function findPiSessionFileById(sessionId, options) {
|
|
8542
|
-
const sessionsDir = piSessionDir(
|
|
9771
|
+
const sessionsDir = piSessionDir(path17.resolve(stringOption(options.home) || os8.homedir()));
|
|
8543
9772
|
try {
|
|
8544
|
-
const projectDirs = await
|
|
9773
|
+
const projectDirs = await readdir8(sessionsDir, { withFileTypes: true });
|
|
8545
9774
|
for (const dir of projectDirs) {
|
|
8546
9775
|
if (!dir.isDirectory()) {
|
|
8547
9776
|
continue;
|
|
8548
9777
|
}
|
|
8549
|
-
const names = await
|
|
9778
|
+
const names = await readdir8(path17.join(sessionsDir, dir.name));
|
|
8550
9779
|
const hit = names.find((name) => name.endsWith(`_${sessionId}.jsonl`));
|
|
8551
9780
|
if (hit) {
|
|
8552
|
-
return
|
|
9781
|
+
return path17.join(sessionsDir, dir.name, hit);
|
|
8553
9782
|
}
|
|
8554
9783
|
}
|
|
8555
9784
|
} catch {
|
|
@@ -8791,29 +10020,48 @@ export default function (pi: ExtensionAPI) {
|
|
|
8791
10020
|
function piAgentDir(home, env) {
|
|
8792
10021
|
const override = env?.PI_CODING_AGENT_DIR;
|
|
8793
10022
|
if (override && override.trim()) {
|
|
8794
|
-
return
|
|
10023
|
+
return path17.resolve(override);
|
|
8795
10024
|
}
|
|
8796
|
-
return
|
|
10025
|
+
return path17.join(home, ".pi", "agent");
|
|
8797
10026
|
}
|
|
8798
10027
|
function piSessionDir(home, env) {
|
|
8799
10028
|
const override = env?.PI_CODING_AGENT_SESSION_DIR;
|
|
8800
10029
|
if (override && override.trim()) {
|
|
8801
|
-
return
|
|
10030
|
+
return path17.resolve(override);
|
|
8802
10031
|
}
|
|
8803
|
-
return
|
|
10032
|
+
return path17.join(piAgentDir(home, env), "sessions");
|
|
8804
10033
|
}
|
|
8805
10034
|
function piWorkflowProjectsDir(home) {
|
|
8806
|
-
return
|
|
10035
|
+
return path17.join(home, ".pi", "workflows", "projects");
|
|
10036
|
+
}
|
|
10037
|
+
async function workflowRunGroupId(filePath) {
|
|
10038
|
+
try {
|
|
10039
|
+
const raw = JSON.parse(await readFile11(filePath, "utf8"));
|
|
10040
|
+
if (!isPlainObject(raw)) {
|
|
10041
|
+
return void 0;
|
|
10042
|
+
}
|
|
10043
|
+
return stringField(raw, "sessionId");
|
|
10044
|
+
} catch {
|
|
10045
|
+
return void 0;
|
|
10046
|
+
}
|
|
8807
10047
|
}
|
|
8808
|
-
async function piBackfillFiles(sourceRoot, home =
|
|
10048
|
+
async function piBackfillFiles(sourceRoot, home = os8.homedir(), env) {
|
|
8809
10049
|
const lists = sourceRoot ? [await listFilesByExtensions(sourceRoot, [".jsonl", ".json"])] : await Promise.all([
|
|
8810
10050
|
listFilesByExtensions(piSessionDir(home, env), [".jsonl"]),
|
|
8811
10051
|
listFilesByExtensions(piWorkflowProjectsDir(home), [".json"])
|
|
8812
10052
|
]);
|
|
8813
10053
|
const files = lists.flat().sort();
|
|
8814
10054
|
return Promise.all(files.map(async (filePath) => {
|
|
8815
|
-
const info = await
|
|
8816
|
-
|
|
10055
|
+
const info = await stat9(filePath);
|
|
10056
|
+
let groupId = resolvePiBackfillGroupId(filePath);
|
|
10057
|
+
if (!groupId && filePath.endsWith(".json")) {
|
|
10058
|
+
groupId = await workflowRunGroupId(filePath);
|
|
10059
|
+
}
|
|
10060
|
+
return {
|
|
10061
|
+
path: filePath,
|
|
10062
|
+
modifiedAt: info.mtime.toISOString(),
|
|
10063
|
+
...groupId ? { groupId } : {}
|
|
10064
|
+
};
|
|
8817
10065
|
}));
|
|
8818
10066
|
}
|
|
8819
10067
|
function createPiAdapter() {
|
|
@@ -8826,12 +10074,12 @@ function createPiAdapter() {
|
|
|
8826
10074
|
return piAgentDir(home, env);
|
|
8827
10075
|
},
|
|
8828
10076
|
installedPath(home, env) {
|
|
8829
|
-
return
|
|
10077
|
+
return path17.join(piAgentDir(home, env), "extensions", "vibetime.ts");
|
|
8830
10078
|
},
|
|
8831
10079
|
async isInstalled(home, env) {
|
|
8832
10080
|
try {
|
|
8833
10081
|
const { pathExists: pathExists2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
8834
|
-
return await pathExists2(
|
|
10082
|
+
return await pathExists2(path17.join(piAgentDir(home, env), "extensions", "vibetime.ts"));
|
|
8835
10083
|
} catch {
|
|
8836
10084
|
return false;
|
|
8837
10085
|
}
|
|
@@ -8839,7 +10087,7 @@ function createPiAdapter() {
|
|
|
8839
10087
|
installEntries(home, env) {
|
|
8840
10088
|
return [{
|
|
8841
10089
|
kind: "file",
|
|
8842
|
-
path:
|
|
10090
|
+
path: path17.join(piAgentDir(home, env), "extensions", "vibetime.ts"),
|
|
8843
10091
|
content: piExtensionContent()
|
|
8844
10092
|
}];
|
|
8845
10093
|
},
|
|
@@ -8851,14 +10099,14 @@ function createPiAdapter() {
|
|
|
8851
10099
|
}
|
|
8852
10100
|
|
|
8853
10101
|
// src/adapters/qoder-cn.ts
|
|
8854
|
-
import { readdir as
|
|
8855
|
-
import
|
|
8856
|
-
import
|
|
10102
|
+
import { readdir as readdir9, readFile as readFile12, stat as stat10 } from "node:fs/promises";
|
|
10103
|
+
import os10 from "node:os";
|
|
10104
|
+
import path19 from "node:path";
|
|
8857
10105
|
|
|
8858
10106
|
// src/adapters/qoder-local-db.ts
|
|
8859
10107
|
import { access } from "node:fs/promises";
|
|
8860
|
-
import
|
|
8861
|
-
import
|
|
10108
|
+
import os9 from "node:os";
|
|
10109
|
+
import path18 from "node:path";
|
|
8862
10110
|
function takeQoderDbModelCall(calls, requestId, blockStart) {
|
|
8863
10111
|
if (requestId) {
|
|
8864
10112
|
const call = calls.byRequestId.get(requestId)?.shift();
|
|
@@ -8875,14 +10123,14 @@ function takeQoderDbModelCall(calls, requestId, blockStart) {
|
|
|
8875
10123
|
}
|
|
8876
10124
|
return calls.ordered.shift();
|
|
8877
10125
|
}
|
|
8878
|
-
function appDataRoot(appDirName, home =
|
|
10126
|
+
function appDataRoot(appDirName, home = os9.homedir()) {
|
|
8879
10127
|
if (process.platform === "darwin") {
|
|
8880
|
-
return
|
|
10128
|
+
return path18.join(home, "Library", "Application Support", appDirName);
|
|
8881
10129
|
}
|
|
8882
10130
|
if (process.platform === "win32") {
|
|
8883
|
-
return
|
|
10131
|
+
return path18.join(process.env.APPDATA || path18.join(home, "AppData", "Roaming"), appDirName);
|
|
8884
10132
|
}
|
|
8885
|
-
return
|
|
10133
|
+
return path18.join(home, ".config", appDirName);
|
|
8886
10134
|
}
|
|
8887
10135
|
function qoderLocalDbCandidates(appDirName) {
|
|
8888
10136
|
const candidates = [];
|
|
@@ -8892,8 +10140,8 @@ function qoderLocalDbCandidates(appDirName) {
|
|
|
8892
10140
|
}
|
|
8893
10141
|
const configRoot = appDataRoot(appDirName);
|
|
8894
10142
|
candidates.push(
|
|
8895
|
-
|
|
8896
|
-
|
|
10143
|
+
path18.join(configRoot, "SharedClientCache", "cache", "db", "local.db"),
|
|
10144
|
+
path18.join(configRoot, "SharedClientCache", "db", "local.db")
|
|
8897
10145
|
);
|
|
8898
10146
|
return candidates;
|
|
8899
10147
|
}
|
|
@@ -8902,7 +10150,7 @@ async function loadQoderIdeModelCatalog(appDirName, home) {
|
|
|
8902
10150
|
try {
|
|
8903
10151
|
const { DatabaseSync } = await import("node:sqlite");
|
|
8904
10152
|
const db = new DatabaseSync(
|
|
8905
|
-
|
|
10153
|
+
path18.join(appDataRoot(appDirName, home), "User", "globalStorage", "state.vscdb"),
|
|
8906
10154
|
{ readOnly: true }
|
|
8907
10155
|
);
|
|
8908
10156
|
try {
|
|
@@ -9044,7 +10292,7 @@ function resolveSessionPreferredModel(db, sessionId, modelMap) {
|
|
|
9044
10292
|
|
|
9045
10293
|
// src/adapters/qoder-cn.ts
|
|
9046
10294
|
function parseQoderCnPaths(filePath) {
|
|
9047
|
-
const parts = filePath.split(
|
|
10295
|
+
const parts = filePath.split(path19.sep);
|
|
9048
10296
|
const subagentsIdx = parts.lastIndexOf("subagents");
|
|
9049
10297
|
let sessionId = "";
|
|
9050
10298
|
let projectName = "";
|
|
@@ -9054,17 +10302,17 @@ function parseQoderCnPaths(filePath) {
|
|
|
9054
10302
|
sessionId = parts[subagentsIdx - 1];
|
|
9055
10303
|
projectName = parts[subagentsIdx - 2];
|
|
9056
10304
|
const projectsIdx = parts.lastIndexOf("projects");
|
|
9057
|
-
configDir2 = parts.slice(0, projectsIdx).join(
|
|
9058
|
-
mainTranscriptPath = [...parts.slice(0, subagentsIdx - 1), `${sessionId}.jsonl`].join(
|
|
10305
|
+
configDir2 = parts.slice(0, projectsIdx).join(path19.sep);
|
|
10306
|
+
mainTranscriptPath = [...parts.slice(0, subagentsIdx - 1), `${sessionId}.jsonl`].join(path19.sep);
|
|
9059
10307
|
} else {
|
|
9060
10308
|
const filename = parts.at(-1) || "";
|
|
9061
|
-
sessionId =
|
|
10309
|
+
sessionId = path19.basename(filename, ".jsonl");
|
|
9062
10310
|
projectName = parts.at(-2) || "";
|
|
9063
10311
|
if (projectName === "transcript") {
|
|
9064
10312
|
projectName = parts.at(-3) || "";
|
|
9065
10313
|
}
|
|
9066
10314
|
const projectsIdx = parts.lastIndexOf("projects");
|
|
9067
|
-
configDir2 = parts.slice(0, projectsIdx).join(
|
|
10315
|
+
configDir2 = parts.slice(0, projectsIdx).join(path19.sep);
|
|
9068
10316
|
}
|
|
9069
10317
|
return { configDir: configDir2, projectName, sessionId, mainTranscriptPath };
|
|
9070
10318
|
}
|
|
@@ -9087,7 +10335,7 @@ function rebuildEventIdentity3(event) {
|
|
|
9087
10335
|
}
|
|
9088
10336
|
async function parseModelNamesFromDynamicTexts(dynamicTextsPath) {
|
|
9089
10337
|
try {
|
|
9090
|
-
const content = await
|
|
10338
|
+
const content = await readFile12(dynamicTextsPath, "utf8");
|
|
9091
10339
|
const json = JSON.parse(content);
|
|
9092
10340
|
const texts = json.texts || {};
|
|
9093
10341
|
const map = {};
|
|
@@ -9103,10 +10351,10 @@ async function parseModelNamesFromDynamicTexts(dynamicTextsPath) {
|
|
|
9103
10351
|
}
|
|
9104
10352
|
}
|
|
9105
10353
|
async function loadQoderCnModelNames(configDir2, home) {
|
|
9106
|
-
const map = await parseModelNamesFromDynamicTexts(
|
|
10354
|
+
const map = await parseModelNamesFromDynamicTexts(path19.join(configDir2, ".auth", "dynamic-texts.json"));
|
|
9107
10355
|
const siblingConfigDir = configDir2.replace(/\.qoder-cn$/, ".qoder");
|
|
9108
10356
|
if (siblingConfigDir !== configDir2) {
|
|
9109
|
-
const siblingMap = await parseModelNamesFromDynamicTexts(
|
|
10357
|
+
const siblingMap = await parseModelNamesFromDynamicTexts(path19.join(siblingConfigDir, ".auth", "dynamic-texts.json"));
|
|
9110
10358
|
for (const [key, val] of Object.entries(siblingMap)) {
|
|
9111
10359
|
if (!(key in map)) {
|
|
9112
10360
|
map[key] = val;
|
|
@@ -9125,15 +10373,15 @@ async function loadQoderCnModelNames(configDir2, home) {
|
|
|
9125
10373
|
}
|
|
9126
10374
|
async function loadQoderCnSegmentModelCalls(filePath, isSubagentSession, modelMap) {
|
|
9127
10375
|
const { configDir: configDir2, projectName, sessionId } = parseQoderCnPaths(filePath);
|
|
9128
|
-
const segmentsPath =
|
|
10376
|
+
const segmentsPath = path19.join(configDir2, "logs", "sessions", projectName, sessionId, "segments");
|
|
9129
10377
|
const modelCalls = [];
|
|
9130
10378
|
try {
|
|
9131
|
-
const files = await
|
|
10379
|
+
const files = await readdir9(segmentsPath);
|
|
9132
10380
|
for (const file of files) {
|
|
9133
10381
|
if (!file.endsWith(".jsonl")) {
|
|
9134
10382
|
continue;
|
|
9135
10383
|
}
|
|
9136
|
-
const content = await
|
|
10384
|
+
const content = await readFile12(path19.join(segmentsPath, file), "utf8");
|
|
9137
10385
|
let currentTurnIsSubagent = false;
|
|
9138
10386
|
for (const line of content.split("\n").filter(Boolean)) {
|
|
9139
10387
|
const raw = parseJsonLine(line);
|
|
@@ -9167,7 +10415,7 @@ async function loadQoderCnSegmentModelCalls(filePath, isSubagentSession, modelMa
|
|
|
9167
10415
|
return modelCalls.filter((call) => call.isSubagent === isSubagentSession);
|
|
9168
10416
|
}
|
|
9169
10417
|
async function parseQoderCnSessionFile(filePath, options) {
|
|
9170
|
-
const text = await
|
|
10418
|
+
const text = await readFile12(filePath, "utf8");
|
|
9171
10419
|
const lines = text.split("\n").filter(Boolean);
|
|
9172
10420
|
const parsedPaths = parseQoderCnPaths(filePath);
|
|
9173
10421
|
const { configDir: configDir2 } = parsedPaths;
|
|
@@ -9178,7 +10426,7 @@ async function parseQoderCnSessionFile(filePath, options) {
|
|
|
9178
10426
|
let cwd;
|
|
9179
10427
|
let project = projectContext.project;
|
|
9180
10428
|
let model;
|
|
9181
|
-
const home =
|
|
10429
|
+
const home = path19.resolve(stringOption(options.home) || os10.homedir());
|
|
9182
10430
|
const modelMap = await loadQoderCnModelNames(configDir2, home);
|
|
9183
10431
|
const isSubagentSession = filePath.includes("subagents");
|
|
9184
10432
|
const segmentModelCalls = await loadQoderCnSegmentModelCalls(filePath, isSubagentSession, modelMap);
|
|
@@ -9213,7 +10461,7 @@ async function parseQoderCnSessionFile(filePath, options) {
|
|
|
9213
10461
|
sessionId = stringField(raw, "sessionId") || sessionId;
|
|
9214
10462
|
state.sessionId = sessionId;
|
|
9215
10463
|
cwd = stringField(raw, "cwd") || cwd;
|
|
9216
|
-
project = projectContext.project || (cwd ?
|
|
10464
|
+
project = projectContext.project || (cwd ? path19.basename(cwd) : project || await qoderCnProjectFromFilePath(filePath, options));
|
|
9217
10465
|
if (!ts) {
|
|
9218
10466
|
continue;
|
|
9219
10467
|
}
|
|
@@ -9532,10 +10780,11 @@ async function parseQoderCnSessionFile(filePath, options) {
|
|
|
9532
10780
|
const { sessionId: parentSessionId, mainTranscriptPath } = parseQoderCnPaths(filePath);
|
|
9533
10781
|
if (parentSessionId && mainTranscriptPath) {
|
|
9534
10782
|
const parentSourcePathHash = `sha256:${createStableHash(mainTranscriptPath)}`;
|
|
9535
|
-
return validEvents.map((event) => {
|
|
10783
|
+
return withoutSubagentTurnEvents(validEvents).map((event) => {
|
|
9536
10784
|
const mapped = {
|
|
9537
10785
|
...event,
|
|
9538
10786
|
sessionId: parentSessionId,
|
|
10787
|
+
turnId: void 0,
|
|
9539
10788
|
refs: {
|
|
9540
10789
|
...event.refs,
|
|
9541
10790
|
sourcePathHash: parentSourcePathHash,
|
|
@@ -9548,11 +10797,12 @@ async function parseQoderCnSessionFile(filePath, options) {
|
|
|
9548
10797
|
}
|
|
9549
10798
|
dbModelCalls ??= await loadQoderDbModelCalls("QoderCN", parsedPaths.sessionId, modelMap);
|
|
9550
10799
|
if (dbModelCalls.rootSessionId) {
|
|
9551
|
-
const parentPath =
|
|
10800
|
+
const parentPath = path19.join(path19.dirname(filePath), `${dbModelCalls.rootSessionId}.jsonl`);
|
|
9552
10801
|
const parentSourcePathHash = `sha256:${createStableHash(parentPath)}`;
|
|
9553
|
-
return validEvents.map((event) => rebuildEventIdentity3({
|
|
10802
|
+
return withoutSubagentTurnEvents(validEvents).map((event) => rebuildEventIdentity3({
|
|
9554
10803
|
...event,
|
|
9555
10804
|
sessionId: dbModelCalls.rootSessionId,
|
|
10805
|
+
turnId: void 0,
|
|
9556
10806
|
refs: {
|
|
9557
10807
|
...event.refs,
|
|
9558
10808
|
sourcePathHash: parentSourcePathHash,
|
|
@@ -9700,28 +10950,28 @@ function extractQoderAttachedPrompt(content) {
|
|
|
9700
10950
|
}
|
|
9701
10951
|
async function qoderCnProjectContextFromLines(filePath, lines, options, configDir2) {
|
|
9702
10952
|
const { projectName: projectDir, sessionId } = parseQoderCnPaths(filePath);
|
|
9703
|
-
const isSubagent = filePath.includes(`${
|
|
10953
|
+
const isSubagent = filePath.includes(`${path19.sep}subagents${path19.sep}`);
|
|
9704
10954
|
const inherited = isSubagent ? await readPersistedSessionContextFromOptions(options, sessionId) : void 0;
|
|
9705
10955
|
let cwds = [];
|
|
9706
10956
|
for (const line of lines) {
|
|
9707
10957
|
const raw = parseJsonLine(line);
|
|
9708
10958
|
const cwd = raw ? stringField(raw, "cwd") : void 0;
|
|
9709
|
-
if (cwd &&
|
|
10959
|
+
if (cwd && path19.isAbsolute(cwd)) {
|
|
9710
10960
|
cwds.push(cwd);
|
|
9711
10961
|
}
|
|
9712
10962
|
}
|
|
9713
10963
|
if (isSubagent) {
|
|
9714
|
-
if (inherited?.cwd &&
|
|
10964
|
+
if (inherited?.cwd && path19.isAbsolute(inherited.cwd)) {
|
|
9715
10965
|
cwds = [inherited.cwd];
|
|
9716
10966
|
} else {
|
|
9717
|
-
const parentSessionPath =
|
|
10967
|
+
const parentSessionPath = path19.join(configDir2, "projects", projectDir, `${sessionId}.jsonl`);
|
|
9718
10968
|
try {
|
|
9719
|
-
const parentText = await
|
|
10969
|
+
const parentText = await readFile12(parentSessionPath, "utf8");
|
|
9720
10970
|
const parentCwds = [];
|
|
9721
10971
|
for (const line of parentText.split("\n").filter(Boolean)) {
|
|
9722
10972
|
const raw = parseJsonLine(line);
|
|
9723
10973
|
const cwd = raw ? stringField(raw, "cwd") : void 0;
|
|
9724
|
-
if (cwd &&
|
|
10974
|
+
if (cwd && path19.isAbsolute(cwd)) {
|
|
9725
10975
|
parentCwds.push(cwd);
|
|
9726
10976
|
}
|
|
9727
10977
|
}
|
|
@@ -9733,7 +10983,7 @@ async function qoderCnProjectContextFromLines(filePath, lines, options, configDi
|
|
|
9733
10983
|
}
|
|
9734
10984
|
}
|
|
9735
10985
|
const root = await gitRootFromCwds2(cwds) || qoderCnProjectRootFromCwds(projectDir, cwds);
|
|
9736
|
-
const project = inherited?.project || (cwds.length > 0 ?
|
|
10986
|
+
const project = inherited?.project || (cwds.length > 0 ? path19.basename(cwds[0]) : root ? path19.basename(root) : await qoderCnProjectFromFilePath(filePath, options));
|
|
9737
10987
|
return {
|
|
9738
10988
|
project,
|
|
9739
10989
|
workspaceId: createWorkspaceId({ projectName: project, repoRoot: root })
|
|
@@ -9742,15 +10992,15 @@ async function qoderCnProjectContextFromLines(filePath, lines, options, configDi
|
|
|
9742
10992
|
async function gitRootFromCwds2(cwds) {
|
|
9743
10993
|
const seen = /* @__PURE__ */ new Set();
|
|
9744
10994
|
for (const cwd of cwds) {
|
|
9745
|
-
let current =
|
|
10995
|
+
let current = path19.resolve(cwd);
|
|
9746
10996
|
while (!seen.has(current)) {
|
|
9747
10997
|
seen.add(current);
|
|
9748
10998
|
try {
|
|
9749
|
-
await
|
|
10999
|
+
await stat10(path19.join(current, ".git"));
|
|
9750
11000
|
return current;
|
|
9751
11001
|
} catch {
|
|
9752
11002
|
}
|
|
9753
|
-
const parent =
|
|
11003
|
+
const parent = path19.dirname(current);
|
|
9754
11004
|
if (parent === current) {
|
|
9755
11005
|
break;
|
|
9756
11006
|
}
|
|
@@ -9761,12 +11011,12 @@ async function gitRootFromCwds2(cwds) {
|
|
|
9761
11011
|
}
|
|
9762
11012
|
function qoderCnProjectRootFromCwds(projectDir, cwds) {
|
|
9763
11013
|
for (const cwd of cwds) {
|
|
9764
|
-
let current =
|
|
11014
|
+
let current = path19.resolve(cwd);
|
|
9765
11015
|
while (true) {
|
|
9766
11016
|
if (encodeQoderCnProjectPath(current) === projectDir) {
|
|
9767
11017
|
return current;
|
|
9768
11018
|
}
|
|
9769
|
-
const parent =
|
|
11019
|
+
const parent = path19.dirname(current);
|
|
9770
11020
|
if (parent === current) {
|
|
9771
11021
|
break;
|
|
9772
11022
|
}
|
|
@@ -9776,14 +11026,14 @@ function qoderCnProjectRootFromCwds(projectDir, cwds) {
|
|
|
9776
11026
|
return void 0;
|
|
9777
11027
|
}
|
|
9778
11028
|
function encodeQoderCnProjectPath(value) {
|
|
9779
|
-
return
|
|
11029
|
+
return path19.resolve(value).split(path19.sep).join("-").replaceAll("_", "-");
|
|
9780
11030
|
}
|
|
9781
11031
|
async function qoderCnProjectFromFilePath(filePath, options) {
|
|
9782
|
-
const projectDir =
|
|
9783
|
-
const home = options ?
|
|
11032
|
+
const projectDir = path19.basename(path19.dirname(filePath));
|
|
11033
|
+
const home = options ? path19.resolve(stringOption(options.home) || os10.homedir()) : os10.homedir();
|
|
9784
11034
|
const resolved = await resolveQoderCnProjectPath(projectDir, home);
|
|
9785
11035
|
if (resolved) {
|
|
9786
|
-
return
|
|
11036
|
+
return path19.basename(resolved);
|
|
9787
11037
|
}
|
|
9788
11038
|
const homePrefix = `${encodeQoderCnProjectPath(home)}-`;
|
|
9789
11039
|
if (projectDir.startsWith(homePrefix)) {
|
|
@@ -9803,12 +11053,12 @@ async function resolveQoderCnProjectPath(projectDir, home) {
|
|
|
9803
11053
|
while (projectDir.startsWith(`${currentEncoded}-`)) {
|
|
9804
11054
|
let matchedChild;
|
|
9805
11055
|
try {
|
|
9806
|
-
const entries = await
|
|
11056
|
+
const entries = await readdir9(current, { withFileTypes: true });
|
|
9807
11057
|
for (const entry of entries) {
|
|
9808
11058
|
if (!entry.isDirectory()) {
|
|
9809
11059
|
continue;
|
|
9810
11060
|
}
|
|
9811
|
-
const candidate =
|
|
11061
|
+
const candidate = path19.join(current, entry.name);
|
|
9812
11062
|
const encoded = encodeQoderCnProjectPath(candidate);
|
|
9813
11063
|
if (encoded === projectDir) {
|
|
9814
11064
|
return candidate;
|
|
@@ -9853,9 +11103,9 @@ function hookConfig6() {
|
|
|
9853
11103
|
function qoderCnConfigDir(home, env) {
|
|
9854
11104
|
const override = env?.QODER_CN_CONFIG_DIR;
|
|
9855
11105
|
if (override && override.trim()) {
|
|
9856
|
-
return
|
|
11106
|
+
return path19.resolve(override);
|
|
9857
11107
|
}
|
|
9858
|
-
return
|
|
11108
|
+
return path19.join(home, ".qoder-cn");
|
|
9859
11109
|
}
|
|
9860
11110
|
function createQoderCnAdapter() {
|
|
9861
11111
|
return {
|
|
@@ -9867,27 +11117,27 @@ function createQoderCnAdapter() {
|
|
|
9867
11117
|
return qoderCnConfigDir(home, env);
|
|
9868
11118
|
},
|
|
9869
11119
|
installedPath(home, env) {
|
|
9870
|
-
return
|
|
11120
|
+
return path19.join(qoderCnConfigDir(home, env), "settings.json");
|
|
9871
11121
|
},
|
|
9872
11122
|
async isInstalled(home, env) {
|
|
9873
11123
|
return isHooksJsonInstalled(
|
|
9874
|
-
|
|
11124
|
+
path19.join(qoderCnConfigDir(home, env), "settings.json"),
|
|
9875
11125
|
"vibetime hook --agent qoder-cn"
|
|
9876
11126
|
);
|
|
9877
11127
|
},
|
|
9878
11128
|
installEntries(home, env) {
|
|
9879
11129
|
return [{
|
|
9880
11130
|
kind: "hooks-json",
|
|
9881
|
-
path:
|
|
11131
|
+
path: path19.join(qoderCnConfigDir(home, env), "settings.json"),
|
|
9882
11132
|
content: hookConfig6()
|
|
9883
11133
|
}];
|
|
9884
11134
|
},
|
|
9885
11135
|
sourcePaths(home, env) {
|
|
9886
11136
|
const base = qoderCnConfigDir(home, env);
|
|
9887
11137
|
return [
|
|
9888
|
-
|
|
9889
|
-
|
|
9890
|
-
|
|
11138
|
+
path19.join(base, "projects"),
|
|
11139
|
+
path19.join(base, ".qoder.json"),
|
|
11140
|
+
path19.join(home, ".qoder.json")
|
|
9891
11141
|
];
|
|
9892
11142
|
},
|
|
9893
11143
|
parseSessionFile: parseQoderCnSessionFile
|
|
@@ -9896,11 +11146,11 @@ function createQoderCnAdapter() {
|
|
|
9896
11146
|
|
|
9897
11147
|
// src/adapters/qoder.ts
|
|
9898
11148
|
import { existsSync } from "node:fs";
|
|
9899
|
-
import { readdir as
|
|
9900
|
-
import
|
|
9901
|
-
import
|
|
11149
|
+
import { readdir as readdir10, readFile as readFile13, stat as stat11 } from "node:fs/promises";
|
|
11150
|
+
import os11 from "node:os";
|
|
11151
|
+
import path20 from "node:path";
|
|
9902
11152
|
function parseQoderPaths(filePath) {
|
|
9903
|
-
const parts = filePath.split(
|
|
11153
|
+
const parts = filePath.split(path20.sep);
|
|
9904
11154
|
const subagentsIdx = parts.lastIndexOf("subagents");
|
|
9905
11155
|
let sessionId = "";
|
|
9906
11156
|
let projectName = "";
|
|
@@ -9910,17 +11160,17 @@ function parseQoderPaths(filePath) {
|
|
|
9910
11160
|
sessionId = parts[subagentsIdx - 1];
|
|
9911
11161
|
projectName = parts[subagentsIdx - 2];
|
|
9912
11162
|
const projectsIdx = parts.lastIndexOf("projects");
|
|
9913
|
-
configDir2 = parts.slice(0, projectsIdx).join(
|
|
9914
|
-
mainTranscriptPath = [...parts.slice(0, subagentsIdx - 1), `${sessionId}.jsonl`].join(
|
|
11163
|
+
configDir2 = parts.slice(0, projectsIdx).join(path20.sep);
|
|
11164
|
+
mainTranscriptPath = [...parts.slice(0, subagentsIdx - 1), `${sessionId}.jsonl`].join(path20.sep);
|
|
9915
11165
|
} else {
|
|
9916
11166
|
const filename = parts.at(-1) || "";
|
|
9917
|
-
sessionId =
|
|
11167
|
+
sessionId = path20.basename(filename, ".jsonl");
|
|
9918
11168
|
projectName = parts.at(-2) || "";
|
|
9919
11169
|
if (projectName === "transcript") {
|
|
9920
11170
|
projectName = parts.at(-3) || "";
|
|
9921
11171
|
}
|
|
9922
11172
|
const projectsIdx = parts.lastIndexOf("projects");
|
|
9923
|
-
configDir2 = parts.slice(0, projectsIdx).join(
|
|
11173
|
+
configDir2 = parts.slice(0, projectsIdx).join(path20.sep);
|
|
9924
11174
|
}
|
|
9925
11175
|
return { configDir: configDir2, projectName, sessionId, mainTranscriptPath };
|
|
9926
11176
|
}
|
|
@@ -9943,7 +11193,7 @@ function rebuildEventIdentity4(event) {
|
|
|
9943
11193
|
}
|
|
9944
11194
|
async function parseModelNamesFromDynamicTexts2(dynamicTextsPath) {
|
|
9945
11195
|
try {
|
|
9946
|
-
const content = await
|
|
11196
|
+
const content = await readFile13(dynamicTextsPath, "utf8");
|
|
9947
11197
|
const json = JSON.parse(content);
|
|
9948
11198
|
const texts = json.texts || {};
|
|
9949
11199
|
const map = {};
|
|
@@ -9959,10 +11209,10 @@ async function parseModelNamesFromDynamicTexts2(dynamicTextsPath) {
|
|
|
9959
11209
|
}
|
|
9960
11210
|
}
|
|
9961
11211
|
async function loadQoderModelNames(configDir2, home) {
|
|
9962
|
-
const map = await parseModelNamesFromDynamicTexts2(
|
|
11212
|
+
const map = await parseModelNamesFromDynamicTexts2(path20.join(configDir2, ".auth", "dynamic-texts.json"));
|
|
9963
11213
|
const siblingConfigDir = configDir2.replace(/\.qoder$/, ".qoder-cn");
|
|
9964
11214
|
if (siblingConfigDir !== configDir2) {
|
|
9965
|
-
const siblingMap = await parseModelNamesFromDynamicTexts2(
|
|
11215
|
+
const siblingMap = await parseModelNamesFromDynamicTexts2(path20.join(siblingConfigDir, ".auth", "dynamic-texts.json"));
|
|
9966
11216
|
for (const [key, val] of Object.entries(siblingMap)) {
|
|
9967
11217
|
if (!(key in map)) {
|
|
9968
11218
|
map[key] = val;
|
|
@@ -9970,11 +11220,11 @@ async function loadQoderModelNames(configDir2, home) {
|
|
|
9970
11220
|
}
|
|
9971
11221
|
}
|
|
9972
11222
|
if (isQwenworkConfigRoot(configDir2)) {
|
|
9973
|
-
for (const dir of [
|
|
9974
|
-
if (
|
|
11223
|
+
for (const dir of [path20.join(home, ".qoder"), path20.join(home, ".qoder-cn")]) {
|
|
11224
|
+
if (path20.resolve(dir) === path20.resolve(configDir2)) {
|
|
9975
11225
|
continue;
|
|
9976
11226
|
}
|
|
9977
|
-
const fallbackMap = await parseModelNamesFromDynamicTexts2(
|
|
11227
|
+
const fallbackMap = await parseModelNamesFromDynamicTexts2(path20.join(dir, ".auth", "dynamic-texts.json"));
|
|
9978
11228
|
for (const [key, val] of Object.entries(fallbackMap)) {
|
|
9979
11229
|
if (!(key in map)) {
|
|
9980
11230
|
map[key] = val;
|
|
@@ -9993,25 +11243,25 @@ async function loadQoderModelNames(configDir2, home) {
|
|
|
9993
11243
|
return map;
|
|
9994
11244
|
}
|
|
9995
11245
|
function isQwenworkConfigRoot(configDir2) {
|
|
9996
|
-
const name =
|
|
11246
|
+
const name = path20.basename(path20.resolve(configDir2));
|
|
9997
11247
|
if (name === ".qwenworkcn" || name === ".qwenwork") {
|
|
9998
11248
|
return true;
|
|
9999
11249
|
}
|
|
10000
11250
|
const override = process.env.QWENWORK_CONFIG_DIR;
|
|
10001
|
-
return Boolean(override && override.trim() &&
|
|
11251
|
+
return Boolean(override && override.trim() && path20.basename(path20.resolve(override)) === name);
|
|
10002
11252
|
}
|
|
10003
11253
|
var FAILED_SEGMENT_STOP_REASONS = /* @__PURE__ */ new Set(["cancelled", "canceled", "error", "failed", "refusal"]);
|
|
10004
11254
|
async function loadQoderSegmentModelCalls(filePath, isSubagentSession, modelMap) {
|
|
10005
11255
|
const { configDir: configDir2, projectName, sessionId } = parseQoderPaths(filePath);
|
|
10006
|
-
const segmentsPath =
|
|
11256
|
+
const segmentsPath = path20.join(configDir2, "logs", "sessions", projectName, sessionId, "segments");
|
|
10007
11257
|
const modelCalls = [];
|
|
10008
11258
|
try {
|
|
10009
|
-
const files = await
|
|
11259
|
+
const files = await readdir10(segmentsPath);
|
|
10010
11260
|
for (const file of files) {
|
|
10011
11261
|
if (!file.endsWith(".jsonl")) {
|
|
10012
11262
|
continue;
|
|
10013
11263
|
}
|
|
10014
|
-
const content = await
|
|
11264
|
+
const content = await readFile13(path20.join(segmentsPath, file), "utf8");
|
|
10015
11265
|
let currentTurnIsSubagent = false;
|
|
10016
11266
|
for (const line of content.split("\n").filter(Boolean)) {
|
|
10017
11267
|
const raw = parseJsonLine(line);
|
|
@@ -10045,7 +11295,7 @@ async function loadQoderSegmentModelCalls(filePath, isSubagentSession, modelMap)
|
|
|
10045
11295
|
return modelCalls.filter((call) => call.isSubagent === isSubagentSession);
|
|
10046
11296
|
}
|
|
10047
11297
|
async function parseQoderSessionFile(filePath, options) {
|
|
10048
|
-
const text = await
|
|
11298
|
+
const text = await readFile13(filePath, "utf8");
|
|
10049
11299
|
const lines = text.split("\n").filter(Boolean);
|
|
10050
11300
|
const parsedPaths = parseQoderPaths(filePath);
|
|
10051
11301
|
const { configDir: configDir2 } = parsedPaths;
|
|
@@ -10056,7 +11306,7 @@ async function parseQoderSessionFile(filePath, options) {
|
|
|
10056
11306
|
let cwd;
|
|
10057
11307
|
let project = projectContext.project;
|
|
10058
11308
|
let model;
|
|
10059
|
-
const home =
|
|
11309
|
+
const home = path20.resolve(stringOption(options.home) || os11.homedir());
|
|
10060
11310
|
const modelMap = await loadQoderModelNames(configDir2, home);
|
|
10061
11311
|
const qwenworkRoot = isQwenworkConfigRoot(configDir2);
|
|
10062
11312
|
const isSubagentSession = filePath.includes("subagents");
|
|
@@ -10092,7 +11342,7 @@ async function parseQoderSessionFile(filePath, options) {
|
|
|
10092
11342
|
sessionId = stringField(raw, "sessionId") || sessionId;
|
|
10093
11343
|
state.sessionId = sessionId;
|
|
10094
11344
|
cwd = stringField(raw, "cwd") || cwd;
|
|
10095
|
-
project = projectContext.project || (cwd ?
|
|
11345
|
+
project = projectContext.project || (cwd ? path20.basename(cwd) : project || await qoderProjectFromFilePath(filePath, options));
|
|
10096
11346
|
if (!ts) {
|
|
10097
11347
|
continue;
|
|
10098
11348
|
}
|
|
@@ -10386,10 +11636,11 @@ async function parseQoderSessionFile(filePath, options) {
|
|
|
10386
11636
|
const { sessionId: parentSessionId, mainTranscriptPath } = parseQoderPaths(filePath);
|
|
10387
11637
|
if (parentSessionId && mainTranscriptPath) {
|
|
10388
11638
|
const parentSourcePathHash = `sha256:${createStableHash(mainTranscriptPath)}`;
|
|
10389
|
-
return validEvents.map((event) => {
|
|
11639
|
+
return withoutSubagentTurnEvents(validEvents).map((event) => {
|
|
10390
11640
|
const mapped = {
|
|
10391
11641
|
...event,
|
|
10392
11642
|
sessionId: parentSessionId,
|
|
11643
|
+
turnId: void 0,
|
|
10393
11644
|
refs: {
|
|
10394
11645
|
...event.refs,
|
|
10395
11646
|
sourcePathHash: parentSourcePathHash,
|
|
@@ -10402,11 +11653,12 @@ async function parseQoderSessionFile(filePath, options) {
|
|
|
10402
11653
|
}
|
|
10403
11654
|
dbModelCalls ??= await loadQoderDbModelCalls("Qoder", parsedPaths.sessionId, modelMap);
|
|
10404
11655
|
if (dbModelCalls.rootSessionId) {
|
|
10405
|
-
const parentPath =
|
|
11656
|
+
const parentPath = path20.join(path20.dirname(filePath), `${dbModelCalls.rootSessionId}.jsonl`);
|
|
10406
11657
|
const parentSourcePathHash = `sha256:${createStableHash(parentPath)}`;
|
|
10407
|
-
return validEvents.map((event) => rebuildEventIdentity4({
|
|
11658
|
+
return withoutSubagentTurnEvents(validEvents).map((event) => rebuildEventIdentity4({
|
|
10408
11659
|
...event,
|
|
10409
11660
|
sessionId: dbModelCalls.rootSessionId,
|
|
11661
|
+
turnId: void 0,
|
|
10410
11662
|
refs: {
|
|
10411
11663
|
...event.refs,
|
|
10412
11664
|
sourcePathHash: parentSourcePathHash,
|
|
@@ -10518,41 +11770,41 @@ function qoderExtractText(value) {
|
|
|
10518
11770
|
}
|
|
10519
11771
|
async function qoderProjectContextFromLines(filePath, lines, options, configDir2) {
|
|
10520
11772
|
const { projectName: projectDir, sessionId } = parseQoderPaths(filePath);
|
|
10521
|
-
const isSubagent = filePath.includes(`${
|
|
11773
|
+
const isSubagent = filePath.includes(`${path20.sep}subagents${path20.sep}`);
|
|
10522
11774
|
const inherited = isSubagent ? await readPersistedSessionContextFromOptions(options, sessionId) : void 0;
|
|
10523
11775
|
let cwds = [];
|
|
10524
11776
|
const workspaceDirs = [];
|
|
10525
11777
|
for (const line of lines) {
|
|
10526
11778
|
const raw = parseJsonLine(line);
|
|
10527
11779
|
const cwd = raw ? stringField(raw, "cwd") : void 0;
|
|
10528
|
-
if (cwd &&
|
|
11780
|
+
if (cwd && path20.isAbsolute(cwd)) {
|
|
10529
11781
|
cwds.push(cwd);
|
|
10530
11782
|
}
|
|
10531
11783
|
if (raw && stringField(raw, "type") === "workspace-directories") {
|
|
10532
11784
|
for (const dir of arrayField5(raw, "directories")) {
|
|
10533
|
-
if (typeof dir === "string" &&
|
|
11785
|
+
if (typeof dir === "string" && path20.isAbsolute(dir)) {
|
|
10534
11786
|
workspaceDirs.push(dir);
|
|
10535
11787
|
}
|
|
10536
11788
|
}
|
|
10537
11789
|
}
|
|
10538
11790
|
}
|
|
10539
11791
|
if (isSubagent) {
|
|
10540
|
-
if (inherited?.cwd &&
|
|
11792
|
+
if (inherited?.cwd && path20.isAbsolute(inherited.cwd)) {
|
|
10541
11793
|
cwds = [inherited.cwd];
|
|
10542
11794
|
} else {
|
|
10543
|
-
const parentSessionPath =
|
|
11795
|
+
const parentSessionPath = path20.join(configDir2, "projects", projectDir, `${sessionId}.jsonl`);
|
|
10544
11796
|
try {
|
|
10545
|
-
const parentText = await
|
|
11797
|
+
const parentText = await readFile13(parentSessionPath, "utf8");
|
|
10546
11798
|
const parentCwds = [];
|
|
10547
11799
|
for (const line of parentText.split("\n").filter(Boolean)) {
|
|
10548
11800
|
const raw = parseJsonLine(line);
|
|
10549
11801
|
const cwd = raw ? stringField(raw, "cwd") : void 0;
|
|
10550
|
-
if (cwd &&
|
|
11802
|
+
if (cwd && path20.isAbsolute(cwd)) {
|
|
10551
11803
|
parentCwds.push(cwd);
|
|
10552
11804
|
}
|
|
10553
11805
|
if (workspaceDirs.length === 0 && raw && stringField(raw, "type") === "workspace-directories") {
|
|
10554
11806
|
for (const dir of arrayField5(raw, "directories")) {
|
|
10555
|
-
if (typeof dir === "string" &&
|
|
11807
|
+
if (typeof dir === "string" && path20.isAbsolute(dir)) {
|
|
10556
11808
|
workspaceDirs.push(dir);
|
|
10557
11809
|
}
|
|
10558
11810
|
}
|
|
@@ -10572,29 +11824,29 @@ async function qoderProjectContextFromLines(filePath, lines, options, configDir2
|
|
|
10572
11824
|
}
|
|
10573
11825
|
}
|
|
10574
11826
|
const root = await gitRootFromCwds3(cwds) || qoderProjectRootFromCwds(projectDir, cwds);
|
|
10575
|
-
const project = inherited?.project || (cwds.length > 0 ?
|
|
11827
|
+
const project = inherited?.project || (cwds.length > 0 ? path20.basename(cwds[0]) : root ? path20.basename(root) : await qoderProjectFromFilePath(filePath, options));
|
|
10576
11828
|
return {
|
|
10577
11829
|
project,
|
|
10578
11830
|
workspaceId: createWorkspaceId({ projectName: project, repoRoot: root })
|
|
10579
11831
|
};
|
|
10580
11832
|
}
|
|
10581
11833
|
function pathInsideDir(candidate, dir) {
|
|
10582
|
-
const resolvedDir =
|
|
10583
|
-
const resolved =
|
|
10584
|
-
return resolved === resolvedDir || resolved.startsWith(`${resolvedDir}${
|
|
11834
|
+
const resolvedDir = path20.resolve(dir);
|
|
11835
|
+
const resolved = path20.resolve(candidate);
|
|
11836
|
+
return resolved === resolvedDir || resolved.startsWith(`${resolvedDir}${path20.sep}`);
|
|
10585
11837
|
}
|
|
10586
11838
|
async function gitRootFromCwds3(cwds) {
|
|
10587
11839
|
const seen = /* @__PURE__ */ new Set();
|
|
10588
11840
|
for (const cwd of cwds) {
|
|
10589
|
-
let current =
|
|
11841
|
+
let current = path20.resolve(cwd);
|
|
10590
11842
|
while (!seen.has(current)) {
|
|
10591
11843
|
seen.add(current);
|
|
10592
11844
|
try {
|
|
10593
|
-
await
|
|
11845
|
+
await stat11(path20.join(current, ".git"));
|
|
10594
11846
|
return current;
|
|
10595
11847
|
} catch {
|
|
10596
11848
|
}
|
|
10597
|
-
const parent =
|
|
11849
|
+
const parent = path20.dirname(current);
|
|
10598
11850
|
if (parent === current) {
|
|
10599
11851
|
break;
|
|
10600
11852
|
}
|
|
@@ -10605,12 +11857,12 @@ async function gitRootFromCwds3(cwds) {
|
|
|
10605
11857
|
}
|
|
10606
11858
|
function qoderProjectRootFromCwds(projectDir, cwds) {
|
|
10607
11859
|
for (const cwd of cwds) {
|
|
10608
|
-
let current =
|
|
11860
|
+
let current = path20.resolve(cwd);
|
|
10609
11861
|
while (true) {
|
|
10610
11862
|
if (qoderEncodedVariants(current).includes(projectDir)) {
|
|
10611
11863
|
return current;
|
|
10612
11864
|
}
|
|
10613
|
-
const parent =
|
|
11865
|
+
const parent = path20.dirname(current);
|
|
10614
11866
|
if (parent === current) {
|
|
10615
11867
|
break;
|
|
10616
11868
|
}
|
|
@@ -10620,7 +11872,7 @@ function qoderProjectRootFromCwds(projectDir, cwds) {
|
|
|
10620
11872
|
return void 0;
|
|
10621
11873
|
}
|
|
10622
11874
|
function rawQoderProjectPath(value) {
|
|
10623
|
-
return
|
|
11875
|
+
return path20.resolve(value).split(path20.sep).join("-");
|
|
10624
11876
|
}
|
|
10625
11877
|
function qoderEncodedVariants(value) {
|
|
10626
11878
|
const raw = rawQoderProjectPath(value);
|
|
@@ -10641,11 +11893,11 @@ function qoderEncodedProjectSuffix(projectDir, home) {
|
|
|
10641
11893
|
return void 0;
|
|
10642
11894
|
}
|
|
10643
11895
|
async function qoderProjectFromFilePath(filePath, options) {
|
|
10644
|
-
const projectDir =
|
|
10645
|
-
const home = options ?
|
|
11896
|
+
const projectDir = path20.basename(path20.dirname(filePath));
|
|
11897
|
+
const home = options ? path20.resolve(stringOption(options.home) || os11.homedir()) : os11.homedir();
|
|
10646
11898
|
const resolved = await resolveQoderProjectPath(projectDir, home);
|
|
10647
11899
|
if (resolved) {
|
|
10648
|
-
return
|
|
11900
|
+
return path20.basename(resolved);
|
|
10649
11901
|
}
|
|
10650
11902
|
const suffix = qoderEncodedProjectSuffix(projectDir, home);
|
|
10651
11903
|
if (suffix) {
|
|
@@ -10666,12 +11918,12 @@ async function resolveQoderProjectPath(projectDir, home) {
|
|
|
10666
11918
|
while (currentEncodedVariants.some((prefix) => projectDir.startsWith(`${prefix}-`))) {
|
|
10667
11919
|
let matchedChild;
|
|
10668
11920
|
try {
|
|
10669
|
-
const entries = await
|
|
11921
|
+
const entries = await readdir10(current, { withFileTypes: true });
|
|
10670
11922
|
for (const entry of entries) {
|
|
10671
11923
|
if (!entry.isDirectory()) {
|
|
10672
11924
|
continue;
|
|
10673
11925
|
}
|
|
10674
|
-
const candidate =
|
|
11926
|
+
const candidate = path20.join(current, entry.name);
|
|
10675
11927
|
const candidateVariants = qoderEncodedVariants(candidate);
|
|
10676
11928
|
if (candidateVariants.includes(projectDir)) {
|
|
10677
11929
|
return candidate;
|
|
@@ -10716,19 +11968,19 @@ function hookConfig7() {
|
|
|
10716
11968
|
function qoderConfigDir(home, env) {
|
|
10717
11969
|
const override = env?.QODER_CONFIG_DIR;
|
|
10718
11970
|
if (override && override.trim()) {
|
|
10719
|
-
return
|
|
11971
|
+
return path20.resolve(override);
|
|
10720
11972
|
}
|
|
10721
|
-
return
|
|
11973
|
+
return path20.join(home, ".qoder");
|
|
10722
11974
|
}
|
|
10723
11975
|
function qwenworkConfigDir(home, env) {
|
|
10724
11976
|
const override = env?.QWENWORK_CONFIG_DIR;
|
|
10725
11977
|
if (override && override.trim()) {
|
|
10726
|
-
return
|
|
11978
|
+
return path20.resolve(override);
|
|
10727
11979
|
}
|
|
10728
|
-
return
|
|
11980
|
+
return path20.join(home, ".qwenworkcn");
|
|
10729
11981
|
}
|
|
10730
11982
|
function qoderConfigDirs(home, env) {
|
|
10731
|
-
return [...new Set([qoderConfigDir(home, env), qwenworkConfigDir(home, env)].map((dir) =>
|
|
11983
|
+
return [...new Set([qoderConfigDir(home, env), qwenworkConfigDir(home, env)].map((dir) => path20.resolve(dir)))];
|
|
10732
11984
|
}
|
|
10733
11985
|
function createQoderAdapter() {
|
|
10734
11986
|
return {
|
|
@@ -10740,11 +11992,11 @@ function createQoderAdapter() {
|
|
|
10740
11992
|
return qoderConfigDir(home, env);
|
|
10741
11993
|
},
|
|
10742
11994
|
installedPath(home, env) {
|
|
10743
|
-
return
|
|
11995
|
+
return path20.join(qoderConfigDir(home, env), "settings.json");
|
|
10744
11996
|
},
|
|
10745
11997
|
async isInstalled(home, env) {
|
|
10746
11998
|
return isHooksJsonInstalled(
|
|
10747
|
-
|
|
11999
|
+
path20.join(qoderConfigDir(home, env), "settings.json"),
|
|
10748
12000
|
"vibetime hook --agent qoder"
|
|
10749
12001
|
);
|
|
10750
12002
|
},
|
|
@@ -10753,16 +12005,16 @@ function createQoderAdapter() {
|
|
|
10753
12005
|
const targets = [primary, ...variants.filter((dir) => existsSync(dir))];
|
|
10754
12006
|
return targets.map((base) => ({
|
|
10755
12007
|
kind: "hooks-json",
|
|
10756
|
-
path:
|
|
12008
|
+
path: path20.join(base, "settings.json"),
|
|
10757
12009
|
content: hookConfig7()
|
|
10758
12010
|
}));
|
|
10759
12011
|
},
|
|
10760
12012
|
sourcePaths(home, env) {
|
|
10761
|
-
const paths = qoderConfigDirs(home, env).map((base2) =>
|
|
12013
|
+
const paths = qoderConfigDirs(home, env).map((base2) => path20.join(base2, "projects"));
|
|
10762
12014
|
const base = qoderConfigDir(home, env);
|
|
10763
12015
|
paths.push(
|
|
10764
|
-
|
|
10765
|
-
|
|
12016
|
+
path20.join(base, ".qoder.json"),
|
|
12017
|
+
path20.join(home, ".qoder.json")
|
|
10766
12018
|
);
|
|
10767
12019
|
return paths;
|
|
10768
12020
|
},
|
|
@@ -10798,27 +12050,27 @@ function normalizeId(id) {
|
|
|
10798
12050
|
}
|
|
10799
12051
|
|
|
10800
12052
|
// src/adapters/workbuddy.ts
|
|
10801
|
-
import { readdir as
|
|
10802
|
-
import
|
|
12053
|
+
import { readdir as readdir11, readFile as readFile14, stat as stat12 } from "node:fs/promises";
|
|
12054
|
+
import path21 from "node:path";
|
|
10803
12055
|
function workbuddyProjectsDir(home, env) {
|
|
10804
12056
|
const override = env?.WORKBUDDY_PROJECTS_DIR || env?.WORKBUDDY_HOME;
|
|
10805
12057
|
if (override && override.trim()) {
|
|
10806
|
-
return
|
|
12058
|
+
return path21.resolve(override, override.endsWith("projects") ? "" : "projects");
|
|
10807
12059
|
}
|
|
10808
|
-
return
|
|
12060
|
+
return path21.join(home, ".workbuddy", "projects");
|
|
10809
12061
|
}
|
|
10810
12062
|
function workbuddyBaseDir(home, env) {
|
|
10811
12063
|
const override = env?.WORKBUDDY_HOME;
|
|
10812
12064
|
if (override && override.trim()) {
|
|
10813
|
-
return
|
|
12065
|
+
return path21.resolve(override);
|
|
10814
12066
|
}
|
|
10815
|
-
return
|
|
12067
|
+
return path21.join(home, ".workbuddy");
|
|
10816
12068
|
}
|
|
10817
12069
|
function projectFromCwd(cwd, fallback) {
|
|
10818
12070
|
if (!cwd) {
|
|
10819
12071
|
return fallback;
|
|
10820
12072
|
}
|
|
10821
|
-
return
|
|
12073
|
+
return path21.basename(cwd) || fallback;
|
|
10822
12074
|
}
|
|
10823
12075
|
function sourceHash(filePath) {
|
|
10824
12076
|
return `sha256:${createStableHash(filePath)}`;
|
|
@@ -10936,7 +12188,7 @@ function toolCallFailed(record) {
|
|
|
10936
12188
|
return status === "failed" || status === "incomplete" || record.is_error === true || providerData.error != null || providerData.isError === true;
|
|
10937
12189
|
}
|
|
10938
12190
|
async function readWorkbuddyLines(filePath) {
|
|
10939
|
-
const text = await
|
|
12191
|
+
const text = await readFile14(filePath, "utf8");
|
|
10940
12192
|
return text.split(/\r?\n/).map((line, index) => {
|
|
10941
12193
|
if (!line.trim()) {
|
|
10942
12194
|
return void 0;
|
|
@@ -10956,8 +12208,8 @@ async function parseWorkbuddySessionFile(filePath, options) {
|
|
|
10956
12208
|
}
|
|
10957
12209
|
const events = [];
|
|
10958
12210
|
const first = lines[0].record;
|
|
10959
|
-
const sessionId = stringField(first, "sessionId") ||
|
|
10960
|
-
const fallbackProject =
|
|
12211
|
+
const sessionId = stringField(first, "sessionId") || path21.basename(filePath, ".jsonl");
|
|
12212
|
+
const fallbackProject = path21.basename(path21.dirname(filePath));
|
|
10961
12213
|
const cwd = lines.map((line) => stringField(line.record, "cwd")).find(Boolean);
|
|
10962
12214
|
const project = projectFromCwd(cwd, fallbackProject);
|
|
10963
12215
|
const workspaceId = createWorkspaceId({ projectName: project, repoRoot: cwd });
|
|
@@ -11241,17 +12493,17 @@ async function workbuddyBackfillFiles(sourceRoot, home, env) {
|
|
|
11241
12493
|
const base = sourceRoot || workbuddyProjectsDir(home, env);
|
|
11242
12494
|
const files = [];
|
|
11243
12495
|
try {
|
|
11244
|
-
const projects = await
|
|
12496
|
+
const projects = await readdir11(base, { withFileTypes: true });
|
|
11245
12497
|
for (const project of projects) {
|
|
11246
12498
|
if (!project.isDirectory()) {
|
|
11247
12499
|
continue;
|
|
11248
12500
|
}
|
|
11249
|
-
const projectDir =
|
|
11250
|
-
const entries = await
|
|
12501
|
+
const projectDir = path21.join(base, project.name);
|
|
12502
|
+
const entries = await readdir11(projectDir, { withFileTypes: true });
|
|
11251
12503
|
for (const entry of entries) {
|
|
11252
12504
|
if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
11253
|
-
const filePath =
|
|
11254
|
-
const info = await
|
|
12505
|
+
const filePath = path21.join(projectDir, entry.name);
|
|
12506
|
+
const info = await stat12(filePath);
|
|
11255
12507
|
files.push({ path: filePath, modifiedAt: info.mtime.toISOString() });
|
|
11256
12508
|
}
|
|
11257
12509
|
}
|
|
@@ -11288,18 +12540,18 @@ function createWorkbuddyAdapter() {
|
|
|
11288
12540
|
return workbuddyProjectsDir(home, env);
|
|
11289
12541
|
},
|
|
11290
12542
|
installedPath(home, env) {
|
|
11291
|
-
return
|
|
12543
|
+
return path21.join(workbuddyBaseDir(home, env), "settings.json");
|
|
11292
12544
|
},
|
|
11293
12545
|
async isInstalled(home, env) {
|
|
11294
12546
|
return isHooksJsonInstalled(
|
|
11295
|
-
|
|
12547
|
+
path21.join(workbuddyBaseDir(home, env), "settings.json"),
|
|
11296
12548
|
"vibetime hook --agent workbuddy"
|
|
11297
12549
|
);
|
|
11298
12550
|
},
|
|
11299
12551
|
installEntries(home, env) {
|
|
11300
12552
|
return [{
|
|
11301
12553
|
kind: "hooks-json",
|
|
11302
|
-
path:
|
|
12554
|
+
path: path21.join(workbuddyBaseDir(home, env), "settings.json"),
|
|
11303
12555
|
content: hookConfig8()
|
|
11304
12556
|
}];
|
|
11305
12557
|
},
|
|
@@ -11312,26 +12564,26 @@ function createWorkbuddyAdapter() {
|
|
|
11312
12564
|
|
|
11313
12565
|
// src/adapters/zcode.ts
|
|
11314
12566
|
import { execFile } from "node:child_process";
|
|
11315
|
-
import { readFile as
|
|
11316
|
-
import
|
|
12567
|
+
import { readFile as readFile15, stat as stat13 } from "node:fs/promises";
|
|
12568
|
+
import path22 from "node:path";
|
|
11317
12569
|
import { promisify as promisify2 } from "node:util";
|
|
11318
12570
|
init_fs();
|
|
11319
12571
|
var execFileAsync = promisify2(execFile);
|
|
11320
12572
|
function zcodeCliDir(home, env) {
|
|
11321
12573
|
const override = env?.ZCODE_CLI_DIR || env?.ZCODE_HOME;
|
|
11322
12574
|
if (override && override.trim()) {
|
|
11323
|
-
return
|
|
12575
|
+
return path22.resolve(override, override.endsWith("cli") ? "" : "cli");
|
|
11324
12576
|
}
|
|
11325
|
-
return
|
|
12577
|
+
return path22.join(home, ".zcode", "cli");
|
|
11326
12578
|
}
|
|
11327
12579
|
function zcodeDbPath(home, env) {
|
|
11328
|
-
return
|
|
12580
|
+
return path22.join(zcodeCliDir(home, env), "db", "db.sqlite");
|
|
11329
12581
|
}
|
|
11330
12582
|
var providerNameCache = null;
|
|
11331
12583
|
async function loadProviderNames(configPath2) {
|
|
11332
12584
|
let fileMtime = 0;
|
|
11333
12585
|
try {
|
|
11334
|
-
const info = await
|
|
12586
|
+
const info = await stat13(configPath2);
|
|
11335
12587
|
fileMtime = info.mtimeMs;
|
|
11336
12588
|
} catch {
|
|
11337
12589
|
return /* @__PURE__ */ new Map();
|
|
@@ -11341,7 +12593,7 @@ async function loadProviderNames(configPath2) {
|
|
|
11341
12593
|
}
|
|
11342
12594
|
const map = /* @__PURE__ */ new Map();
|
|
11343
12595
|
try {
|
|
11344
|
-
const raw = await
|
|
12596
|
+
const raw = await readFile15(configPath2, "utf-8");
|
|
11345
12597
|
const config = JSON.parse(raw);
|
|
11346
12598
|
const providers = config?.provider;
|
|
11347
12599
|
if (isPlainObject(providers)) {
|
|
@@ -11359,7 +12611,7 @@ function sourceHash2(filePath) {
|
|
|
11359
12611
|
return `sha256:${createStableHash(filePath)}`;
|
|
11360
12612
|
}
|
|
11361
12613
|
function projectFromDirectory(directory) {
|
|
11362
|
-
return directory ?
|
|
12614
|
+
return directory ? path22.basename(directory) || "zcode" : "zcode";
|
|
11363
12615
|
}
|
|
11364
12616
|
function isoFromMs(value) {
|
|
11365
12617
|
return timestampFrom(typeof value === "number" ? value : Number(value));
|
|
@@ -11543,16 +12795,16 @@ async function parseZCodeDb(filePath, options) {
|
|
|
11543
12795
|
if (rows.length === 0) {
|
|
11544
12796
|
return [];
|
|
11545
12797
|
}
|
|
11546
|
-
let candidate =
|
|
12798
|
+
let candidate = path22.resolve(filePath);
|
|
11547
12799
|
let configPath2 = "";
|
|
11548
12800
|
for (let i = 0; i < 12; i++) {
|
|
11549
|
-
const probe =
|
|
12801
|
+
const probe = path22.join(candidate, ".zcode", "v2", "config.json");
|
|
11550
12802
|
try {
|
|
11551
|
-
await
|
|
12803
|
+
await stat13(probe);
|
|
11552
12804
|
configPath2 = probe;
|
|
11553
12805
|
break;
|
|
11554
12806
|
} catch {
|
|
11555
|
-
const parent =
|
|
12807
|
+
const parent = path22.dirname(candidate);
|
|
11556
12808
|
if (parent === candidate) break;
|
|
11557
12809
|
candidate = parent;
|
|
11558
12810
|
}
|
|
@@ -11772,9 +13024,9 @@ async function parseZCodeDb(filePath, options) {
|
|
|
11772
13024
|
}
|
|
11773
13025
|
async function zcodeBackfillFiles(sourceRoot, home, env) {
|
|
11774
13026
|
const candidate = sourceRoot || zcodeDbPath(home, env);
|
|
11775
|
-
const filePath = candidate.endsWith(".sqlite") ? candidate :
|
|
13027
|
+
const filePath = candidate.endsWith(".sqlite") ? candidate : path22.join(candidate, "db", "db.sqlite");
|
|
11776
13028
|
try {
|
|
11777
|
-
const info = await
|
|
13029
|
+
const info = await stat13(filePath);
|
|
11778
13030
|
return [{ path: filePath, modifiedAt: info.mtime.toISOString() }];
|
|
11779
13031
|
} catch {
|
|
11780
13032
|
return [];
|
|
@@ -11806,50 +13058,50 @@ function createZCodeAdapter() {
|
|
|
11806
13058
|
}
|
|
11807
13059
|
|
|
11808
13060
|
// src/adapters/zed.ts
|
|
11809
|
-
import
|
|
11810
|
-
import
|
|
13061
|
+
import os12 from "node:os";
|
|
13062
|
+
import path23 from "node:path";
|
|
11811
13063
|
function zedThreadsCandidates(home, env) {
|
|
11812
13064
|
const candidates = [];
|
|
11813
13065
|
const platform2 = process.platform;
|
|
11814
13066
|
if (platform2 === "darwin") {
|
|
11815
|
-
candidates.push(
|
|
13067
|
+
candidates.push(path23.join(home, "Library", "Application Support", "Zed", "threads", "threads.db"));
|
|
11816
13068
|
} else if (platform2 === "win32") {
|
|
11817
13069
|
const appdata = env?.APPDATA;
|
|
11818
13070
|
if (appdata && appdata.trim()) {
|
|
11819
|
-
candidates.push(
|
|
13071
|
+
candidates.push(path23.join(path23.resolve(appdata), "Zed", "threads", "threads.db"));
|
|
11820
13072
|
}
|
|
11821
|
-
candidates.push(
|
|
13073
|
+
candidates.push(path23.join(home, "AppData", "Roaming", "Zed", "threads", "threads.db"));
|
|
11822
13074
|
} else {
|
|
11823
13075
|
const xdgData = env?.XDG_DATA_HOME;
|
|
11824
13076
|
if (xdgData && xdgData.trim()) {
|
|
11825
|
-
candidates.push(
|
|
13077
|
+
candidates.push(path23.join(path23.resolve(xdgData), "zed", "threads", "threads.db"));
|
|
11826
13078
|
}
|
|
11827
|
-
candidates.push(
|
|
13079
|
+
candidates.push(path23.join(home, ".local", "share", "zed", "threads", "threads.db"));
|
|
11828
13080
|
const xdgConfig = env?.XDG_CONFIG_HOME;
|
|
11829
13081
|
if (xdgConfig && xdgConfig.trim()) {
|
|
11830
|
-
candidates.push(
|
|
13082
|
+
candidates.push(path23.join(path23.resolve(xdgConfig), "zed", "threads", "threads.db"));
|
|
11831
13083
|
}
|
|
11832
|
-
candidates.push(
|
|
13084
|
+
candidates.push(path23.join(home, ".config", "zed", "threads", "threads.db"));
|
|
11833
13085
|
}
|
|
11834
13086
|
return candidates;
|
|
11835
13087
|
}
|
|
11836
13088
|
function zedConfigDir(home, env) {
|
|
11837
13089
|
const platform2 = process.platform;
|
|
11838
13090
|
if (platform2 === "darwin") {
|
|
11839
|
-
return
|
|
13091
|
+
return path23.join(home, "Library", "Application Support", "Zed");
|
|
11840
13092
|
}
|
|
11841
13093
|
if (platform2 === "win32") {
|
|
11842
13094
|
const appdata = env?.APPDATA;
|
|
11843
13095
|
if (appdata && appdata.trim()) {
|
|
11844
|
-
return
|
|
13096
|
+
return path23.join(path23.resolve(appdata), "Zed");
|
|
11845
13097
|
}
|
|
11846
|
-
return
|
|
13098
|
+
return path23.join(home, "AppData", "Roaming", "Zed");
|
|
11847
13099
|
}
|
|
11848
13100
|
const xdgConfig = env?.XDG_CONFIG_HOME;
|
|
11849
13101
|
if (xdgConfig && xdgConfig.trim()) {
|
|
11850
|
-
return
|
|
13102
|
+
return path23.join(path23.resolve(xdgConfig), "zed");
|
|
11851
13103
|
}
|
|
11852
|
-
return
|
|
13104
|
+
return path23.join(home, ".config", "zed");
|
|
11853
13105
|
}
|
|
11854
13106
|
function baseZedEvent(event) {
|
|
11855
13107
|
return {
|
|
@@ -11913,7 +13165,7 @@ async function parseZedSessionFile(dbPath, options) {
|
|
|
11913
13165
|
const folderRaw = row.folder_paths || "";
|
|
11914
13166
|
const folder = folderRaw.split(/[\n,]/).map((s) => s.trim()).find(Boolean);
|
|
11915
13167
|
const cwd = folder || void 0;
|
|
11916
|
-
const project = cwd ?
|
|
13168
|
+
const project = cwd ? path23.basename(cwd) : row.summary ? row.summary.slice(0, 40) : void 0;
|
|
11917
13169
|
let json;
|
|
11918
13170
|
try {
|
|
11919
13171
|
const bytes = row.data_type === "zstd" ? decompress2(new Uint8Array(row.data)) : new Uint8Array(row.data);
|
|
@@ -12158,20 +13410,20 @@ async function parseZedSessionFile(dbPath, options) {
|
|
|
12158
13410
|
}
|
|
12159
13411
|
return events.filter((event) => matchesBackfillFilters(event, options));
|
|
12160
13412
|
}
|
|
12161
|
-
async function zedBackfillFiles(sourceRoot, home =
|
|
12162
|
-
const { stat:
|
|
13413
|
+
async function zedBackfillFiles(sourceRoot, home = os12.homedir(), env) {
|
|
13414
|
+
const { stat: stat16 } = await import("node:fs/promises");
|
|
12163
13415
|
if (sourceRoot) {
|
|
12164
13416
|
if (!sourceRoot.endsWith(".db")) {
|
|
12165
13417
|
return [];
|
|
12166
13418
|
}
|
|
12167
|
-
const info = await
|
|
13419
|
+
const info = await stat16(sourceRoot).catch(() => null);
|
|
12168
13420
|
if (!info) {
|
|
12169
13421
|
return [];
|
|
12170
13422
|
}
|
|
12171
13423
|
return [{ path: sourceRoot, modifiedAt: info.mtime.toISOString() }];
|
|
12172
13424
|
}
|
|
12173
13425
|
for (const candidatePath of zedThreadsCandidates(home, env)) {
|
|
12174
|
-
const info = await
|
|
13426
|
+
const info = await stat16(candidatePath).catch(() => null);
|
|
12175
13427
|
if (info) {
|
|
12176
13428
|
return [{ path: candidatePath, modifiedAt: info.mtime.toISOString() }];
|
|
12177
13429
|
}
|
|
@@ -12188,7 +13440,7 @@ function createZedAdapter() {
|
|
|
12188
13440
|
return zedConfigDir(home, env);
|
|
12189
13441
|
},
|
|
12190
13442
|
installedPath(home, env) {
|
|
12191
|
-
return
|
|
13443
|
+
return path23.join(zedConfigDir(home, env), "vibetime-marker");
|
|
12192
13444
|
},
|
|
12193
13445
|
async isInstalled() {
|
|
12194
13446
|
return false;
|
|
@@ -12563,6 +13815,10 @@ async function installEntry(entry, options) {
|
|
|
12563
13815
|
await mergeHooksJson(entry.path, entry.content, options);
|
|
12564
13816
|
return;
|
|
12565
13817
|
}
|
|
13818
|
+
if (entry.kind === "cursor-hooks-json" && typeof entry.content === "object") {
|
|
13819
|
+
await mergeCursorHooksFile(entry.path, entry.content, options);
|
|
13820
|
+
return;
|
|
13821
|
+
}
|
|
12566
13822
|
if (entry.kind === "hooks-toml" && typeof entry.content === "object") {
|
|
12567
13823
|
await mergeHooksToml(entry.path, entry.content, options);
|
|
12568
13824
|
return;
|
|
@@ -12577,6 +13833,10 @@ async function uninstallEntry(entry, options) {
|
|
|
12577
13833
|
await uninstallHooksToml(entry.path, entry.content, options);
|
|
12578
13834
|
return;
|
|
12579
13835
|
}
|
|
13836
|
+
if (entry.kind === "cursor-hooks-json" && typeof entry.content === "object") {
|
|
13837
|
+
await uninstallCursorHooksFile(entry.path, entry.content, options);
|
|
13838
|
+
return;
|
|
13839
|
+
}
|
|
12580
13840
|
if (entry.kind === "hooks-json" && typeof entry.content === "object") {
|
|
12581
13841
|
await uninstallHooksJson(entry.path, entry.content, options);
|
|
12582
13842
|
return;
|
|
@@ -12609,6 +13869,67 @@ async function mergeHooksJson(filePath, content, { dryRun, force, onWrite }) {
|
|
|
12609
13869
|
await writeFile5(filePath, nextText, "utf8");
|
|
12610
13870
|
onWrite(`Installed ${filePath}`);
|
|
12611
13871
|
}
|
|
13872
|
+
async function mergeCursorHooksFile(filePath, content, { dryRun, force, onWrite }) {
|
|
13873
|
+
const { mkdir: mkdir6, writeFile: writeFile5 } = await import("node:fs/promises");
|
|
13874
|
+
const pathMod = await import("node:path");
|
|
13875
|
+
if (dryRun) {
|
|
13876
|
+
onWrite(`Would merge ${filePath}`);
|
|
13877
|
+
return;
|
|
13878
|
+
}
|
|
13879
|
+
const existingText = await readTextIfExists(filePath);
|
|
13880
|
+
const existing = existingText ? JSON.parse(existingText) : {};
|
|
13881
|
+
if (existingText !== null && !isPlainObject(existing) && !force) {
|
|
13882
|
+
throw new Error(
|
|
13883
|
+
`Refusing to update non-object JSON file: ${filePath}. Re-run with --force if this is intentional.`
|
|
13884
|
+
);
|
|
13885
|
+
}
|
|
13886
|
+
const merged = mergeCursorHooksJson(existing, content);
|
|
13887
|
+
const nextText = `${JSON.stringify(merged, null, 2)}
|
|
13888
|
+
`;
|
|
13889
|
+
if (existingText === nextText) {
|
|
13890
|
+
onWrite(`Already installed ${filePath}`);
|
|
13891
|
+
return;
|
|
13892
|
+
}
|
|
13893
|
+
await mkdir6(pathMod.dirname(filePath), { recursive: true });
|
|
13894
|
+
await writeFile5(filePath, nextText, "utf8");
|
|
13895
|
+
onWrite(`Installed ${filePath}`);
|
|
13896
|
+
}
|
|
13897
|
+
async function uninstallCursorHooksFile(filePath, content, { dryRun, onWrite }) {
|
|
13898
|
+
const existingText = await readTextIfExists(filePath);
|
|
13899
|
+
if (existingText === null) {
|
|
13900
|
+
onWrite(`Already uninstalled ${filePath}`);
|
|
13901
|
+
return;
|
|
13902
|
+
}
|
|
13903
|
+
let existing;
|
|
13904
|
+
try {
|
|
13905
|
+
existing = JSON.parse(existingText);
|
|
13906
|
+
} catch {
|
|
13907
|
+
onWrite(`Skipped non-JSON file ${filePath}`);
|
|
13908
|
+
return;
|
|
13909
|
+
}
|
|
13910
|
+
if (!isPlainObject(existing)) {
|
|
13911
|
+
onWrite(`Skipped non-object JSON file ${filePath}`);
|
|
13912
|
+
return;
|
|
13913
|
+
}
|
|
13914
|
+
const commands = new Set(collectCursorHookCommands(content));
|
|
13915
|
+
if (commands.size === 0) {
|
|
13916
|
+
onWrite(`Already uninstalled ${filePath}`);
|
|
13917
|
+
return;
|
|
13918
|
+
}
|
|
13919
|
+
const { next, changed } = stripCursorHookCommands(existing, commands);
|
|
13920
|
+
if (!changed) {
|
|
13921
|
+
onWrite(`Already uninstalled ${filePath}`);
|
|
13922
|
+
return;
|
|
13923
|
+
}
|
|
13924
|
+
if (dryRun) {
|
|
13925
|
+
onWrite(`Would uninstall ${filePath}`);
|
|
13926
|
+
return;
|
|
13927
|
+
}
|
|
13928
|
+
const { writeFile: writeFile5 } = await import("node:fs/promises");
|
|
13929
|
+
await writeFile5(filePath, `${JSON.stringify(next, null, 2)}
|
|
13930
|
+
`, "utf8");
|
|
13931
|
+
onWrite(`Uninstalled ${filePath}`);
|
|
13932
|
+
}
|
|
12612
13933
|
function mergeAgyHooksJson(existing, addition) {
|
|
12613
13934
|
const merged = structuredClone(isPlainObject(existing) ? existing : {});
|
|
12614
13935
|
const additionObject = isPlainObject(addition) ? addition : {};
|
|
@@ -12896,15 +14217,15 @@ async function uninstallGeneratedFile(filePath, { dryRun, onWrite }) {
|
|
|
12896
14217
|
import { randomUUID } from "node:crypto";
|
|
12897
14218
|
import { existsSync as existsSync2, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
12898
14219
|
import { homedir, hostname } from "node:os";
|
|
12899
|
-
import
|
|
14220
|
+
import path24 from "node:path";
|
|
12900
14221
|
function configDir(home = homedir()) {
|
|
12901
|
-
return
|
|
14222
|
+
return path24.join(home, ".vibetime");
|
|
12902
14223
|
}
|
|
12903
14224
|
function configPath(home = homedir()) {
|
|
12904
|
-
return
|
|
14225
|
+
return path24.join(configDir(home), "config.json");
|
|
12905
14226
|
}
|
|
12906
14227
|
function machineIdPath(home = homedir()) {
|
|
12907
|
-
return
|
|
14228
|
+
return path24.join(configDir(home), "machine-id");
|
|
12908
14229
|
}
|
|
12909
14230
|
function readConfig(home = homedir()) {
|
|
12910
14231
|
const file = configPath(home);
|
|
@@ -12951,15 +14272,15 @@ function defaultMachineName() {
|
|
|
12951
14272
|
init_fs();
|
|
12952
14273
|
|
|
12953
14274
|
// src/lib/logger.ts
|
|
12954
|
-
import { appendFile, mkdir as mkdir4, rename, stat as
|
|
14275
|
+
import { appendFile, mkdir as mkdir4, rename, stat as stat14 } from "node:fs/promises";
|
|
12955
14276
|
import { homedir as homedir2 } from "node:os";
|
|
12956
|
-
import
|
|
14277
|
+
import path25 from "node:path";
|
|
12957
14278
|
var MAX_BYTES = 1 * 1024 * 1024;
|
|
12958
14279
|
function logDir(home = homedir2()) {
|
|
12959
|
-
return
|
|
14280
|
+
return path25.join(home, ".vibetime", "logs");
|
|
12960
14281
|
}
|
|
12961
14282
|
function logPath(home = homedir2(), name = "cli.log") {
|
|
12962
|
-
return
|
|
14283
|
+
return path25.join(logDir(home), name);
|
|
12963
14284
|
}
|
|
12964
14285
|
function serializeError(error) {
|
|
12965
14286
|
if (error instanceof Error) {
|
|
@@ -12969,7 +14290,7 @@ function serializeError(error) {
|
|
|
12969
14290
|
}
|
|
12970
14291
|
async function rotateIfNeeded(file) {
|
|
12971
14292
|
try {
|
|
12972
|
-
const info = await
|
|
14293
|
+
const info = await stat14(file);
|
|
12973
14294
|
if (info.size > MAX_BYTES) {
|
|
12974
14295
|
await rename(file, `${file}.1`).catch(() => {
|
|
12975
14296
|
});
|
|
@@ -13134,8 +14455,8 @@ function buildHeaders(token, machine) {
|
|
|
13134
14455
|
...machine?.platform ? { "x-machine-platform": machine.platform } : {}
|
|
13135
14456
|
};
|
|
13136
14457
|
}
|
|
13137
|
-
function joinUrl(base,
|
|
13138
|
-
return new URL(
|
|
14458
|
+
function joinUrl(base, path27) {
|
|
14459
|
+
return new URL(path27, base.endsWith("/") ? base : `${base}/`).toString();
|
|
13139
14460
|
}
|
|
13140
14461
|
async function postRollupBatch(remote, rollups, options = {}) {
|
|
13141
14462
|
const response = await remote.fetchImpl(joinUrl(remote.baseUrl, "/v3/agent/ingest"), {
|
|
@@ -13205,7 +14526,7 @@ async function deleteMachine(remote, id) {
|
|
|
13205
14526
|
}
|
|
13206
14527
|
|
|
13207
14528
|
// src/lib/types.ts
|
|
13208
|
-
var BACKFILL_STATE_SCHEMA_VERSION =
|
|
14529
|
+
var BACKFILL_STATE_SCHEMA_VERSION = 7;
|
|
13209
14530
|
|
|
13210
14531
|
// src/cli.ts
|
|
13211
14532
|
function createRegistry() {
|
|
@@ -13225,6 +14546,7 @@ function createRegistry() {
|
|
|
13225
14546
|
registry.register(createGrokBuildAdapter());
|
|
13226
14547
|
registry.register(createZedAdapter());
|
|
13227
14548
|
registry.register(createKimiCodeAdapter());
|
|
14549
|
+
registry.register(createCursorAdapter());
|
|
13228
14550
|
return registry;
|
|
13229
14551
|
}
|
|
13230
14552
|
var defaultContext = {
|
|
@@ -13782,6 +15104,9 @@ async function listBackfillSourceFiles(source, options, ctx) {
|
|
|
13782
15104
|
if (source.id === "zed") {
|
|
13783
15105
|
return zedBackfillFiles(stringOption(options["source-root"]), resolveHome3(options, ctx), ctx.env);
|
|
13784
15106
|
}
|
|
15107
|
+
if (source.id === "cursor") {
|
|
15108
|
+
return cursorBackfillFiles(stringOption(options["source-root"]), resolveHome3(options, ctx), ctx.env);
|
|
15109
|
+
}
|
|
13785
15110
|
if (source.id === "pi") {
|
|
13786
15111
|
return piBackfillFiles(stringOption(options["source-root"]), resolveHome3(options, ctx), ctx.env);
|
|
13787
15112
|
}
|
|
@@ -13789,7 +15114,7 @@ async function listBackfillSourceFiles(source, options, ctx) {
|
|
|
13789
15114
|
const fileLists = await Promise.all(roots.map((r) => listJsonlFiles(r)));
|
|
13790
15115
|
const files = fileLists.flat().sort().slice(0, numberOption(options.limit) || void 0);
|
|
13791
15116
|
return Promise.all(files.map(async (filePath) => {
|
|
13792
|
-
const info = await
|
|
15117
|
+
const info = await stat15(filePath);
|
|
13793
15118
|
return { path: filePath, modifiedAt: info.mtime.toISOString() };
|
|
13794
15119
|
}));
|
|
13795
15120
|
}
|
|
@@ -14121,13 +15446,13 @@ function selectBackfillFilesForImport(files, watermarkTs) {
|
|
|
14121
15446
|
return picked;
|
|
14122
15447
|
}
|
|
14123
15448
|
function backfillIncrementalStatePath(home) {
|
|
14124
|
-
return
|
|
15449
|
+
return path26.join(home, ".vibetime", "backfill-state.json");
|
|
14125
15450
|
}
|
|
14126
15451
|
function syncLocalTriggerStatePath(home) {
|
|
14127
|
-
return
|
|
15452
|
+
return path26.join(home, ".vibetime", "sync-local-trigger.json");
|
|
14128
15453
|
}
|
|
14129
15454
|
function syncLocalTriggerLockPath(home) {
|
|
14130
|
-
return
|
|
15455
|
+
return path26.join(home, ".vibetime", "sync-local-trigger.lock");
|
|
14131
15456
|
}
|
|
14132
15457
|
function backfillRemoteKey(baseUrl) {
|
|
14133
15458
|
try {
|
|
@@ -14189,7 +15514,7 @@ async function readBackfillIncrementalStateFile(home, ctx) {
|
|
|
14189
15514
|
}
|
|
14190
15515
|
async function writeBackfillIncrementalStateFile(home, file) {
|
|
14191
15516
|
const statePath = backfillIncrementalStatePath(home);
|
|
14192
|
-
await mkdir5(
|
|
15517
|
+
await mkdir5(path26.dirname(statePath), { recursive: true });
|
|
14193
15518
|
await writeFile4(statePath, `${JSON.stringify(file, null, 2)}
|
|
14194
15519
|
`, "utf8");
|
|
14195
15520
|
}
|
|
@@ -14238,7 +15563,7 @@ async function readSyncLocalTriggerState(statePath) {
|
|
|
14238
15563
|
return nextState;
|
|
14239
15564
|
}
|
|
14240
15565
|
async function writeSyncLocalTriggerState(statePath, state) {
|
|
14241
|
-
await mkdir5(
|
|
15566
|
+
await mkdir5(path26.dirname(statePath), { recursive: true });
|
|
14242
15567
|
await writeFile4(statePath, `${JSON.stringify(state, null, 2)}
|
|
14243
15568
|
`, "utf8");
|
|
14244
15569
|
}
|
|
@@ -14253,12 +15578,12 @@ async function readSyncLocalLock(lockPath) {
|
|
|
14253
15578
|
return { pid: lock.pid, startedAt: lock.startedAt };
|
|
14254
15579
|
}
|
|
14255
15580
|
async function writeSyncLocalLock(lockPath, lock) {
|
|
14256
|
-
await mkdir5(
|
|
15581
|
+
await mkdir5(path26.dirname(lockPath), { recursive: true });
|
|
14257
15582
|
await writeFile4(lockPath, `${JSON.stringify(lock, null, 2)}
|
|
14258
15583
|
`, "utf8");
|
|
14259
15584
|
}
|
|
14260
15585
|
async function acquireSyncLocalLock(lockPath, lock) {
|
|
14261
|
-
await mkdir5(
|
|
15586
|
+
await mkdir5(path26.dirname(lockPath), { recursive: true });
|
|
14262
15587
|
try {
|
|
14263
15588
|
const handle = await open(lockPath, "wx");
|
|
14264
15589
|
try {
|
|
@@ -14338,10 +15663,10 @@ function syncLocalRunnerEntryArgs(cliPath) {
|
|
|
14338
15663
|
if (cliPath.endsWith(".ts")) {
|
|
14339
15664
|
return ["--import", "tsx", cliPath];
|
|
14340
15665
|
}
|
|
14341
|
-
return [
|
|
15666
|
+
return [path26.resolve(path26.dirname(cliPath), "../bin/vibetime.mjs")];
|
|
14342
15667
|
}
|
|
14343
15668
|
function resolveHome3(options, ctx) {
|
|
14344
|
-
return
|
|
15669
|
+
return path26.resolve(stringOption(options.home) || ctx.env.HOME || os13.homedir());
|
|
14345
15670
|
}
|
|
14346
15671
|
function requestedTargets(options) {
|
|
14347
15672
|
const value = options.target || options.targets;
|