@yhong91/vibetime 0.1.56 → 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 +1614 -335
- 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
|
}
|
|
@@ -6168,95 +6168,1301 @@ function baseCopilotEvent(event) {
|
|
|
6168
6168
|
...event
|
|
6169
6169
|
};
|
|
6170
6170
|
}
|
|
6171
|
-
async function copilotBackfillFiles(sourceRoot, home = os5.homedir(), _env) {
|
|
6172
|
-
const sessionDir = sourceRoot || path12.join(home, ".copilot", "session-state");
|
|
6173
|
-
const results = [];
|
|
6174
|
-
let entries;
|
|
6175
|
-
try {
|
|
6176
|
-
entries = await readdir5(sessionDir);
|
|
6177
|
-
} catch {
|
|
6178
|
-
return [];
|
|
6171
|
+
async function copilotBackfillFiles(sourceRoot, home = os5.homedir(), _env) {
|
|
6172
|
+
const sessionDir = sourceRoot || path12.join(home, ".copilot", "session-state");
|
|
6173
|
+
const results = [];
|
|
6174
|
+
let entries;
|
|
6175
|
+
try {
|
|
6176
|
+
entries = await readdir5(sessionDir);
|
|
6177
|
+
} catch {
|
|
6178
|
+
return [];
|
|
6179
|
+
}
|
|
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() }];
|
|
6179
7387
|
}
|
|
6180
|
-
|
|
6181
|
-
|
|
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,9 +9356,9 @@ 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();
|
|
8157
9363
|
function piHostSessionIdFromBasename(basename) {
|
|
8158
9364
|
const withoutExt = basename.endsWith(".jsonl") ? basename.slice(0, -".jsonl".length) : basename;
|
|
@@ -8167,7 +9373,7 @@ function piHostSessionIdFromBasename(basename) {
|
|
|
8167
9373
|
}
|
|
8168
9374
|
function resolvePiBackfillGroupId(filePath) {
|
|
8169
9375
|
const normalized = filePath.replaceAll("\\", "/");
|
|
8170
|
-
const base =
|
|
9376
|
+
const base = path17.basename(filePath);
|
|
8171
9377
|
const nest = normalized.match(/([^/]+)\/[^/]+\/run-\d+\/session\.jsonl$/);
|
|
8172
9378
|
if (nest) {
|
|
8173
9379
|
return piHostSessionIdFromBasename(nest[1]);
|
|
@@ -8179,8 +9385,8 @@ function resolvePiBackfillGroupId(filePath) {
|
|
|
8179
9385
|
}
|
|
8180
9386
|
function parsePiSubagentLink(filePath, headerParentSession) {
|
|
8181
9387
|
if (headerParentSession) {
|
|
8182
|
-
const parentFile =
|
|
8183
|
-
const parentSessionId2 = parentFile ?
|
|
9388
|
+
const parentFile = path17.isAbsolute(headerParentSession) ? headerParentSession : void 0;
|
|
9389
|
+
const parentSessionId2 = parentFile ? path17.basename(parentFile, ".jsonl") : headerParentSession;
|
|
8184
9390
|
return { parentSessionId: parentSessionId2, parentSessionFile: parentFile, explicit: true };
|
|
8185
9391
|
}
|
|
8186
9392
|
const normalized = filePath.replaceAll("\\", "/");
|
|
@@ -8193,20 +9399,20 @@ function parsePiSubagentLink(filePath, headerParentSession) {
|
|
|
8193
9399
|
return void 0;
|
|
8194
9400
|
}
|
|
8195
9401
|
const parentSessionId = parentBasename.endsWith(".jsonl") ? parentBasename.slice(0, -".jsonl".length) : parentBasename;
|
|
8196
|
-
const parentDir =
|
|
8197
|
-
const parentSessionFile =
|
|
9402
|
+
const parentDir = path17.dirname(path17.dirname(path17.dirname(filePath)));
|
|
9403
|
+
const parentSessionFile = path17.join(path17.dirname(parentDir), `${parentBasename}.jsonl`);
|
|
8198
9404
|
return { parentSessionId, parentSessionFile, explicit: false };
|
|
8199
9405
|
}
|
|
8200
9406
|
async function resolveParentContext(link, options) {
|
|
8201
9407
|
if (link.parentSessionFile) {
|
|
8202
9408
|
try {
|
|
8203
|
-
const text = await
|
|
9409
|
+
const text = await readFile11(link.parentSessionFile, "utf8");
|
|
8204
9410
|
const firstLine = text.split("\n").find((line) => line.trim().length > 0);
|
|
8205
9411
|
const raw = firstLine ? parseJsonLine(firstLine) : void 0;
|
|
8206
9412
|
if (raw) {
|
|
8207
9413
|
const id = stringField(raw, "id");
|
|
8208
9414
|
const cwd = stringField(raw, "cwd");
|
|
8209
|
-
const project = cwd ?
|
|
9415
|
+
const project = cwd ? path17.basename(cwd) : void 0;
|
|
8210
9416
|
if (id || cwd || project) {
|
|
8211
9417
|
return { sessionId: id, cwd, project };
|
|
8212
9418
|
}
|
|
@@ -8255,7 +9461,7 @@ function rebuildEventIdentity2(event) {
|
|
|
8255
9461
|
};
|
|
8256
9462
|
}
|
|
8257
9463
|
async function parsePiSessionFile(filePath, options) {
|
|
8258
|
-
const text = await
|
|
9464
|
+
const text = await readFile11(filePath, "utf8");
|
|
8259
9465
|
const lines = text.split("\n").filter(Boolean);
|
|
8260
9466
|
let sessionId;
|
|
8261
9467
|
let cwd;
|
|
@@ -8287,7 +9493,7 @@ async function parsePiSessionFile(filePath, options) {
|
|
|
8287
9493
|
sessionId = stringField(raw, "id") || state.sessionId;
|
|
8288
9494
|
state.sessionId = sessionId || state.sessionId;
|
|
8289
9495
|
cwd = stringField(raw, "cwd") || cwd;
|
|
8290
|
-
project = cwd ?
|
|
9496
|
+
project = cwd ? path17.basename(cwd) : project;
|
|
8291
9497
|
headerParentSession = stringField(raw, "parentSession") || headerParentSession;
|
|
8292
9498
|
continue;
|
|
8293
9499
|
}
|
|
@@ -8494,7 +9700,7 @@ async function parsePiFile(filePath, options) {
|
|
|
8494
9700
|
async function parsePiWorkflowRunFile(filePath, options) {
|
|
8495
9701
|
let raw;
|
|
8496
9702
|
try {
|
|
8497
|
-
raw = JSON.parse(await
|
|
9703
|
+
raw = JSON.parse(await readFile11(filePath, "utf8"));
|
|
8498
9704
|
} catch {
|
|
8499
9705
|
return [];
|
|
8500
9706
|
}
|
|
@@ -8562,17 +9768,17 @@ async function parsePiWorkflowRunFile(filePath, options) {
|
|
|
8562
9768
|
return state.events.filter((event) => matchesBackfillFilters(event, options));
|
|
8563
9769
|
}
|
|
8564
9770
|
async function findPiSessionFileById(sessionId, options) {
|
|
8565
|
-
const sessionsDir = piSessionDir(
|
|
9771
|
+
const sessionsDir = piSessionDir(path17.resolve(stringOption(options.home) || os8.homedir()));
|
|
8566
9772
|
try {
|
|
8567
|
-
const projectDirs = await
|
|
9773
|
+
const projectDirs = await readdir8(sessionsDir, { withFileTypes: true });
|
|
8568
9774
|
for (const dir of projectDirs) {
|
|
8569
9775
|
if (!dir.isDirectory()) {
|
|
8570
9776
|
continue;
|
|
8571
9777
|
}
|
|
8572
|
-
const names = await
|
|
9778
|
+
const names = await readdir8(path17.join(sessionsDir, dir.name));
|
|
8573
9779
|
const hit = names.find((name) => name.endsWith(`_${sessionId}.jsonl`));
|
|
8574
9780
|
if (hit) {
|
|
8575
|
-
return
|
|
9781
|
+
return path17.join(sessionsDir, dir.name, hit);
|
|
8576
9782
|
}
|
|
8577
9783
|
}
|
|
8578
9784
|
} catch {
|
|
@@ -8814,23 +10020,23 @@ export default function (pi: ExtensionAPI) {
|
|
|
8814
10020
|
function piAgentDir(home, env) {
|
|
8815
10021
|
const override = env?.PI_CODING_AGENT_DIR;
|
|
8816
10022
|
if (override && override.trim()) {
|
|
8817
|
-
return
|
|
10023
|
+
return path17.resolve(override);
|
|
8818
10024
|
}
|
|
8819
|
-
return
|
|
10025
|
+
return path17.join(home, ".pi", "agent");
|
|
8820
10026
|
}
|
|
8821
10027
|
function piSessionDir(home, env) {
|
|
8822
10028
|
const override = env?.PI_CODING_AGENT_SESSION_DIR;
|
|
8823
10029
|
if (override && override.trim()) {
|
|
8824
|
-
return
|
|
10030
|
+
return path17.resolve(override);
|
|
8825
10031
|
}
|
|
8826
|
-
return
|
|
10032
|
+
return path17.join(piAgentDir(home, env), "sessions");
|
|
8827
10033
|
}
|
|
8828
10034
|
function piWorkflowProjectsDir(home) {
|
|
8829
|
-
return
|
|
10035
|
+
return path17.join(home, ".pi", "workflows", "projects");
|
|
8830
10036
|
}
|
|
8831
10037
|
async function workflowRunGroupId(filePath) {
|
|
8832
10038
|
try {
|
|
8833
|
-
const raw = JSON.parse(await
|
|
10039
|
+
const raw = JSON.parse(await readFile11(filePath, "utf8"));
|
|
8834
10040
|
if (!isPlainObject(raw)) {
|
|
8835
10041
|
return void 0;
|
|
8836
10042
|
}
|
|
@@ -8839,14 +10045,14 @@ async function workflowRunGroupId(filePath) {
|
|
|
8839
10045
|
return void 0;
|
|
8840
10046
|
}
|
|
8841
10047
|
}
|
|
8842
|
-
async function piBackfillFiles(sourceRoot, home =
|
|
10048
|
+
async function piBackfillFiles(sourceRoot, home = os8.homedir(), env) {
|
|
8843
10049
|
const lists = sourceRoot ? [await listFilesByExtensions(sourceRoot, [".jsonl", ".json"])] : await Promise.all([
|
|
8844
10050
|
listFilesByExtensions(piSessionDir(home, env), [".jsonl"]),
|
|
8845
10051
|
listFilesByExtensions(piWorkflowProjectsDir(home), [".json"])
|
|
8846
10052
|
]);
|
|
8847
10053
|
const files = lists.flat().sort();
|
|
8848
10054
|
return Promise.all(files.map(async (filePath) => {
|
|
8849
|
-
const info = await
|
|
10055
|
+
const info = await stat9(filePath);
|
|
8850
10056
|
let groupId = resolvePiBackfillGroupId(filePath);
|
|
8851
10057
|
if (!groupId && filePath.endsWith(".json")) {
|
|
8852
10058
|
groupId = await workflowRunGroupId(filePath);
|
|
@@ -8868,12 +10074,12 @@ function createPiAdapter() {
|
|
|
8868
10074
|
return piAgentDir(home, env);
|
|
8869
10075
|
},
|
|
8870
10076
|
installedPath(home, env) {
|
|
8871
|
-
return
|
|
10077
|
+
return path17.join(piAgentDir(home, env), "extensions", "vibetime.ts");
|
|
8872
10078
|
},
|
|
8873
10079
|
async isInstalled(home, env) {
|
|
8874
10080
|
try {
|
|
8875
10081
|
const { pathExists: pathExists2 } = await Promise.resolve().then(() => (init_fs(), fs_exports));
|
|
8876
|
-
return await pathExists2(
|
|
10082
|
+
return await pathExists2(path17.join(piAgentDir(home, env), "extensions", "vibetime.ts"));
|
|
8877
10083
|
} catch {
|
|
8878
10084
|
return false;
|
|
8879
10085
|
}
|
|
@@ -8881,7 +10087,7 @@ function createPiAdapter() {
|
|
|
8881
10087
|
installEntries(home, env) {
|
|
8882
10088
|
return [{
|
|
8883
10089
|
kind: "file",
|
|
8884
|
-
path:
|
|
10090
|
+
path: path17.join(piAgentDir(home, env), "extensions", "vibetime.ts"),
|
|
8885
10091
|
content: piExtensionContent()
|
|
8886
10092
|
}];
|
|
8887
10093
|
},
|
|
@@ -8893,14 +10099,14 @@ function createPiAdapter() {
|
|
|
8893
10099
|
}
|
|
8894
10100
|
|
|
8895
10101
|
// src/adapters/qoder-cn.ts
|
|
8896
|
-
import { readdir as
|
|
8897
|
-
import
|
|
8898
|
-
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";
|
|
8899
10105
|
|
|
8900
10106
|
// src/adapters/qoder-local-db.ts
|
|
8901
10107
|
import { access } from "node:fs/promises";
|
|
8902
|
-
import
|
|
8903
|
-
import
|
|
10108
|
+
import os9 from "node:os";
|
|
10109
|
+
import path18 from "node:path";
|
|
8904
10110
|
function takeQoderDbModelCall(calls, requestId, blockStart) {
|
|
8905
10111
|
if (requestId) {
|
|
8906
10112
|
const call = calls.byRequestId.get(requestId)?.shift();
|
|
@@ -8917,14 +10123,14 @@ function takeQoderDbModelCall(calls, requestId, blockStart) {
|
|
|
8917
10123
|
}
|
|
8918
10124
|
return calls.ordered.shift();
|
|
8919
10125
|
}
|
|
8920
|
-
function appDataRoot(appDirName, home =
|
|
10126
|
+
function appDataRoot(appDirName, home = os9.homedir()) {
|
|
8921
10127
|
if (process.platform === "darwin") {
|
|
8922
|
-
return
|
|
10128
|
+
return path18.join(home, "Library", "Application Support", appDirName);
|
|
8923
10129
|
}
|
|
8924
10130
|
if (process.platform === "win32") {
|
|
8925
|
-
return
|
|
10131
|
+
return path18.join(process.env.APPDATA || path18.join(home, "AppData", "Roaming"), appDirName);
|
|
8926
10132
|
}
|
|
8927
|
-
return
|
|
10133
|
+
return path18.join(home, ".config", appDirName);
|
|
8928
10134
|
}
|
|
8929
10135
|
function qoderLocalDbCandidates(appDirName) {
|
|
8930
10136
|
const candidates = [];
|
|
@@ -8934,8 +10140,8 @@ function qoderLocalDbCandidates(appDirName) {
|
|
|
8934
10140
|
}
|
|
8935
10141
|
const configRoot = appDataRoot(appDirName);
|
|
8936
10142
|
candidates.push(
|
|
8937
|
-
|
|
8938
|
-
|
|
10143
|
+
path18.join(configRoot, "SharedClientCache", "cache", "db", "local.db"),
|
|
10144
|
+
path18.join(configRoot, "SharedClientCache", "db", "local.db")
|
|
8939
10145
|
);
|
|
8940
10146
|
return candidates;
|
|
8941
10147
|
}
|
|
@@ -8944,7 +10150,7 @@ async function loadQoderIdeModelCatalog(appDirName, home) {
|
|
|
8944
10150
|
try {
|
|
8945
10151
|
const { DatabaseSync } = await import("node:sqlite");
|
|
8946
10152
|
const db = new DatabaseSync(
|
|
8947
|
-
|
|
10153
|
+
path18.join(appDataRoot(appDirName, home), "User", "globalStorage", "state.vscdb"),
|
|
8948
10154
|
{ readOnly: true }
|
|
8949
10155
|
);
|
|
8950
10156
|
try {
|
|
@@ -9086,7 +10292,7 @@ function resolveSessionPreferredModel(db, sessionId, modelMap) {
|
|
|
9086
10292
|
|
|
9087
10293
|
// src/adapters/qoder-cn.ts
|
|
9088
10294
|
function parseQoderCnPaths(filePath) {
|
|
9089
|
-
const parts = filePath.split(
|
|
10295
|
+
const parts = filePath.split(path19.sep);
|
|
9090
10296
|
const subagentsIdx = parts.lastIndexOf("subagents");
|
|
9091
10297
|
let sessionId = "";
|
|
9092
10298
|
let projectName = "";
|
|
@@ -9096,17 +10302,17 @@ function parseQoderCnPaths(filePath) {
|
|
|
9096
10302
|
sessionId = parts[subagentsIdx - 1];
|
|
9097
10303
|
projectName = parts[subagentsIdx - 2];
|
|
9098
10304
|
const projectsIdx = parts.lastIndexOf("projects");
|
|
9099
|
-
configDir2 = parts.slice(0, projectsIdx).join(
|
|
9100
|
-
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);
|
|
9101
10307
|
} else {
|
|
9102
10308
|
const filename = parts.at(-1) || "";
|
|
9103
|
-
sessionId =
|
|
10309
|
+
sessionId = path19.basename(filename, ".jsonl");
|
|
9104
10310
|
projectName = parts.at(-2) || "";
|
|
9105
10311
|
if (projectName === "transcript") {
|
|
9106
10312
|
projectName = parts.at(-3) || "";
|
|
9107
10313
|
}
|
|
9108
10314
|
const projectsIdx = parts.lastIndexOf("projects");
|
|
9109
|
-
configDir2 = parts.slice(0, projectsIdx).join(
|
|
10315
|
+
configDir2 = parts.slice(0, projectsIdx).join(path19.sep);
|
|
9110
10316
|
}
|
|
9111
10317
|
return { configDir: configDir2, projectName, sessionId, mainTranscriptPath };
|
|
9112
10318
|
}
|
|
@@ -9129,7 +10335,7 @@ function rebuildEventIdentity3(event) {
|
|
|
9129
10335
|
}
|
|
9130
10336
|
async function parseModelNamesFromDynamicTexts(dynamicTextsPath) {
|
|
9131
10337
|
try {
|
|
9132
|
-
const content = await
|
|
10338
|
+
const content = await readFile12(dynamicTextsPath, "utf8");
|
|
9133
10339
|
const json = JSON.parse(content);
|
|
9134
10340
|
const texts = json.texts || {};
|
|
9135
10341
|
const map = {};
|
|
@@ -9145,10 +10351,10 @@ async function parseModelNamesFromDynamicTexts(dynamicTextsPath) {
|
|
|
9145
10351
|
}
|
|
9146
10352
|
}
|
|
9147
10353
|
async function loadQoderCnModelNames(configDir2, home) {
|
|
9148
|
-
const map = await parseModelNamesFromDynamicTexts(
|
|
10354
|
+
const map = await parseModelNamesFromDynamicTexts(path19.join(configDir2, ".auth", "dynamic-texts.json"));
|
|
9149
10355
|
const siblingConfigDir = configDir2.replace(/\.qoder-cn$/, ".qoder");
|
|
9150
10356
|
if (siblingConfigDir !== configDir2) {
|
|
9151
|
-
const siblingMap = await parseModelNamesFromDynamicTexts(
|
|
10357
|
+
const siblingMap = await parseModelNamesFromDynamicTexts(path19.join(siblingConfigDir, ".auth", "dynamic-texts.json"));
|
|
9152
10358
|
for (const [key, val] of Object.entries(siblingMap)) {
|
|
9153
10359
|
if (!(key in map)) {
|
|
9154
10360
|
map[key] = val;
|
|
@@ -9167,15 +10373,15 @@ async function loadQoderCnModelNames(configDir2, home) {
|
|
|
9167
10373
|
}
|
|
9168
10374
|
async function loadQoderCnSegmentModelCalls(filePath, isSubagentSession, modelMap) {
|
|
9169
10375
|
const { configDir: configDir2, projectName, sessionId } = parseQoderCnPaths(filePath);
|
|
9170
|
-
const segmentsPath =
|
|
10376
|
+
const segmentsPath = path19.join(configDir2, "logs", "sessions", projectName, sessionId, "segments");
|
|
9171
10377
|
const modelCalls = [];
|
|
9172
10378
|
try {
|
|
9173
|
-
const files = await
|
|
10379
|
+
const files = await readdir9(segmentsPath);
|
|
9174
10380
|
for (const file of files) {
|
|
9175
10381
|
if (!file.endsWith(".jsonl")) {
|
|
9176
10382
|
continue;
|
|
9177
10383
|
}
|
|
9178
|
-
const content = await
|
|
10384
|
+
const content = await readFile12(path19.join(segmentsPath, file), "utf8");
|
|
9179
10385
|
let currentTurnIsSubagent = false;
|
|
9180
10386
|
for (const line of content.split("\n").filter(Boolean)) {
|
|
9181
10387
|
const raw = parseJsonLine(line);
|
|
@@ -9209,7 +10415,7 @@ async function loadQoderCnSegmentModelCalls(filePath, isSubagentSession, modelMa
|
|
|
9209
10415
|
return modelCalls.filter((call) => call.isSubagent === isSubagentSession);
|
|
9210
10416
|
}
|
|
9211
10417
|
async function parseQoderCnSessionFile(filePath, options) {
|
|
9212
|
-
const text = await
|
|
10418
|
+
const text = await readFile12(filePath, "utf8");
|
|
9213
10419
|
const lines = text.split("\n").filter(Boolean);
|
|
9214
10420
|
const parsedPaths = parseQoderCnPaths(filePath);
|
|
9215
10421
|
const { configDir: configDir2 } = parsedPaths;
|
|
@@ -9220,7 +10426,7 @@ async function parseQoderCnSessionFile(filePath, options) {
|
|
|
9220
10426
|
let cwd;
|
|
9221
10427
|
let project = projectContext.project;
|
|
9222
10428
|
let model;
|
|
9223
|
-
const home =
|
|
10429
|
+
const home = path19.resolve(stringOption(options.home) || os10.homedir());
|
|
9224
10430
|
const modelMap = await loadQoderCnModelNames(configDir2, home);
|
|
9225
10431
|
const isSubagentSession = filePath.includes("subagents");
|
|
9226
10432
|
const segmentModelCalls = await loadQoderCnSegmentModelCalls(filePath, isSubagentSession, modelMap);
|
|
@@ -9255,7 +10461,7 @@ async function parseQoderCnSessionFile(filePath, options) {
|
|
|
9255
10461
|
sessionId = stringField(raw, "sessionId") || sessionId;
|
|
9256
10462
|
state.sessionId = sessionId;
|
|
9257
10463
|
cwd = stringField(raw, "cwd") || cwd;
|
|
9258
|
-
project = projectContext.project || (cwd ?
|
|
10464
|
+
project = projectContext.project || (cwd ? path19.basename(cwd) : project || await qoderCnProjectFromFilePath(filePath, options));
|
|
9259
10465
|
if (!ts) {
|
|
9260
10466
|
continue;
|
|
9261
10467
|
}
|
|
@@ -9591,7 +10797,7 @@ async function parseQoderCnSessionFile(filePath, options) {
|
|
|
9591
10797
|
}
|
|
9592
10798
|
dbModelCalls ??= await loadQoderDbModelCalls("QoderCN", parsedPaths.sessionId, modelMap);
|
|
9593
10799
|
if (dbModelCalls.rootSessionId) {
|
|
9594
|
-
const parentPath =
|
|
10800
|
+
const parentPath = path19.join(path19.dirname(filePath), `${dbModelCalls.rootSessionId}.jsonl`);
|
|
9595
10801
|
const parentSourcePathHash = `sha256:${createStableHash(parentPath)}`;
|
|
9596
10802
|
return withoutSubagentTurnEvents(validEvents).map((event) => rebuildEventIdentity3({
|
|
9597
10803
|
...event,
|
|
@@ -9744,28 +10950,28 @@ function extractQoderAttachedPrompt(content) {
|
|
|
9744
10950
|
}
|
|
9745
10951
|
async function qoderCnProjectContextFromLines(filePath, lines, options, configDir2) {
|
|
9746
10952
|
const { projectName: projectDir, sessionId } = parseQoderCnPaths(filePath);
|
|
9747
|
-
const isSubagent = filePath.includes(`${
|
|
10953
|
+
const isSubagent = filePath.includes(`${path19.sep}subagents${path19.sep}`);
|
|
9748
10954
|
const inherited = isSubagent ? await readPersistedSessionContextFromOptions(options, sessionId) : void 0;
|
|
9749
10955
|
let cwds = [];
|
|
9750
10956
|
for (const line of lines) {
|
|
9751
10957
|
const raw = parseJsonLine(line);
|
|
9752
10958
|
const cwd = raw ? stringField(raw, "cwd") : void 0;
|
|
9753
|
-
if (cwd &&
|
|
10959
|
+
if (cwd && path19.isAbsolute(cwd)) {
|
|
9754
10960
|
cwds.push(cwd);
|
|
9755
10961
|
}
|
|
9756
10962
|
}
|
|
9757
10963
|
if (isSubagent) {
|
|
9758
|
-
if (inherited?.cwd &&
|
|
10964
|
+
if (inherited?.cwd && path19.isAbsolute(inherited.cwd)) {
|
|
9759
10965
|
cwds = [inherited.cwd];
|
|
9760
10966
|
} else {
|
|
9761
|
-
const parentSessionPath =
|
|
10967
|
+
const parentSessionPath = path19.join(configDir2, "projects", projectDir, `${sessionId}.jsonl`);
|
|
9762
10968
|
try {
|
|
9763
|
-
const parentText = await
|
|
10969
|
+
const parentText = await readFile12(parentSessionPath, "utf8");
|
|
9764
10970
|
const parentCwds = [];
|
|
9765
10971
|
for (const line of parentText.split("\n").filter(Boolean)) {
|
|
9766
10972
|
const raw = parseJsonLine(line);
|
|
9767
10973
|
const cwd = raw ? stringField(raw, "cwd") : void 0;
|
|
9768
|
-
if (cwd &&
|
|
10974
|
+
if (cwd && path19.isAbsolute(cwd)) {
|
|
9769
10975
|
parentCwds.push(cwd);
|
|
9770
10976
|
}
|
|
9771
10977
|
}
|
|
@@ -9777,7 +10983,7 @@ async function qoderCnProjectContextFromLines(filePath, lines, options, configDi
|
|
|
9777
10983
|
}
|
|
9778
10984
|
}
|
|
9779
10985
|
const root = await gitRootFromCwds2(cwds) || qoderCnProjectRootFromCwds(projectDir, cwds);
|
|
9780
|
-
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));
|
|
9781
10987
|
return {
|
|
9782
10988
|
project,
|
|
9783
10989
|
workspaceId: createWorkspaceId({ projectName: project, repoRoot: root })
|
|
@@ -9786,15 +10992,15 @@ async function qoderCnProjectContextFromLines(filePath, lines, options, configDi
|
|
|
9786
10992
|
async function gitRootFromCwds2(cwds) {
|
|
9787
10993
|
const seen = /* @__PURE__ */ new Set();
|
|
9788
10994
|
for (const cwd of cwds) {
|
|
9789
|
-
let current =
|
|
10995
|
+
let current = path19.resolve(cwd);
|
|
9790
10996
|
while (!seen.has(current)) {
|
|
9791
10997
|
seen.add(current);
|
|
9792
10998
|
try {
|
|
9793
|
-
await
|
|
10999
|
+
await stat10(path19.join(current, ".git"));
|
|
9794
11000
|
return current;
|
|
9795
11001
|
} catch {
|
|
9796
11002
|
}
|
|
9797
|
-
const parent =
|
|
11003
|
+
const parent = path19.dirname(current);
|
|
9798
11004
|
if (parent === current) {
|
|
9799
11005
|
break;
|
|
9800
11006
|
}
|
|
@@ -9805,12 +11011,12 @@ async function gitRootFromCwds2(cwds) {
|
|
|
9805
11011
|
}
|
|
9806
11012
|
function qoderCnProjectRootFromCwds(projectDir, cwds) {
|
|
9807
11013
|
for (const cwd of cwds) {
|
|
9808
|
-
let current =
|
|
11014
|
+
let current = path19.resolve(cwd);
|
|
9809
11015
|
while (true) {
|
|
9810
11016
|
if (encodeQoderCnProjectPath(current) === projectDir) {
|
|
9811
11017
|
return current;
|
|
9812
11018
|
}
|
|
9813
|
-
const parent =
|
|
11019
|
+
const parent = path19.dirname(current);
|
|
9814
11020
|
if (parent === current) {
|
|
9815
11021
|
break;
|
|
9816
11022
|
}
|
|
@@ -9820,14 +11026,14 @@ function qoderCnProjectRootFromCwds(projectDir, cwds) {
|
|
|
9820
11026
|
return void 0;
|
|
9821
11027
|
}
|
|
9822
11028
|
function encodeQoderCnProjectPath(value) {
|
|
9823
|
-
return
|
|
11029
|
+
return path19.resolve(value).split(path19.sep).join("-").replaceAll("_", "-");
|
|
9824
11030
|
}
|
|
9825
11031
|
async function qoderCnProjectFromFilePath(filePath, options) {
|
|
9826
|
-
const projectDir =
|
|
9827
|
-
const home = options ?
|
|
11032
|
+
const projectDir = path19.basename(path19.dirname(filePath));
|
|
11033
|
+
const home = options ? path19.resolve(stringOption(options.home) || os10.homedir()) : os10.homedir();
|
|
9828
11034
|
const resolved = await resolveQoderCnProjectPath(projectDir, home);
|
|
9829
11035
|
if (resolved) {
|
|
9830
|
-
return
|
|
11036
|
+
return path19.basename(resolved);
|
|
9831
11037
|
}
|
|
9832
11038
|
const homePrefix = `${encodeQoderCnProjectPath(home)}-`;
|
|
9833
11039
|
if (projectDir.startsWith(homePrefix)) {
|
|
@@ -9847,12 +11053,12 @@ async function resolveQoderCnProjectPath(projectDir, home) {
|
|
|
9847
11053
|
while (projectDir.startsWith(`${currentEncoded}-`)) {
|
|
9848
11054
|
let matchedChild;
|
|
9849
11055
|
try {
|
|
9850
|
-
const entries = await
|
|
11056
|
+
const entries = await readdir9(current, { withFileTypes: true });
|
|
9851
11057
|
for (const entry of entries) {
|
|
9852
11058
|
if (!entry.isDirectory()) {
|
|
9853
11059
|
continue;
|
|
9854
11060
|
}
|
|
9855
|
-
const candidate =
|
|
11061
|
+
const candidate = path19.join(current, entry.name);
|
|
9856
11062
|
const encoded = encodeQoderCnProjectPath(candidate);
|
|
9857
11063
|
if (encoded === projectDir) {
|
|
9858
11064
|
return candidate;
|
|
@@ -9897,9 +11103,9 @@ function hookConfig6() {
|
|
|
9897
11103
|
function qoderCnConfigDir(home, env) {
|
|
9898
11104
|
const override = env?.QODER_CN_CONFIG_DIR;
|
|
9899
11105
|
if (override && override.trim()) {
|
|
9900
|
-
return
|
|
11106
|
+
return path19.resolve(override);
|
|
9901
11107
|
}
|
|
9902
|
-
return
|
|
11108
|
+
return path19.join(home, ".qoder-cn");
|
|
9903
11109
|
}
|
|
9904
11110
|
function createQoderCnAdapter() {
|
|
9905
11111
|
return {
|
|
@@ -9911,27 +11117,27 @@ function createQoderCnAdapter() {
|
|
|
9911
11117
|
return qoderCnConfigDir(home, env);
|
|
9912
11118
|
},
|
|
9913
11119
|
installedPath(home, env) {
|
|
9914
|
-
return
|
|
11120
|
+
return path19.join(qoderCnConfigDir(home, env), "settings.json");
|
|
9915
11121
|
},
|
|
9916
11122
|
async isInstalled(home, env) {
|
|
9917
11123
|
return isHooksJsonInstalled(
|
|
9918
|
-
|
|
11124
|
+
path19.join(qoderCnConfigDir(home, env), "settings.json"),
|
|
9919
11125
|
"vibetime hook --agent qoder-cn"
|
|
9920
11126
|
);
|
|
9921
11127
|
},
|
|
9922
11128
|
installEntries(home, env) {
|
|
9923
11129
|
return [{
|
|
9924
11130
|
kind: "hooks-json",
|
|
9925
|
-
path:
|
|
11131
|
+
path: path19.join(qoderCnConfigDir(home, env), "settings.json"),
|
|
9926
11132
|
content: hookConfig6()
|
|
9927
11133
|
}];
|
|
9928
11134
|
},
|
|
9929
11135
|
sourcePaths(home, env) {
|
|
9930
11136
|
const base = qoderCnConfigDir(home, env);
|
|
9931
11137
|
return [
|
|
9932
|
-
|
|
9933
|
-
|
|
9934
|
-
|
|
11138
|
+
path19.join(base, "projects"),
|
|
11139
|
+
path19.join(base, ".qoder.json"),
|
|
11140
|
+
path19.join(home, ".qoder.json")
|
|
9935
11141
|
];
|
|
9936
11142
|
},
|
|
9937
11143
|
parseSessionFile: parseQoderCnSessionFile
|
|
@@ -9940,11 +11146,11 @@ function createQoderCnAdapter() {
|
|
|
9940
11146
|
|
|
9941
11147
|
// src/adapters/qoder.ts
|
|
9942
11148
|
import { existsSync } from "node:fs";
|
|
9943
|
-
import { readdir as
|
|
9944
|
-
import
|
|
9945
|
-
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";
|
|
9946
11152
|
function parseQoderPaths(filePath) {
|
|
9947
|
-
const parts = filePath.split(
|
|
11153
|
+
const parts = filePath.split(path20.sep);
|
|
9948
11154
|
const subagentsIdx = parts.lastIndexOf("subagents");
|
|
9949
11155
|
let sessionId = "";
|
|
9950
11156
|
let projectName = "";
|
|
@@ -9954,17 +11160,17 @@ function parseQoderPaths(filePath) {
|
|
|
9954
11160
|
sessionId = parts[subagentsIdx - 1];
|
|
9955
11161
|
projectName = parts[subagentsIdx - 2];
|
|
9956
11162
|
const projectsIdx = parts.lastIndexOf("projects");
|
|
9957
|
-
configDir2 = parts.slice(0, projectsIdx).join(
|
|
9958
|
-
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);
|
|
9959
11165
|
} else {
|
|
9960
11166
|
const filename = parts.at(-1) || "";
|
|
9961
|
-
sessionId =
|
|
11167
|
+
sessionId = path20.basename(filename, ".jsonl");
|
|
9962
11168
|
projectName = parts.at(-2) || "";
|
|
9963
11169
|
if (projectName === "transcript") {
|
|
9964
11170
|
projectName = parts.at(-3) || "";
|
|
9965
11171
|
}
|
|
9966
11172
|
const projectsIdx = parts.lastIndexOf("projects");
|
|
9967
|
-
configDir2 = parts.slice(0, projectsIdx).join(
|
|
11173
|
+
configDir2 = parts.slice(0, projectsIdx).join(path20.sep);
|
|
9968
11174
|
}
|
|
9969
11175
|
return { configDir: configDir2, projectName, sessionId, mainTranscriptPath };
|
|
9970
11176
|
}
|
|
@@ -9987,7 +11193,7 @@ function rebuildEventIdentity4(event) {
|
|
|
9987
11193
|
}
|
|
9988
11194
|
async function parseModelNamesFromDynamicTexts2(dynamicTextsPath) {
|
|
9989
11195
|
try {
|
|
9990
|
-
const content = await
|
|
11196
|
+
const content = await readFile13(dynamicTextsPath, "utf8");
|
|
9991
11197
|
const json = JSON.parse(content);
|
|
9992
11198
|
const texts = json.texts || {};
|
|
9993
11199
|
const map = {};
|
|
@@ -10003,10 +11209,10 @@ async function parseModelNamesFromDynamicTexts2(dynamicTextsPath) {
|
|
|
10003
11209
|
}
|
|
10004
11210
|
}
|
|
10005
11211
|
async function loadQoderModelNames(configDir2, home) {
|
|
10006
|
-
const map = await parseModelNamesFromDynamicTexts2(
|
|
11212
|
+
const map = await parseModelNamesFromDynamicTexts2(path20.join(configDir2, ".auth", "dynamic-texts.json"));
|
|
10007
11213
|
const siblingConfigDir = configDir2.replace(/\.qoder$/, ".qoder-cn");
|
|
10008
11214
|
if (siblingConfigDir !== configDir2) {
|
|
10009
|
-
const siblingMap = await parseModelNamesFromDynamicTexts2(
|
|
11215
|
+
const siblingMap = await parseModelNamesFromDynamicTexts2(path20.join(siblingConfigDir, ".auth", "dynamic-texts.json"));
|
|
10010
11216
|
for (const [key, val] of Object.entries(siblingMap)) {
|
|
10011
11217
|
if (!(key in map)) {
|
|
10012
11218
|
map[key] = val;
|
|
@@ -10014,11 +11220,11 @@ async function loadQoderModelNames(configDir2, home) {
|
|
|
10014
11220
|
}
|
|
10015
11221
|
}
|
|
10016
11222
|
if (isQwenworkConfigRoot(configDir2)) {
|
|
10017
|
-
for (const dir of [
|
|
10018
|
-
if (
|
|
11223
|
+
for (const dir of [path20.join(home, ".qoder"), path20.join(home, ".qoder-cn")]) {
|
|
11224
|
+
if (path20.resolve(dir) === path20.resolve(configDir2)) {
|
|
10019
11225
|
continue;
|
|
10020
11226
|
}
|
|
10021
|
-
const fallbackMap = await parseModelNamesFromDynamicTexts2(
|
|
11227
|
+
const fallbackMap = await parseModelNamesFromDynamicTexts2(path20.join(dir, ".auth", "dynamic-texts.json"));
|
|
10022
11228
|
for (const [key, val] of Object.entries(fallbackMap)) {
|
|
10023
11229
|
if (!(key in map)) {
|
|
10024
11230
|
map[key] = val;
|
|
@@ -10037,25 +11243,25 @@ async function loadQoderModelNames(configDir2, home) {
|
|
|
10037
11243
|
return map;
|
|
10038
11244
|
}
|
|
10039
11245
|
function isQwenworkConfigRoot(configDir2) {
|
|
10040
|
-
const name =
|
|
11246
|
+
const name = path20.basename(path20.resolve(configDir2));
|
|
10041
11247
|
if (name === ".qwenworkcn" || name === ".qwenwork") {
|
|
10042
11248
|
return true;
|
|
10043
11249
|
}
|
|
10044
11250
|
const override = process.env.QWENWORK_CONFIG_DIR;
|
|
10045
|
-
return Boolean(override && override.trim() &&
|
|
11251
|
+
return Boolean(override && override.trim() && path20.basename(path20.resolve(override)) === name);
|
|
10046
11252
|
}
|
|
10047
11253
|
var FAILED_SEGMENT_STOP_REASONS = /* @__PURE__ */ new Set(["cancelled", "canceled", "error", "failed", "refusal"]);
|
|
10048
11254
|
async function loadQoderSegmentModelCalls(filePath, isSubagentSession, modelMap) {
|
|
10049
11255
|
const { configDir: configDir2, projectName, sessionId } = parseQoderPaths(filePath);
|
|
10050
|
-
const segmentsPath =
|
|
11256
|
+
const segmentsPath = path20.join(configDir2, "logs", "sessions", projectName, sessionId, "segments");
|
|
10051
11257
|
const modelCalls = [];
|
|
10052
11258
|
try {
|
|
10053
|
-
const files = await
|
|
11259
|
+
const files = await readdir10(segmentsPath);
|
|
10054
11260
|
for (const file of files) {
|
|
10055
11261
|
if (!file.endsWith(".jsonl")) {
|
|
10056
11262
|
continue;
|
|
10057
11263
|
}
|
|
10058
|
-
const content = await
|
|
11264
|
+
const content = await readFile13(path20.join(segmentsPath, file), "utf8");
|
|
10059
11265
|
let currentTurnIsSubagent = false;
|
|
10060
11266
|
for (const line of content.split("\n").filter(Boolean)) {
|
|
10061
11267
|
const raw = parseJsonLine(line);
|
|
@@ -10089,7 +11295,7 @@ async function loadQoderSegmentModelCalls(filePath, isSubagentSession, modelMap)
|
|
|
10089
11295
|
return modelCalls.filter((call) => call.isSubagent === isSubagentSession);
|
|
10090
11296
|
}
|
|
10091
11297
|
async function parseQoderSessionFile(filePath, options) {
|
|
10092
|
-
const text = await
|
|
11298
|
+
const text = await readFile13(filePath, "utf8");
|
|
10093
11299
|
const lines = text.split("\n").filter(Boolean);
|
|
10094
11300
|
const parsedPaths = parseQoderPaths(filePath);
|
|
10095
11301
|
const { configDir: configDir2 } = parsedPaths;
|
|
@@ -10100,7 +11306,7 @@ async function parseQoderSessionFile(filePath, options) {
|
|
|
10100
11306
|
let cwd;
|
|
10101
11307
|
let project = projectContext.project;
|
|
10102
11308
|
let model;
|
|
10103
|
-
const home =
|
|
11309
|
+
const home = path20.resolve(stringOption(options.home) || os11.homedir());
|
|
10104
11310
|
const modelMap = await loadQoderModelNames(configDir2, home);
|
|
10105
11311
|
const qwenworkRoot = isQwenworkConfigRoot(configDir2);
|
|
10106
11312
|
const isSubagentSession = filePath.includes("subagents");
|
|
@@ -10136,7 +11342,7 @@ async function parseQoderSessionFile(filePath, options) {
|
|
|
10136
11342
|
sessionId = stringField(raw, "sessionId") || sessionId;
|
|
10137
11343
|
state.sessionId = sessionId;
|
|
10138
11344
|
cwd = stringField(raw, "cwd") || cwd;
|
|
10139
|
-
project = projectContext.project || (cwd ?
|
|
11345
|
+
project = projectContext.project || (cwd ? path20.basename(cwd) : project || await qoderProjectFromFilePath(filePath, options));
|
|
10140
11346
|
if (!ts) {
|
|
10141
11347
|
continue;
|
|
10142
11348
|
}
|
|
@@ -10447,7 +11653,7 @@ async function parseQoderSessionFile(filePath, options) {
|
|
|
10447
11653
|
}
|
|
10448
11654
|
dbModelCalls ??= await loadQoderDbModelCalls("Qoder", parsedPaths.sessionId, modelMap);
|
|
10449
11655
|
if (dbModelCalls.rootSessionId) {
|
|
10450
|
-
const parentPath =
|
|
11656
|
+
const parentPath = path20.join(path20.dirname(filePath), `${dbModelCalls.rootSessionId}.jsonl`);
|
|
10451
11657
|
const parentSourcePathHash = `sha256:${createStableHash(parentPath)}`;
|
|
10452
11658
|
return withoutSubagentTurnEvents(validEvents).map((event) => rebuildEventIdentity4({
|
|
10453
11659
|
...event,
|
|
@@ -10564,41 +11770,41 @@ function qoderExtractText(value) {
|
|
|
10564
11770
|
}
|
|
10565
11771
|
async function qoderProjectContextFromLines(filePath, lines, options, configDir2) {
|
|
10566
11772
|
const { projectName: projectDir, sessionId } = parseQoderPaths(filePath);
|
|
10567
|
-
const isSubagent = filePath.includes(`${
|
|
11773
|
+
const isSubagent = filePath.includes(`${path20.sep}subagents${path20.sep}`);
|
|
10568
11774
|
const inherited = isSubagent ? await readPersistedSessionContextFromOptions(options, sessionId) : void 0;
|
|
10569
11775
|
let cwds = [];
|
|
10570
11776
|
const workspaceDirs = [];
|
|
10571
11777
|
for (const line of lines) {
|
|
10572
11778
|
const raw = parseJsonLine(line);
|
|
10573
11779
|
const cwd = raw ? stringField(raw, "cwd") : void 0;
|
|
10574
|
-
if (cwd &&
|
|
11780
|
+
if (cwd && path20.isAbsolute(cwd)) {
|
|
10575
11781
|
cwds.push(cwd);
|
|
10576
11782
|
}
|
|
10577
11783
|
if (raw && stringField(raw, "type") === "workspace-directories") {
|
|
10578
11784
|
for (const dir of arrayField5(raw, "directories")) {
|
|
10579
|
-
if (typeof dir === "string" &&
|
|
11785
|
+
if (typeof dir === "string" && path20.isAbsolute(dir)) {
|
|
10580
11786
|
workspaceDirs.push(dir);
|
|
10581
11787
|
}
|
|
10582
11788
|
}
|
|
10583
11789
|
}
|
|
10584
11790
|
}
|
|
10585
11791
|
if (isSubagent) {
|
|
10586
|
-
if (inherited?.cwd &&
|
|
11792
|
+
if (inherited?.cwd && path20.isAbsolute(inherited.cwd)) {
|
|
10587
11793
|
cwds = [inherited.cwd];
|
|
10588
11794
|
} else {
|
|
10589
|
-
const parentSessionPath =
|
|
11795
|
+
const parentSessionPath = path20.join(configDir2, "projects", projectDir, `${sessionId}.jsonl`);
|
|
10590
11796
|
try {
|
|
10591
|
-
const parentText = await
|
|
11797
|
+
const parentText = await readFile13(parentSessionPath, "utf8");
|
|
10592
11798
|
const parentCwds = [];
|
|
10593
11799
|
for (const line of parentText.split("\n").filter(Boolean)) {
|
|
10594
11800
|
const raw = parseJsonLine(line);
|
|
10595
11801
|
const cwd = raw ? stringField(raw, "cwd") : void 0;
|
|
10596
|
-
if (cwd &&
|
|
11802
|
+
if (cwd && path20.isAbsolute(cwd)) {
|
|
10597
11803
|
parentCwds.push(cwd);
|
|
10598
11804
|
}
|
|
10599
11805
|
if (workspaceDirs.length === 0 && raw && stringField(raw, "type") === "workspace-directories") {
|
|
10600
11806
|
for (const dir of arrayField5(raw, "directories")) {
|
|
10601
|
-
if (typeof dir === "string" &&
|
|
11807
|
+
if (typeof dir === "string" && path20.isAbsolute(dir)) {
|
|
10602
11808
|
workspaceDirs.push(dir);
|
|
10603
11809
|
}
|
|
10604
11810
|
}
|
|
@@ -10618,29 +11824,29 @@ async function qoderProjectContextFromLines(filePath, lines, options, configDir2
|
|
|
10618
11824
|
}
|
|
10619
11825
|
}
|
|
10620
11826
|
const root = await gitRootFromCwds3(cwds) || qoderProjectRootFromCwds(projectDir, cwds);
|
|
10621
|
-
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));
|
|
10622
11828
|
return {
|
|
10623
11829
|
project,
|
|
10624
11830
|
workspaceId: createWorkspaceId({ projectName: project, repoRoot: root })
|
|
10625
11831
|
};
|
|
10626
11832
|
}
|
|
10627
11833
|
function pathInsideDir(candidate, dir) {
|
|
10628
|
-
const resolvedDir =
|
|
10629
|
-
const resolved =
|
|
10630
|
-
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}`);
|
|
10631
11837
|
}
|
|
10632
11838
|
async function gitRootFromCwds3(cwds) {
|
|
10633
11839
|
const seen = /* @__PURE__ */ new Set();
|
|
10634
11840
|
for (const cwd of cwds) {
|
|
10635
|
-
let current =
|
|
11841
|
+
let current = path20.resolve(cwd);
|
|
10636
11842
|
while (!seen.has(current)) {
|
|
10637
11843
|
seen.add(current);
|
|
10638
11844
|
try {
|
|
10639
|
-
await
|
|
11845
|
+
await stat11(path20.join(current, ".git"));
|
|
10640
11846
|
return current;
|
|
10641
11847
|
} catch {
|
|
10642
11848
|
}
|
|
10643
|
-
const parent =
|
|
11849
|
+
const parent = path20.dirname(current);
|
|
10644
11850
|
if (parent === current) {
|
|
10645
11851
|
break;
|
|
10646
11852
|
}
|
|
@@ -10651,12 +11857,12 @@ async function gitRootFromCwds3(cwds) {
|
|
|
10651
11857
|
}
|
|
10652
11858
|
function qoderProjectRootFromCwds(projectDir, cwds) {
|
|
10653
11859
|
for (const cwd of cwds) {
|
|
10654
|
-
let current =
|
|
11860
|
+
let current = path20.resolve(cwd);
|
|
10655
11861
|
while (true) {
|
|
10656
11862
|
if (qoderEncodedVariants(current).includes(projectDir)) {
|
|
10657
11863
|
return current;
|
|
10658
11864
|
}
|
|
10659
|
-
const parent =
|
|
11865
|
+
const parent = path20.dirname(current);
|
|
10660
11866
|
if (parent === current) {
|
|
10661
11867
|
break;
|
|
10662
11868
|
}
|
|
@@ -10666,7 +11872,7 @@ function qoderProjectRootFromCwds(projectDir, cwds) {
|
|
|
10666
11872
|
return void 0;
|
|
10667
11873
|
}
|
|
10668
11874
|
function rawQoderProjectPath(value) {
|
|
10669
|
-
return
|
|
11875
|
+
return path20.resolve(value).split(path20.sep).join("-");
|
|
10670
11876
|
}
|
|
10671
11877
|
function qoderEncodedVariants(value) {
|
|
10672
11878
|
const raw = rawQoderProjectPath(value);
|
|
@@ -10687,11 +11893,11 @@ function qoderEncodedProjectSuffix(projectDir, home) {
|
|
|
10687
11893
|
return void 0;
|
|
10688
11894
|
}
|
|
10689
11895
|
async function qoderProjectFromFilePath(filePath, options) {
|
|
10690
|
-
const projectDir =
|
|
10691
|
-
const home = options ?
|
|
11896
|
+
const projectDir = path20.basename(path20.dirname(filePath));
|
|
11897
|
+
const home = options ? path20.resolve(stringOption(options.home) || os11.homedir()) : os11.homedir();
|
|
10692
11898
|
const resolved = await resolveQoderProjectPath(projectDir, home);
|
|
10693
11899
|
if (resolved) {
|
|
10694
|
-
return
|
|
11900
|
+
return path20.basename(resolved);
|
|
10695
11901
|
}
|
|
10696
11902
|
const suffix = qoderEncodedProjectSuffix(projectDir, home);
|
|
10697
11903
|
if (suffix) {
|
|
@@ -10712,12 +11918,12 @@ async function resolveQoderProjectPath(projectDir, home) {
|
|
|
10712
11918
|
while (currentEncodedVariants.some((prefix) => projectDir.startsWith(`${prefix}-`))) {
|
|
10713
11919
|
let matchedChild;
|
|
10714
11920
|
try {
|
|
10715
|
-
const entries = await
|
|
11921
|
+
const entries = await readdir10(current, { withFileTypes: true });
|
|
10716
11922
|
for (const entry of entries) {
|
|
10717
11923
|
if (!entry.isDirectory()) {
|
|
10718
11924
|
continue;
|
|
10719
11925
|
}
|
|
10720
|
-
const candidate =
|
|
11926
|
+
const candidate = path20.join(current, entry.name);
|
|
10721
11927
|
const candidateVariants = qoderEncodedVariants(candidate);
|
|
10722
11928
|
if (candidateVariants.includes(projectDir)) {
|
|
10723
11929
|
return candidate;
|
|
@@ -10762,19 +11968,19 @@ function hookConfig7() {
|
|
|
10762
11968
|
function qoderConfigDir(home, env) {
|
|
10763
11969
|
const override = env?.QODER_CONFIG_DIR;
|
|
10764
11970
|
if (override && override.trim()) {
|
|
10765
|
-
return
|
|
11971
|
+
return path20.resolve(override);
|
|
10766
11972
|
}
|
|
10767
|
-
return
|
|
11973
|
+
return path20.join(home, ".qoder");
|
|
10768
11974
|
}
|
|
10769
11975
|
function qwenworkConfigDir(home, env) {
|
|
10770
11976
|
const override = env?.QWENWORK_CONFIG_DIR;
|
|
10771
11977
|
if (override && override.trim()) {
|
|
10772
|
-
return
|
|
11978
|
+
return path20.resolve(override);
|
|
10773
11979
|
}
|
|
10774
|
-
return
|
|
11980
|
+
return path20.join(home, ".qwenworkcn");
|
|
10775
11981
|
}
|
|
10776
11982
|
function qoderConfigDirs(home, env) {
|
|
10777
|
-
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)))];
|
|
10778
11984
|
}
|
|
10779
11985
|
function createQoderAdapter() {
|
|
10780
11986
|
return {
|
|
@@ -10786,11 +11992,11 @@ function createQoderAdapter() {
|
|
|
10786
11992
|
return qoderConfigDir(home, env);
|
|
10787
11993
|
},
|
|
10788
11994
|
installedPath(home, env) {
|
|
10789
|
-
return
|
|
11995
|
+
return path20.join(qoderConfigDir(home, env), "settings.json");
|
|
10790
11996
|
},
|
|
10791
11997
|
async isInstalled(home, env) {
|
|
10792
11998
|
return isHooksJsonInstalled(
|
|
10793
|
-
|
|
11999
|
+
path20.join(qoderConfigDir(home, env), "settings.json"),
|
|
10794
12000
|
"vibetime hook --agent qoder"
|
|
10795
12001
|
);
|
|
10796
12002
|
},
|
|
@@ -10799,16 +12005,16 @@ function createQoderAdapter() {
|
|
|
10799
12005
|
const targets = [primary, ...variants.filter((dir) => existsSync(dir))];
|
|
10800
12006
|
return targets.map((base) => ({
|
|
10801
12007
|
kind: "hooks-json",
|
|
10802
|
-
path:
|
|
12008
|
+
path: path20.join(base, "settings.json"),
|
|
10803
12009
|
content: hookConfig7()
|
|
10804
12010
|
}));
|
|
10805
12011
|
},
|
|
10806
12012
|
sourcePaths(home, env) {
|
|
10807
|
-
const paths = qoderConfigDirs(home, env).map((base2) =>
|
|
12013
|
+
const paths = qoderConfigDirs(home, env).map((base2) => path20.join(base2, "projects"));
|
|
10808
12014
|
const base = qoderConfigDir(home, env);
|
|
10809
12015
|
paths.push(
|
|
10810
|
-
|
|
10811
|
-
|
|
12016
|
+
path20.join(base, ".qoder.json"),
|
|
12017
|
+
path20.join(home, ".qoder.json")
|
|
10812
12018
|
);
|
|
10813
12019
|
return paths;
|
|
10814
12020
|
},
|
|
@@ -10844,27 +12050,27 @@ function normalizeId(id) {
|
|
|
10844
12050
|
}
|
|
10845
12051
|
|
|
10846
12052
|
// src/adapters/workbuddy.ts
|
|
10847
|
-
import { readdir as
|
|
10848
|
-
import
|
|
12053
|
+
import { readdir as readdir11, readFile as readFile14, stat as stat12 } from "node:fs/promises";
|
|
12054
|
+
import path21 from "node:path";
|
|
10849
12055
|
function workbuddyProjectsDir(home, env) {
|
|
10850
12056
|
const override = env?.WORKBUDDY_PROJECTS_DIR || env?.WORKBUDDY_HOME;
|
|
10851
12057
|
if (override && override.trim()) {
|
|
10852
|
-
return
|
|
12058
|
+
return path21.resolve(override, override.endsWith("projects") ? "" : "projects");
|
|
10853
12059
|
}
|
|
10854
|
-
return
|
|
12060
|
+
return path21.join(home, ".workbuddy", "projects");
|
|
10855
12061
|
}
|
|
10856
12062
|
function workbuddyBaseDir(home, env) {
|
|
10857
12063
|
const override = env?.WORKBUDDY_HOME;
|
|
10858
12064
|
if (override && override.trim()) {
|
|
10859
|
-
return
|
|
12065
|
+
return path21.resolve(override);
|
|
10860
12066
|
}
|
|
10861
|
-
return
|
|
12067
|
+
return path21.join(home, ".workbuddy");
|
|
10862
12068
|
}
|
|
10863
12069
|
function projectFromCwd(cwd, fallback) {
|
|
10864
12070
|
if (!cwd) {
|
|
10865
12071
|
return fallback;
|
|
10866
12072
|
}
|
|
10867
|
-
return
|
|
12073
|
+
return path21.basename(cwd) || fallback;
|
|
10868
12074
|
}
|
|
10869
12075
|
function sourceHash(filePath) {
|
|
10870
12076
|
return `sha256:${createStableHash(filePath)}`;
|
|
@@ -10982,7 +12188,7 @@ function toolCallFailed(record) {
|
|
|
10982
12188
|
return status === "failed" || status === "incomplete" || record.is_error === true || providerData.error != null || providerData.isError === true;
|
|
10983
12189
|
}
|
|
10984
12190
|
async function readWorkbuddyLines(filePath) {
|
|
10985
|
-
const text = await
|
|
12191
|
+
const text = await readFile14(filePath, "utf8");
|
|
10986
12192
|
return text.split(/\r?\n/).map((line, index) => {
|
|
10987
12193
|
if (!line.trim()) {
|
|
10988
12194
|
return void 0;
|
|
@@ -11002,8 +12208,8 @@ async function parseWorkbuddySessionFile(filePath, options) {
|
|
|
11002
12208
|
}
|
|
11003
12209
|
const events = [];
|
|
11004
12210
|
const first = lines[0].record;
|
|
11005
|
-
const sessionId = stringField(first, "sessionId") ||
|
|
11006
|
-
const fallbackProject =
|
|
12211
|
+
const sessionId = stringField(first, "sessionId") || path21.basename(filePath, ".jsonl");
|
|
12212
|
+
const fallbackProject = path21.basename(path21.dirname(filePath));
|
|
11007
12213
|
const cwd = lines.map((line) => stringField(line.record, "cwd")).find(Boolean);
|
|
11008
12214
|
const project = projectFromCwd(cwd, fallbackProject);
|
|
11009
12215
|
const workspaceId = createWorkspaceId({ projectName: project, repoRoot: cwd });
|
|
@@ -11287,17 +12493,17 @@ async function workbuddyBackfillFiles(sourceRoot, home, env) {
|
|
|
11287
12493
|
const base = sourceRoot || workbuddyProjectsDir(home, env);
|
|
11288
12494
|
const files = [];
|
|
11289
12495
|
try {
|
|
11290
|
-
const projects = await
|
|
12496
|
+
const projects = await readdir11(base, { withFileTypes: true });
|
|
11291
12497
|
for (const project of projects) {
|
|
11292
12498
|
if (!project.isDirectory()) {
|
|
11293
12499
|
continue;
|
|
11294
12500
|
}
|
|
11295
|
-
const projectDir =
|
|
11296
|
-
const entries = await
|
|
12501
|
+
const projectDir = path21.join(base, project.name);
|
|
12502
|
+
const entries = await readdir11(projectDir, { withFileTypes: true });
|
|
11297
12503
|
for (const entry of entries) {
|
|
11298
12504
|
if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
11299
|
-
const filePath =
|
|
11300
|
-
const info = await
|
|
12505
|
+
const filePath = path21.join(projectDir, entry.name);
|
|
12506
|
+
const info = await stat12(filePath);
|
|
11301
12507
|
files.push({ path: filePath, modifiedAt: info.mtime.toISOString() });
|
|
11302
12508
|
}
|
|
11303
12509
|
}
|
|
@@ -11334,18 +12540,18 @@ function createWorkbuddyAdapter() {
|
|
|
11334
12540
|
return workbuddyProjectsDir(home, env);
|
|
11335
12541
|
},
|
|
11336
12542
|
installedPath(home, env) {
|
|
11337
|
-
return
|
|
12543
|
+
return path21.join(workbuddyBaseDir(home, env), "settings.json");
|
|
11338
12544
|
},
|
|
11339
12545
|
async isInstalled(home, env) {
|
|
11340
12546
|
return isHooksJsonInstalled(
|
|
11341
|
-
|
|
12547
|
+
path21.join(workbuddyBaseDir(home, env), "settings.json"),
|
|
11342
12548
|
"vibetime hook --agent workbuddy"
|
|
11343
12549
|
);
|
|
11344
12550
|
},
|
|
11345
12551
|
installEntries(home, env) {
|
|
11346
12552
|
return [{
|
|
11347
12553
|
kind: "hooks-json",
|
|
11348
|
-
path:
|
|
12554
|
+
path: path21.join(workbuddyBaseDir(home, env), "settings.json"),
|
|
11349
12555
|
content: hookConfig8()
|
|
11350
12556
|
}];
|
|
11351
12557
|
},
|
|
@@ -11358,26 +12564,26 @@ function createWorkbuddyAdapter() {
|
|
|
11358
12564
|
|
|
11359
12565
|
// src/adapters/zcode.ts
|
|
11360
12566
|
import { execFile } from "node:child_process";
|
|
11361
|
-
import { readFile as
|
|
11362
|
-
import
|
|
12567
|
+
import { readFile as readFile15, stat as stat13 } from "node:fs/promises";
|
|
12568
|
+
import path22 from "node:path";
|
|
11363
12569
|
import { promisify as promisify2 } from "node:util";
|
|
11364
12570
|
init_fs();
|
|
11365
12571
|
var execFileAsync = promisify2(execFile);
|
|
11366
12572
|
function zcodeCliDir(home, env) {
|
|
11367
12573
|
const override = env?.ZCODE_CLI_DIR || env?.ZCODE_HOME;
|
|
11368
12574
|
if (override && override.trim()) {
|
|
11369
|
-
return
|
|
12575
|
+
return path22.resolve(override, override.endsWith("cli") ? "" : "cli");
|
|
11370
12576
|
}
|
|
11371
|
-
return
|
|
12577
|
+
return path22.join(home, ".zcode", "cli");
|
|
11372
12578
|
}
|
|
11373
12579
|
function zcodeDbPath(home, env) {
|
|
11374
|
-
return
|
|
12580
|
+
return path22.join(zcodeCliDir(home, env), "db", "db.sqlite");
|
|
11375
12581
|
}
|
|
11376
12582
|
var providerNameCache = null;
|
|
11377
12583
|
async function loadProviderNames(configPath2) {
|
|
11378
12584
|
let fileMtime = 0;
|
|
11379
12585
|
try {
|
|
11380
|
-
const info = await
|
|
12586
|
+
const info = await stat13(configPath2);
|
|
11381
12587
|
fileMtime = info.mtimeMs;
|
|
11382
12588
|
} catch {
|
|
11383
12589
|
return /* @__PURE__ */ new Map();
|
|
@@ -11387,7 +12593,7 @@ async function loadProviderNames(configPath2) {
|
|
|
11387
12593
|
}
|
|
11388
12594
|
const map = /* @__PURE__ */ new Map();
|
|
11389
12595
|
try {
|
|
11390
|
-
const raw = await
|
|
12596
|
+
const raw = await readFile15(configPath2, "utf-8");
|
|
11391
12597
|
const config = JSON.parse(raw);
|
|
11392
12598
|
const providers = config?.provider;
|
|
11393
12599
|
if (isPlainObject(providers)) {
|
|
@@ -11405,7 +12611,7 @@ function sourceHash2(filePath) {
|
|
|
11405
12611
|
return `sha256:${createStableHash(filePath)}`;
|
|
11406
12612
|
}
|
|
11407
12613
|
function projectFromDirectory(directory) {
|
|
11408
|
-
return directory ?
|
|
12614
|
+
return directory ? path22.basename(directory) || "zcode" : "zcode";
|
|
11409
12615
|
}
|
|
11410
12616
|
function isoFromMs(value) {
|
|
11411
12617
|
return timestampFrom(typeof value === "number" ? value : Number(value));
|
|
@@ -11589,16 +12795,16 @@ async function parseZCodeDb(filePath, options) {
|
|
|
11589
12795
|
if (rows.length === 0) {
|
|
11590
12796
|
return [];
|
|
11591
12797
|
}
|
|
11592
|
-
let candidate =
|
|
12798
|
+
let candidate = path22.resolve(filePath);
|
|
11593
12799
|
let configPath2 = "";
|
|
11594
12800
|
for (let i = 0; i < 12; i++) {
|
|
11595
|
-
const probe =
|
|
12801
|
+
const probe = path22.join(candidate, ".zcode", "v2", "config.json");
|
|
11596
12802
|
try {
|
|
11597
|
-
await
|
|
12803
|
+
await stat13(probe);
|
|
11598
12804
|
configPath2 = probe;
|
|
11599
12805
|
break;
|
|
11600
12806
|
} catch {
|
|
11601
|
-
const parent =
|
|
12807
|
+
const parent = path22.dirname(candidate);
|
|
11602
12808
|
if (parent === candidate) break;
|
|
11603
12809
|
candidate = parent;
|
|
11604
12810
|
}
|
|
@@ -11818,9 +13024,9 @@ async function parseZCodeDb(filePath, options) {
|
|
|
11818
13024
|
}
|
|
11819
13025
|
async function zcodeBackfillFiles(sourceRoot, home, env) {
|
|
11820
13026
|
const candidate = sourceRoot || zcodeDbPath(home, env);
|
|
11821
|
-
const filePath = candidate.endsWith(".sqlite") ? candidate :
|
|
13027
|
+
const filePath = candidate.endsWith(".sqlite") ? candidate : path22.join(candidate, "db", "db.sqlite");
|
|
11822
13028
|
try {
|
|
11823
|
-
const info = await
|
|
13029
|
+
const info = await stat13(filePath);
|
|
11824
13030
|
return [{ path: filePath, modifiedAt: info.mtime.toISOString() }];
|
|
11825
13031
|
} catch {
|
|
11826
13032
|
return [];
|
|
@@ -11852,50 +13058,50 @@ function createZCodeAdapter() {
|
|
|
11852
13058
|
}
|
|
11853
13059
|
|
|
11854
13060
|
// src/adapters/zed.ts
|
|
11855
|
-
import
|
|
11856
|
-
import
|
|
13061
|
+
import os12 from "node:os";
|
|
13062
|
+
import path23 from "node:path";
|
|
11857
13063
|
function zedThreadsCandidates(home, env) {
|
|
11858
13064
|
const candidates = [];
|
|
11859
13065
|
const platform2 = process.platform;
|
|
11860
13066
|
if (platform2 === "darwin") {
|
|
11861
|
-
candidates.push(
|
|
13067
|
+
candidates.push(path23.join(home, "Library", "Application Support", "Zed", "threads", "threads.db"));
|
|
11862
13068
|
} else if (platform2 === "win32") {
|
|
11863
13069
|
const appdata = env?.APPDATA;
|
|
11864
13070
|
if (appdata && appdata.trim()) {
|
|
11865
|
-
candidates.push(
|
|
13071
|
+
candidates.push(path23.join(path23.resolve(appdata), "Zed", "threads", "threads.db"));
|
|
11866
13072
|
}
|
|
11867
|
-
candidates.push(
|
|
13073
|
+
candidates.push(path23.join(home, "AppData", "Roaming", "Zed", "threads", "threads.db"));
|
|
11868
13074
|
} else {
|
|
11869
13075
|
const xdgData = env?.XDG_DATA_HOME;
|
|
11870
13076
|
if (xdgData && xdgData.trim()) {
|
|
11871
|
-
candidates.push(
|
|
13077
|
+
candidates.push(path23.join(path23.resolve(xdgData), "zed", "threads", "threads.db"));
|
|
11872
13078
|
}
|
|
11873
|
-
candidates.push(
|
|
13079
|
+
candidates.push(path23.join(home, ".local", "share", "zed", "threads", "threads.db"));
|
|
11874
13080
|
const xdgConfig = env?.XDG_CONFIG_HOME;
|
|
11875
13081
|
if (xdgConfig && xdgConfig.trim()) {
|
|
11876
|
-
candidates.push(
|
|
13082
|
+
candidates.push(path23.join(path23.resolve(xdgConfig), "zed", "threads", "threads.db"));
|
|
11877
13083
|
}
|
|
11878
|
-
candidates.push(
|
|
13084
|
+
candidates.push(path23.join(home, ".config", "zed", "threads", "threads.db"));
|
|
11879
13085
|
}
|
|
11880
13086
|
return candidates;
|
|
11881
13087
|
}
|
|
11882
13088
|
function zedConfigDir(home, env) {
|
|
11883
13089
|
const platform2 = process.platform;
|
|
11884
13090
|
if (platform2 === "darwin") {
|
|
11885
|
-
return
|
|
13091
|
+
return path23.join(home, "Library", "Application Support", "Zed");
|
|
11886
13092
|
}
|
|
11887
13093
|
if (platform2 === "win32") {
|
|
11888
13094
|
const appdata = env?.APPDATA;
|
|
11889
13095
|
if (appdata && appdata.trim()) {
|
|
11890
|
-
return
|
|
13096
|
+
return path23.join(path23.resolve(appdata), "Zed");
|
|
11891
13097
|
}
|
|
11892
|
-
return
|
|
13098
|
+
return path23.join(home, "AppData", "Roaming", "Zed");
|
|
11893
13099
|
}
|
|
11894
13100
|
const xdgConfig = env?.XDG_CONFIG_HOME;
|
|
11895
13101
|
if (xdgConfig && xdgConfig.trim()) {
|
|
11896
|
-
return
|
|
13102
|
+
return path23.join(path23.resolve(xdgConfig), "zed");
|
|
11897
13103
|
}
|
|
11898
|
-
return
|
|
13104
|
+
return path23.join(home, ".config", "zed");
|
|
11899
13105
|
}
|
|
11900
13106
|
function baseZedEvent(event) {
|
|
11901
13107
|
return {
|
|
@@ -11959,7 +13165,7 @@ async function parseZedSessionFile(dbPath, options) {
|
|
|
11959
13165
|
const folderRaw = row.folder_paths || "";
|
|
11960
13166
|
const folder = folderRaw.split(/[\n,]/).map((s) => s.trim()).find(Boolean);
|
|
11961
13167
|
const cwd = folder || void 0;
|
|
11962
|
-
const project = cwd ?
|
|
13168
|
+
const project = cwd ? path23.basename(cwd) : row.summary ? row.summary.slice(0, 40) : void 0;
|
|
11963
13169
|
let json;
|
|
11964
13170
|
try {
|
|
11965
13171
|
const bytes = row.data_type === "zstd" ? decompress2(new Uint8Array(row.data)) : new Uint8Array(row.data);
|
|
@@ -12204,20 +13410,20 @@ async function parseZedSessionFile(dbPath, options) {
|
|
|
12204
13410
|
}
|
|
12205
13411
|
return events.filter((event) => matchesBackfillFilters(event, options));
|
|
12206
13412
|
}
|
|
12207
|
-
async function zedBackfillFiles(sourceRoot, home =
|
|
12208
|
-
const { stat:
|
|
13413
|
+
async function zedBackfillFiles(sourceRoot, home = os12.homedir(), env) {
|
|
13414
|
+
const { stat: stat16 } = await import("node:fs/promises");
|
|
12209
13415
|
if (sourceRoot) {
|
|
12210
13416
|
if (!sourceRoot.endsWith(".db")) {
|
|
12211
13417
|
return [];
|
|
12212
13418
|
}
|
|
12213
|
-
const info = await
|
|
13419
|
+
const info = await stat16(sourceRoot).catch(() => null);
|
|
12214
13420
|
if (!info) {
|
|
12215
13421
|
return [];
|
|
12216
13422
|
}
|
|
12217
13423
|
return [{ path: sourceRoot, modifiedAt: info.mtime.toISOString() }];
|
|
12218
13424
|
}
|
|
12219
13425
|
for (const candidatePath of zedThreadsCandidates(home, env)) {
|
|
12220
|
-
const info = await
|
|
13426
|
+
const info = await stat16(candidatePath).catch(() => null);
|
|
12221
13427
|
if (info) {
|
|
12222
13428
|
return [{ path: candidatePath, modifiedAt: info.mtime.toISOString() }];
|
|
12223
13429
|
}
|
|
@@ -12234,7 +13440,7 @@ function createZedAdapter() {
|
|
|
12234
13440
|
return zedConfigDir(home, env);
|
|
12235
13441
|
},
|
|
12236
13442
|
installedPath(home, env) {
|
|
12237
|
-
return
|
|
13443
|
+
return path23.join(zedConfigDir(home, env), "vibetime-marker");
|
|
12238
13444
|
},
|
|
12239
13445
|
async isInstalled() {
|
|
12240
13446
|
return false;
|
|
@@ -12609,6 +13815,10 @@ async function installEntry(entry, options) {
|
|
|
12609
13815
|
await mergeHooksJson(entry.path, entry.content, options);
|
|
12610
13816
|
return;
|
|
12611
13817
|
}
|
|
13818
|
+
if (entry.kind === "cursor-hooks-json" && typeof entry.content === "object") {
|
|
13819
|
+
await mergeCursorHooksFile(entry.path, entry.content, options);
|
|
13820
|
+
return;
|
|
13821
|
+
}
|
|
12612
13822
|
if (entry.kind === "hooks-toml" && typeof entry.content === "object") {
|
|
12613
13823
|
await mergeHooksToml(entry.path, entry.content, options);
|
|
12614
13824
|
return;
|
|
@@ -12623,6 +13833,10 @@ async function uninstallEntry(entry, options) {
|
|
|
12623
13833
|
await uninstallHooksToml(entry.path, entry.content, options);
|
|
12624
13834
|
return;
|
|
12625
13835
|
}
|
|
13836
|
+
if (entry.kind === "cursor-hooks-json" && typeof entry.content === "object") {
|
|
13837
|
+
await uninstallCursorHooksFile(entry.path, entry.content, options);
|
|
13838
|
+
return;
|
|
13839
|
+
}
|
|
12626
13840
|
if (entry.kind === "hooks-json" && typeof entry.content === "object") {
|
|
12627
13841
|
await uninstallHooksJson(entry.path, entry.content, options);
|
|
12628
13842
|
return;
|
|
@@ -12655,6 +13869,67 @@ async function mergeHooksJson(filePath, content, { dryRun, force, onWrite }) {
|
|
|
12655
13869
|
await writeFile5(filePath, nextText, "utf8");
|
|
12656
13870
|
onWrite(`Installed ${filePath}`);
|
|
12657
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
|
+
}
|
|
12658
13933
|
function mergeAgyHooksJson(existing, addition) {
|
|
12659
13934
|
const merged = structuredClone(isPlainObject(existing) ? existing : {});
|
|
12660
13935
|
const additionObject = isPlainObject(addition) ? addition : {};
|
|
@@ -12942,15 +14217,15 @@ async function uninstallGeneratedFile(filePath, { dryRun, onWrite }) {
|
|
|
12942
14217
|
import { randomUUID } from "node:crypto";
|
|
12943
14218
|
import { existsSync as existsSync2, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
12944
14219
|
import { homedir, hostname } from "node:os";
|
|
12945
|
-
import
|
|
14220
|
+
import path24 from "node:path";
|
|
12946
14221
|
function configDir(home = homedir()) {
|
|
12947
|
-
return
|
|
14222
|
+
return path24.join(home, ".vibetime");
|
|
12948
14223
|
}
|
|
12949
14224
|
function configPath(home = homedir()) {
|
|
12950
|
-
return
|
|
14225
|
+
return path24.join(configDir(home), "config.json");
|
|
12951
14226
|
}
|
|
12952
14227
|
function machineIdPath(home = homedir()) {
|
|
12953
|
-
return
|
|
14228
|
+
return path24.join(configDir(home), "machine-id");
|
|
12954
14229
|
}
|
|
12955
14230
|
function readConfig(home = homedir()) {
|
|
12956
14231
|
const file = configPath(home);
|
|
@@ -12997,15 +14272,15 @@ function defaultMachineName() {
|
|
|
12997
14272
|
init_fs();
|
|
12998
14273
|
|
|
12999
14274
|
// src/lib/logger.ts
|
|
13000
|
-
import { appendFile, mkdir as mkdir4, rename, stat as
|
|
14275
|
+
import { appendFile, mkdir as mkdir4, rename, stat as stat14 } from "node:fs/promises";
|
|
13001
14276
|
import { homedir as homedir2 } from "node:os";
|
|
13002
|
-
import
|
|
14277
|
+
import path25 from "node:path";
|
|
13003
14278
|
var MAX_BYTES = 1 * 1024 * 1024;
|
|
13004
14279
|
function logDir(home = homedir2()) {
|
|
13005
|
-
return
|
|
14280
|
+
return path25.join(home, ".vibetime", "logs");
|
|
13006
14281
|
}
|
|
13007
14282
|
function logPath(home = homedir2(), name = "cli.log") {
|
|
13008
|
-
return
|
|
14283
|
+
return path25.join(logDir(home), name);
|
|
13009
14284
|
}
|
|
13010
14285
|
function serializeError(error) {
|
|
13011
14286
|
if (error instanceof Error) {
|
|
@@ -13015,7 +14290,7 @@ function serializeError(error) {
|
|
|
13015
14290
|
}
|
|
13016
14291
|
async function rotateIfNeeded(file) {
|
|
13017
14292
|
try {
|
|
13018
|
-
const info = await
|
|
14293
|
+
const info = await stat14(file);
|
|
13019
14294
|
if (info.size > MAX_BYTES) {
|
|
13020
14295
|
await rename(file, `${file}.1`).catch(() => {
|
|
13021
14296
|
});
|
|
@@ -13180,8 +14455,8 @@ function buildHeaders(token, machine) {
|
|
|
13180
14455
|
...machine?.platform ? { "x-machine-platform": machine.platform } : {}
|
|
13181
14456
|
};
|
|
13182
14457
|
}
|
|
13183
|
-
function joinUrl(base,
|
|
13184
|
-
return new URL(
|
|
14458
|
+
function joinUrl(base, path27) {
|
|
14459
|
+
return new URL(path27, base.endsWith("/") ? base : `${base}/`).toString();
|
|
13185
14460
|
}
|
|
13186
14461
|
async function postRollupBatch(remote, rollups, options = {}) {
|
|
13187
14462
|
const response = await remote.fetchImpl(joinUrl(remote.baseUrl, "/v3/agent/ingest"), {
|
|
@@ -13271,6 +14546,7 @@ function createRegistry() {
|
|
|
13271
14546
|
registry.register(createGrokBuildAdapter());
|
|
13272
14547
|
registry.register(createZedAdapter());
|
|
13273
14548
|
registry.register(createKimiCodeAdapter());
|
|
14549
|
+
registry.register(createCursorAdapter());
|
|
13274
14550
|
return registry;
|
|
13275
14551
|
}
|
|
13276
14552
|
var defaultContext = {
|
|
@@ -13828,6 +15104,9 @@ async function listBackfillSourceFiles(source, options, ctx) {
|
|
|
13828
15104
|
if (source.id === "zed") {
|
|
13829
15105
|
return zedBackfillFiles(stringOption(options["source-root"]), resolveHome3(options, ctx), ctx.env);
|
|
13830
15106
|
}
|
|
15107
|
+
if (source.id === "cursor") {
|
|
15108
|
+
return cursorBackfillFiles(stringOption(options["source-root"]), resolveHome3(options, ctx), ctx.env);
|
|
15109
|
+
}
|
|
13831
15110
|
if (source.id === "pi") {
|
|
13832
15111
|
return piBackfillFiles(stringOption(options["source-root"]), resolveHome3(options, ctx), ctx.env);
|
|
13833
15112
|
}
|
|
@@ -13835,7 +15114,7 @@ async function listBackfillSourceFiles(source, options, ctx) {
|
|
|
13835
15114
|
const fileLists = await Promise.all(roots.map((r) => listJsonlFiles(r)));
|
|
13836
15115
|
const files = fileLists.flat().sort().slice(0, numberOption(options.limit) || void 0);
|
|
13837
15116
|
return Promise.all(files.map(async (filePath) => {
|
|
13838
|
-
const info = await
|
|
15117
|
+
const info = await stat15(filePath);
|
|
13839
15118
|
return { path: filePath, modifiedAt: info.mtime.toISOString() };
|
|
13840
15119
|
}));
|
|
13841
15120
|
}
|
|
@@ -14167,13 +15446,13 @@ function selectBackfillFilesForImport(files, watermarkTs) {
|
|
|
14167
15446
|
return picked;
|
|
14168
15447
|
}
|
|
14169
15448
|
function backfillIncrementalStatePath(home) {
|
|
14170
|
-
return
|
|
15449
|
+
return path26.join(home, ".vibetime", "backfill-state.json");
|
|
14171
15450
|
}
|
|
14172
15451
|
function syncLocalTriggerStatePath(home) {
|
|
14173
|
-
return
|
|
15452
|
+
return path26.join(home, ".vibetime", "sync-local-trigger.json");
|
|
14174
15453
|
}
|
|
14175
15454
|
function syncLocalTriggerLockPath(home) {
|
|
14176
|
-
return
|
|
15455
|
+
return path26.join(home, ".vibetime", "sync-local-trigger.lock");
|
|
14177
15456
|
}
|
|
14178
15457
|
function backfillRemoteKey(baseUrl) {
|
|
14179
15458
|
try {
|
|
@@ -14235,7 +15514,7 @@ async function readBackfillIncrementalStateFile(home, ctx) {
|
|
|
14235
15514
|
}
|
|
14236
15515
|
async function writeBackfillIncrementalStateFile(home, file) {
|
|
14237
15516
|
const statePath = backfillIncrementalStatePath(home);
|
|
14238
|
-
await mkdir5(
|
|
15517
|
+
await mkdir5(path26.dirname(statePath), { recursive: true });
|
|
14239
15518
|
await writeFile4(statePath, `${JSON.stringify(file, null, 2)}
|
|
14240
15519
|
`, "utf8");
|
|
14241
15520
|
}
|
|
@@ -14284,7 +15563,7 @@ async function readSyncLocalTriggerState(statePath) {
|
|
|
14284
15563
|
return nextState;
|
|
14285
15564
|
}
|
|
14286
15565
|
async function writeSyncLocalTriggerState(statePath, state) {
|
|
14287
|
-
await mkdir5(
|
|
15566
|
+
await mkdir5(path26.dirname(statePath), { recursive: true });
|
|
14288
15567
|
await writeFile4(statePath, `${JSON.stringify(state, null, 2)}
|
|
14289
15568
|
`, "utf8");
|
|
14290
15569
|
}
|
|
@@ -14299,12 +15578,12 @@ async function readSyncLocalLock(lockPath) {
|
|
|
14299
15578
|
return { pid: lock.pid, startedAt: lock.startedAt };
|
|
14300
15579
|
}
|
|
14301
15580
|
async function writeSyncLocalLock(lockPath, lock) {
|
|
14302
|
-
await mkdir5(
|
|
15581
|
+
await mkdir5(path26.dirname(lockPath), { recursive: true });
|
|
14303
15582
|
await writeFile4(lockPath, `${JSON.stringify(lock, null, 2)}
|
|
14304
15583
|
`, "utf8");
|
|
14305
15584
|
}
|
|
14306
15585
|
async function acquireSyncLocalLock(lockPath, lock) {
|
|
14307
|
-
await mkdir5(
|
|
15586
|
+
await mkdir5(path26.dirname(lockPath), { recursive: true });
|
|
14308
15587
|
try {
|
|
14309
15588
|
const handle = await open(lockPath, "wx");
|
|
14310
15589
|
try {
|
|
@@ -14384,10 +15663,10 @@ function syncLocalRunnerEntryArgs(cliPath) {
|
|
|
14384
15663
|
if (cliPath.endsWith(".ts")) {
|
|
14385
15664
|
return ["--import", "tsx", cliPath];
|
|
14386
15665
|
}
|
|
14387
|
-
return [
|
|
15666
|
+
return [path26.resolve(path26.dirname(cliPath), "../bin/vibetime.mjs")];
|
|
14388
15667
|
}
|
|
14389
15668
|
function resolveHome3(options, ctx) {
|
|
14390
|
-
return
|
|
15669
|
+
return path26.resolve(stringOption(options.home) || ctx.env.HOME || os13.homedir());
|
|
14391
15670
|
}
|
|
14392
15671
|
function requestedTargets(options) {
|
|
14393
15672
|
const value = options.target || options.targets;
|