@yhong91/vibetime 0.1.39 → 0.1.41
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 +216 -43
- package/package.json +1 -1
package/bin/vibetime.mjs
CHANGED
|
@@ -1928,7 +1928,7 @@ function countTextLines(text) {
|
|
|
1928
1928
|
}
|
|
1929
1929
|
|
|
1930
1930
|
// src/lib/constants.ts
|
|
1931
|
-
var PACKAGE_VERSION = true ? "0.1.
|
|
1931
|
+
var PACKAGE_VERSION = true ? "0.1.41" : "0.1.1";
|
|
1932
1932
|
var DEFAULT_API_URL = "http://121.196.224.82:3001";
|
|
1933
1933
|
var DEFAULT_BACKFILL_BATCH_SIZE = 50;
|
|
1934
1934
|
var DEFAULT_BACKFILL_BATCH_BYTES = 800 * 1024;
|
|
@@ -3308,7 +3308,11 @@ async function isHooksJsonInstalled(filePath, command) {
|
|
|
3308
3308
|
}
|
|
3309
3309
|
|
|
3310
3310
|
// src/adapters/claude-code.ts
|
|
3311
|
+
init_fs();
|
|
3311
3312
|
async function parseClaudeCodeSessionFile(filePath, options) {
|
|
3313
|
+
if (path7.basename(filePath) === "usage.jsonl" && path7.basename(path7.dirname(filePath)) === ".viberouter") {
|
|
3314
|
+
return parseClaudeRouterUsageFile(filePath, options);
|
|
3315
|
+
}
|
|
3312
3316
|
const text = await readFile3(filePath, "utf8");
|
|
3313
3317
|
const lines = text.split("\n").filter(Boolean);
|
|
3314
3318
|
const projectContext = await claudeProjectContextFromLines(filePath, lines, options);
|
|
@@ -3575,6 +3579,86 @@ async function parseClaudeCodeSessionFile(filePath, options) {
|
|
|
3575
3579
|
}
|
|
3576
3580
|
return state.events.filter((event) => validateCanonicalEvent(event).valid);
|
|
3577
3581
|
}
|
|
3582
|
+
async function parseClaudeRouterUsageFile(filePath, options) {
|
|
3583
|
+
const rows = (await readFile3(filePath, "utf8")).split("\n").map(parseJsonLine).filter((row) => Boolean(
|
|
3584
|
+
row && row.surface === "claude" && (numberField(row, "timestamp") ?? 0) > 0 && Object.keys(objectField(row, "usage")).length > 0
|
|
3585
|
+
));
|
|
3586
|
+
if (rows.length === 0) {
|
|
3587
|
+
return [];
|
|
3588
|
+
}
|
|
3589
|
+
const timestamps = rows.map((row) => numberField(row, "timestamp") ?? 0);
|
|
3590
|
+
const firstTs = Math.min(...timestamps) - 6e4;
|
|
3591
|
+
const lastTs = Math.max(...timestamps) + 6e4;
|
|
3592
|
+
const home = path7.resolve(stringOption(options.home) || path7.dirname(path7.dirname(filePath)));
|
|
3593
|
+
const transcriptCounts = /* @__PURE__ */ new Map();
|
|
3594
|
+
for (const transcript of await listJsonlFiles(path7.join(claudeConfigDir(home, process.env), "projects"))) {
|
|
3595
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3596
|
+
for (const line of (await readFile3(transcript, "utf8")).split("\n")) {
|
|
3597
|
+
const raw = parseJsonLine(line);
|
|
3598
|
+
const ts = raw ? Date.parse(timestampFrom(raw.timestamp) || "") : Number.NaN;
|
|
3599
|
+
if (!raw || stringField(raw, "type") !== "assistant" || ts < firstTs || ts > lastTs) {
|
|
3600
|
+
continue;
|
|
3601
|
+
}
|
|
3602
|
+
const message = objectField(raw, "message");
|
|
3603
|
+
const messageId = stringField(message, "id");
|
|
3604
|
+
const usageKey = messageId ? `${messageId}:${stringField(raw, "requestId")}` : null;
|
|
3605
|
+
if (usageKey && seen.has(usageKey)) {
|
|
3606
|
+
continue;
|
|
3607
|
+
}
|
|
3608
|
+
if (usageKey) {
|
|
3609
|
+
seen.add(usageKey);
|
|
3610
|
+
}
|
|
3611
|
+
const usage = claudeUsageFromMessage(message);
|
|
3612
|
+
if (!usage) {
|
|
3613
|
+
continue;
|
|
3614
|
+
}
|
|
3615
|
+
const key = `${usage.tokensInput || 0}:${usage.tokensOutput || 0}`;
|
|
3616
|
+
transcriptCounts.set(key, (transcriptCounts.get(key) || 0) + 1);
|
|
3617
|
+
}
|
|
3618
|
+
}
|
|
3619
|
+
const sourcePathHash = `sha256:${createStableHash(filePath)}`;
|
|
3620
|
+
const events = [];
|
|
3621
|
+
for (const row of rows) {
|
|
3622
|
+
const usage = objectField(row, "usage");
|
|
3623
|
+
const input = numberField(usage, "inputTokens") || 0;
|
|
3624
|
+
const output = numberField(usage, "outputTokens") || 0;
|
|
3625
|
+
const key = `${input}:${output}`;
|
|
3626
|
+
const matched = transcriptCounts.get(key) || 0;
|
|
3627
|
+
if (matched > 0) {
|
|
3628
|
+
transcriptCounts.set(key, matched - 1);
|
|
3629
|
+
continue;
|
|
3630
|
+
}
|
|
3631
|
+
const timestamp = numberField(row, "timestamp") ?? 0;
|
|
3632
|
+
const requestId = stringField(row, "requestId") || createStableHash(row).slice(0, 24);
|
|
3633
|
+
const cacheCreation = numberField(usage, "cacheCreationInputTokens") || 0;
|
|
3634
|
+
const cacheRead = numberField(usage, "cacheReadInputTokens") || 0;
|
|
3635
|
+
const cached = numberField(usage, "cachedInputTokens") || cacheCreation + cacheRead;
|
|
3636
|
+
const event = baseClaudeEvent({
|
|
3637
|
+
ts: new Date(timestamp).toISOString(),
|
|
3638
|
+
type: "model.usage",
|
|
3639
|
+
sessionId: `viberouter:${new Date(timestamp).toISOString().slice(0, 10)}`,
|
|
3640
|
+
project: "unknown",
|
|
3641
|
+
model: stringField(row, "resolvedModel") || stringField(row, "requestedModel") || stringField(row, "model"),
|
|
3642
|
+
provider: stringField(row, "provider"),
|
|
3643
|
+
confidence: "exact",
|
|
3644
|
+
metrics: {
|
|
3645
|
+
tokensInput: input || void 0,
|
|
3646
|
+
tokensCachedInput: cached || void 0,
|
|
3647
|
+
tokensCacheCreationInput: cacheCreation || void 0,
|
|
3648
|
+
tokensCacheReadInput: cacheRead || void 0,
|
|
3649
|
+
tokensOutput: output || void 0,
|
|
3650
|
+
tokensReasoningOutput: numberField(usage, "reasoningOutputTokens") || void 0,
|
|
3651
|
+
tokensTotal: numberField(usage, "totalTokens") || input + output || void 0,
|
|
3652
|
+
modelCalls: 1
|
|
3653
|
+
},
|
|
3654
|
+
refs: stringRefs({ sourceId: requestId, sourcePathHash, importKey: `claude-code:viberouter:${requestId}` })
|
|
3655
|
+
});
|
|
3656
|
+
if (validateCanonicalEvent(event).valid) {
|
|
3657
|
+
events.push(event);
|
|
3658
|
+
}
|
|
3659
|
+
}
|
|
3660
|
+
return events;
|
|
3661
|
+
}
|
|
3578
3662
|
function baseClaudeEvent(event) {
|
|
3579
3663
|
return {
|
|
3580
3664
|
schemaVersion: AGENT_TIME_SCHEMA_VERSION,
|
|
@@ -3911,6 +3995,7 @@ function createClaudeCodeAdapter() {
|
|
|
3911
3995
|
const base = claudeConfigDir(home, env);
|
|
3912
3996
|
return [
|
|
3913
3997
|
path7.join(base, "projects"),
|
|
3998
|
+
path7.join(home, ".viberouter", "usage.jsonl"),
|
|
3914
3999
|
path7.join(base, ".claude.json"),
|
|
3915
4000
|
path7.join(home, ".claude.json")
|
|
3916
4001
|
];
|
|
@@ -4565,48 +4650,71 @@ function normalizeModelCandidate(value) {
|
|
|
4565
4650
|
}
|
|
4566
4651
|
return trimmed;
|
|
4567
4652
|
}
|
|
4568
|
-
function
|
|
4569
|
-
|
|
4570
|
-
if (!Array.isArray(parsed) || parsed.length === 0) {
|
|
4571
|
-
return void 0;
|
|
4572
|
-
}
|
|
4573
|
-
const first = parsed[0];
|
|
4574
|
-
if (!isPlainObject(first)) {
|
|
4575
|
-
return void 0;
|
|
4576
|
-
}
|
|
4577
|
-
const usage = objectField(first, "usage");
|
|
4578
|
-
if (!isPlainObject(usage)) {
|
|
4653
|
+
function finitePositive(value) {
|
|
4654
|
+
if (typeof value !== "number" && typeof value !== "string") {
|
|
4579
4655
|
return void 0;
|
|
4580
4656
|
}
|
|
4657
|
+
const parsed = Number(value);
|
|
4658
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : void 0;
|
|
4659
|
+
}
|
|
4660
|
+
function extractUsageFromGenerationSpan(span) {
|
|
4661
|
+
const parsed = parseEmbeddedJson(span.toolOutput);
|
|
4662
|
+
const first = Array.isArray(parsed) ? parsed[0] : void 0;
|
|
4663
|
+
const usage = isPlainObject(first) ? objectField(first, "usage") : {};
|
|
4581
4664
|
const inputTokens = numberField(usage, "prompt_tokens");
|
|
4582
4665
|
const outputTokens = numberField(usage, "completion_tokens");
|
|
4583
4666
|
const totalTokens = numberField(usage, "total_tokens");
|
|
4584
4667
|
const details = objectField(usage, "prompt_tokens_details");
|
|
4585
4668
|
const cachedTokens = isPlainObject(details) ? numberField(details, "cached_tokens") : void 0;
|
|
4669
|
+
const otelAttributes = span.attributes || {};
|
|
4670
|
+
const otelInputTokens = finitePositive(otelAttributes["gen_ai.usage.input_tokens"]);
|
|
4671
|
+
const otelOutputTokens = finitePositive(otelAttributes["gen_ai.usage.output_tokens"]);
|
|
4672
|
+
const otelCacheReadTokens = finitePositive(otelAttributes["gen_ai.usage.cache_read_input_tokens"]);
|
|
4673
|
+
const otelCacheCreationTokens = finitePositive(otelAttributes["gen_ai.usage.cache_creation_input_tokens"]);
|
|
4674
|
+
const otelReasoningTokens = finitePositive(otelAttributes["gen_ai.usage.reasoning_output_tokens"]);
|
|
4675
|
+
const tokensInput = otelInputTokens ?? inputTokens;
|
|
4676
|
+
const tokensOutput = otelOutputTokens ?? outputTokens;
|
|
4677
|
+
const tokensCacheReadInput = otelCacheReadTokens ?? cachedTokens;
|
|
4678
|
+
const tokensCachedInput = (tokensCacheReadInput ?? 0) + (otelCacheCreationTokens ?? 0);
|
|
4679
|
+
const tokensTotal = totalTokens || (tokensInput ?? 0) + (tokensOutput ?? 0);
|
|
4680
|
+
if (!tokensInput && !tokensOutput && !tokensCachedInput && !otelReasoningTokens && !tokensTotal) {
|
|
4681
|
+
return void 0;
|
|
4682
|
+
}
|
|
4586
4683
|
return {
|
|
4587
|
-
tokensInput:
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4684
|
+
tokensInput: tokensInput || void 0,
|
|
4685
|
+
tokensOutput: tokensOutput || void 0,
|
|
4686
|
+
tokensCachedInput: tokensCachedInput || void 0,
|
|
4687
|
+
tokensCacheReadInput: tokensCacheReadInput || void 0,
|
|
4688
|
+
tokensCacheCreationInput: otelCacheCreationTokens,
|
|
4689
|
+
tokensReasoningOutput: otelReasoningTokens,
|
|
4690
|
+
tokensTotal: tokensTotal || void 0,
|
|
4592
4691
|
modelCalls: 1
|
|
4593
4692
|
};
|
|
4594
4693
|
}
|
|
4595
4694
|
function modelUsageFromTrace(trace, generationIndex, totalGenerations) {
|
|
4596
4695
|
const info = trace.modelInfo;
|
|
4597
|
-
|
|
4696
|
+
const otelAttributes = trace.attributes || {};
|
|
4697
|
+
const otelInputTokens = finitePositive(otelAttributes["gen_ai.usage.input_tokens"]);
|
|
4698
|
+
const otelOutputTokens = finitePositive(otelAttributes["gen_ai.usage.output_tokens"]);
|
|
4699
|
+
const otelCacheReadTokens = finitePositive(otelAttributes["gen_ai.usage.cache_read_input_tokens"]);
|
|
4700
|
+
const otelCacheCreationTokens = finitePositive(otelAttributes["gen_ai.usage.cache_creation_input_tokens"]);
|
|
4701
|
+
const otelReasoningTokens = finitePositive(otelAttributes["gen_ai.usage.reasoning_output_tokens"]);
|
|
4702
|
+
if (!info && !trace.totalTokens && !otelInputTokens && !otelOutputTokens && !otelCacheReadTokens && !otelCacheCreationTokens && !otelReasoningTokens) {
|
|
4598
4703
|
return void 0;
|
|
4599
4704
|
}
|
|
4705
|
+
const inputTokens = otelInputTokens ?? (info?.totalInputTokens || 0);
|
|
4706
|
+
const outputTokens = otelOutputTokens ?? (info?.totalOutputTokens || 0);
|
|
4707
|
+
const cacheReadTokens = otelCacheReadTokens ?? info?.totalCachedTokens;
|
|
4708
|
+
const cachedTokens = (cacheReadTokens ?? 0) + (otelCacheCreationTokens ?? 0);
|
|
4709
|
+
const totalTokens = trace.totalTokens || inputTokens + outputTokens;
|
|
4600
4710
|
if (generationIndex === 1) {
|
|
4601
|
-
const inputTokens = info.totalInputTokens || 0;
|
|
4602
|
-
const outputTokens = info.totalOutputTokens || 0;
|
|
4603
|
-
const cachedTokens = info.totalCachedTokens || 0;
|
|
4604
|
-
const totalTokens = trace.totalTokens || inputTokens + outputTokens;
|
|
4605
4711
|
return {
|
|
4606
4712
|
tokensInput: inputTokens || void 0,
|
|
4607
4713
|
tokensCachedInput: cachedTokens || void 0,
|
|
4608
|
-
tokensCacheReadInput:
|
|
4714
|
+
tokensCacheReadInput: cacheReadTokens,
|
|
4715
|
+
tokensCacheCreationInput: otelCacheCreationTokens || void 0,
|
|
4609
4716
|
tokensOutput: outputTokens || void 0,
|
|
4717
|
+
tokensReasoningOutput: otelReasoningTokens || void 0,
|
|
4610
4718
|
tokensTotal: totalTokens || void 0,
|
|
4611
4719
|
modelCalls: totalGenerations || 1
|
|
4612
4720
|
};
|
|
@@ -7468,6 +7576,11 @@ function createPiAdapter() {
|
|
|
7468
7576
|
};
|
|
7469
7577
|
}
|
|
7470
7578
|
|
|
7579
|
+
// src/adapters/qoder-cn.ts
|
|
7580
|
+
import { readdir as readdir7, readFile as readFile10, stat as stat8 } from "node:fs/promises";
|
|
7581
|
+
import os8 from "node:os";
|
|
7582
|
+
import path17 from "node:path";
|
|
7583
|
+
|
|
7471
7584
|
// src/adapters/qoder-local-db.ts
|
|
7472
7585
|
import { access } from "node:fs/promises";
|
|
7473
7586
|
import os7 from "node:os";
|
|
@@ -7514,6 +7627,7 @@ async function loadQoderDbModelCalls(appDirName, sessionId, modelMap) {
|
|
|
7514
7627
|
}
|
|
7515
7628
|
const db = new DatabaseSync(dbPath, { readOnly: true });
|
|
7516
7629
|
try {
|
|
7630
|
+
calls.rootSessionId = resolveRootSessionId(db, sessionId);
|
|
7517
7631
|
const preferredModel = resolveSessionPreferredModel(db, sessionId, modelMap);
|
|
7518
7632
|
const rows = db.prepare(
|
|
7519
7633
|
`select request_id, token_info, model_info from chat_message where session_id = ? and role = 'assistant' and token_info != '' order by gmt_create asc`
|
|
@@ -7547,6 +7661,18 @@ async function loadQoderDbModelCalls(appDirName, sessionId, modelMap) {
|
|
|
7547
7661
|
}
|
|
7548
7662
|
return calls;
|
|
7549
7663
|
}
|
|
7664
|
+
function resolveRootSessionId(db, sessionId) {
|
|
7665
|
+
let current = sessionId;
|
|
7666
|
+
for (let depth = 0; depth < 5; depth += 1) {
|
|
7667
|
+
const row = db.prepare("select parent_session_id from chat_session where session_id = ?").get(current);
|
|
7668
|
+
const parent = row ? stringField(row, "parent_session_id") : void 0;
|
|
7669
|
+
if (!parent) {
|
|
7670
|
+
return current === sessionId ? void 0 : current;
|
|
7671
|
+
}
|
|
7672
|
+
current = parent;
|
|
7673
|
+
}
|
|
7674
|
+
return current === sessionId ? void 0 : current;
|
|
7675
|
+
}
|
|
7550
7676
|
function resolveSessionPreferredModel(db, sessionId, modelMap) {
|
|
7551
7677
|
let current = sessionId;
|
|
7552
7678
|
for (let depth = 0; depth < 5 && current; depth++) {
|
|
@@ -7572,9 +7698,6 @@ function resolveSessionPreferredModel(db, sessionId, modelMap) {
|
|
|
7572
7698
|
}
|
|
7573
7699
|
|
|
7574
7700
|
// src/adapters/qoder-cn.ts
|
|
7575
|
-
import { readdir as readdir7, readFile as readFile10, stat as stat8 } from "node:fs/promises";
|
|
7576
|
-
import os8 from "node:os";
|
|
7577
|
-
import path17 from "node:path";
|
|
7578
7701
|
function parseQoderCnPaths(filePath) {
|
|
7579
7702
|
const parts = filePath.split(path17.sep);
|
|
7580
7703
|
const subagentsIdx = parts.lastIndexOf("subagents");
|
|
@@ -7617,9 +7740,8 @@ function rebuildEventIdentity2(event) {
|
|
|
7617
7740
|
}
|
|
7618
7741
|
};
|
|
7619
7742
|
}
|
|
7620
|
-
async function
|
|
7743
|
+
async function parseModelNamesFromDynamicTexts(dynamicTextsPath) {
|
|
7621
7744
|
try {
|
|
7622
|
-
const dynamicTextsPath = path17.join(configDir2, ".auth", "dynamic-texts.json");
|
|
7623
7745
|
const content = await readFile10(dynamicTextsPath, "utf8");
|
|
7624
7746
|
const json = JSON.parse(content);
|
|
7625
7747
|
const texts = json.texts || {};
|
|
@@ -7635,6 +7757,19 @@ async function loadQoderCnModelNames(configDir2) {
|
|
|
7635
7757
|
return {};
|
|
7636
7758
|
}
|
|
7637
7759
|
}
|
|
7760
|
+
async function loadQoderCnModelNames(configDir2) {
|
|
7761
|
+
const map = await parseModelNamesFromDynamicTexts(path17.join(configDir2, ".auth", "dynamic-texts.json"));
|
|
7762
|
+
const siblingConfigDir = configDir2.replace(/\.qoder-cn$/, ".qoder");
|
|
7763
|
+
if (siblingConfigDir !== configDir2) {
|
|
7764
|
+
const siblingMap = await parseModelNamesFromDynamicTexts(path17.join(siblingConfigDir, ".auth", "dynamic-texts.json"));
|
|
7765
|
+
for (const [key, val] of Object.entries(siblingMap)) {
|
|
7766
|
+
if (!(key in map)) {
|
|
7767
|
+
map[key] = val;
|
|
7768
|
+
}
|
|
7769
|
+
}
|
|
7770
|
+
}
|
|
7771
|
+
return map;
|
|
7772
|
+
}
|
|
7638
7773
|
async function loadQoderCnSegmentModelCalls(filePath, isSubagentSession, modelMap) {
|
|
7639
7774
|
const { configDir: configDir2, projectName, sessionId } = parseQoderCnPaths(filePath);
|
|
7640
7775
|
const segmentsPath = path17.join(configDir2, "logs", "sessions", projectName, sessionId, "segments");
|
|
@@ -7680,7 +7815,8 @@ async function loadQoderCnSegmentModelCalls(filePath, isSubagentSession, modelMa
|
|
|
7680
7815
|
async function parseQoderCnSessionFile(filePath, options) {
|
|
7681
7816
|
const text = await readFile10(filePath, "utf8");
|
|
7682
7817
|
const lines = text.split("\n").filter(Boolean);
|
|
7683
|
-
const
|
|
7818
|
+
const parsedPaths = parseQoderCnPaths(filePath);
|
|
7819
|
+
const { configDir: configDir2 } = parsedPaths;
|
|
7684
7820
|
const projectContext = await qoderCnProjectContextFromLines(filePath, lines, options, configDir2);
|
|
7685
7821
|
const pendingTools = /* @__PURE__ */ new Map();
|
|
7686
7822
|
const seenUsageKeys = /* @__PURE__ */ new Set();
|
|
@@ -8008,6 +8144,16 @@ async function parseQoderCnSessionFile(filePath, options) {
|
|
|
8008
8144
|
});
|
|
8009
8145
|
}
|
|
8010
8146
|
}
|
|
8147
|
+
dbModelCalls ??= await loadQoderDbModelCalls("QoderCN", parsedPaths.sessionId, modelMap);
|
|
8148
|
+
if (dbModelCalls.rootSessionId) {
|
|
8149
|
+
const parentPath = path17.join(path17.dirname(filePath), `${dbModelCalls.rootSessionId}.jsonl`);
|
|
8150
|
+
const parentSourcePathHash = `sha256:${createStableHash(parentPath)}`;
|
|
8151
|
+
return validEvents.map((event) => rebuildEventIdentity2({
|
|
8152
|
+
...event,
|
|
8153
|
+
sessionId: dbModelCalls.rootSessionId,
|
|
8154
|
+
refs: { ...event.refs, sourcePathHash: parentSourcePathHash }
|
|
8155
|
+
}));
|
|
8156
|
+
}
|
|
8011
8157
|
return validEvents;
|
|
8012
8158
|
}
|
|
8013
8159
|
function baseQoderCnEvent(event) {
|
|
@@ -8420,9 +8566,8 @@ function rebuildEventIdentity3(event) {
|
|
|
8420
8566
|
}
|
|
8421
8567
|
};
|
|
8422
8568
|
}
|
|
8423
|
-
async function
|
|
8569
|
+
async function parseModelNamesFromDynamicTexts2(dynamicTextsPath) {
|
|
8424
8570
|
try {
|
|
8425
|
-
const dynamicTextsPath = path18.join(configDir2, ".auth", "dynamic-texts.json");
|
|
8426
8571
|
const content = await readFile11(dynamicTextsPath, "utf8");
|
|
8427
8572
|
const json = JSON.parse(content);
|
|
8428
8573
|
const texts = json.texts || {};
|
|
@@ -8438,6 +8583,19 @@ async function loadQoderModelNames(configDir2) {
|
|
|
8438
8583
|
return {};
|
|
8439
8584
|
}
|
|
8440
8585
|
}
|
|
8586
|
+
async function loadQoderModelNames(configDir2) {
|
|
8587
|
+
const map = await parseModelNamesFromDynamicTexts2(path18.join(configDir2, ".auth", "dynamic-texts.json"));
|
|
8588
|
+
const siblingConfigDir = configDir2.replace(/\.qoder$/, ".qoder-cn");
|
|
8589
|
+
if (siblingConfigDir !== configDir2) {
|
|
8590
|
+
const siblingMap = await parseModelNamesFromDynamicTexts2(path18.join(siblingConfigDir, ".auth", "dynamic-texts.json"));
|
|
8591
|
+
for (const [key, val] of Object.entries(siblingMap)) {
|
|
8592
|
+
if (!(key in map)) {
|
|
8593
|
+
map[key] = val;
|
|
8594
|
+
}
|
|
8595
|
+
}
|
|
8596
|
+
}
|
|
8597
|
+
return map;
|
|
8598
|
+
}
|
|
8441
8599
|
async function loadQoderSegmentModelCalls(filePath, isSubagentSession, modelMap) {
|
|
8442
8600
|
const { configDir: configDir2, projectName, sessionId } = parseQoderPaths(filePath);
|
|
8443
8601
|
const segmentsPath = path18.join(configDir2, "logs", "sessions", projectName, sessionId, "segments");
|
|
@@ -8483,7 +8641,8 @@ async function loadQoderSegmentModelCalls(filePath, isSubagentSession, modelMap)
|
|
|
8483
8641
|
async function parseQoderSessionFile(filePath, options) {
|
|
8484
8642
|
const text = await readFile11(filePath, "utf8");
|
|
8485
8643
|
const lines = text.split("\n").filter(Boolean);
|
|
8486
|
-
const
|
|
8644
|
+
const parsedPaths = parseQoderPaths(filePath);
|
|
8645
|
+
const { configDir: configDir2 } = parsedPaths;
|
|
8487
8646
|
const projectContext = await qoderProjectContextFromLines(filePath, lines, options, configDir2);
|
|
8488
8647
|
const pendingTools = /* @__PURE__ */ new Map();
|
|
8489
8648
|
const seenUsageKeys = /* @__PURE__ */ new Set();
|
|
@@ -8811,6 +8970,16 @@ async function parseQoderSessionFile(filePath, options) {
|
|
|
8811
8970
|
});
|
|
8812
8971
|
}
|
|
8813
8972
|
}
|
|
8973
|
+
dbModelCalls ??= await loadQoderDbModelCalls("Qoder", parsedPaths.sessionId, modelMap);
|
|
8974
|
+
if (dbModelCalls.rootSessionId) {
|
|
8975
|
+
const parentPath = path18.join(path18.dirname(filePath), `${dbModelCalls.rootSessionId}.jsonl`);
|
|
8976
|
+
const parentSourcePathHash = `sha256:${createStableHash(parentPath)}`;
|
|
8977
|
+
return validEvents.map((event) => rebuildEventIdentity3({
|
|
8978
|
+
...event,
|
|
8979
|
+
sessionId: dbModelCalls.rootSessionId,
|
|
8980
|
+
refs: { ...event.refs, sourcePathHash: parentSourcePathHash }
|
|
8981
|
+
}));
|
|
8982
|
+
}
|
|
8814
8983
|
return validEvents;
|
|
8815
8984
|
}
|
|
8816
8985
|
function baseQoderEvent(event) {
|
|
@@ -9846,7 +10015,7 @@ function modelMetrics(row) {
|
|
|
9846
10015
|
toolCalls: numberField(row, "tool_call_count")
|
|
9847
10016
|
};
|
|
9848
10017
|
}
|
|
9849
|
-
function
|
|
10018
|
+
function resolveRootSessionId2(sessionId, sessions) {
|
|
9850
10019
|
let current = sessionId;
|
|
9851
10020
|
const visited = /* @__PURE__ */ new Set();
|
|
9852
10021
|
while (true) {
|
|
@@ -9868,7 +10037,7 @@ function sessionContext(row, sessions) {
|
|
|
9868
10037
|
const session = sessions.get(sessionId);
|
|
9869
10038
|
const cwd = stringField(session, "directory");
|
|
9870
10039
|
const project = projectFromDirectory(cwd);
|
|
9871
|
-
const rootSessionId =
|
|
10040
|
+
const rootSessionId = resolveRootSessionId2(sessionId, sessions);
|
|
9872
10041
|
return {
|
|
9873
10042
|
rawSessionId: sessionId,
|
|
9874
10043
|
sessionId: `zcode:${rootSessionId}`,
|
|
@@ -11242,9 +11411,10 @@ async function postRollupBatch(remote, rollups, options = {}) {
|
|
|
11242
11411
|
failed: 0
|
|
11243
11412
|
};
|
|
11244
11413
|
}
|
|
11245
|
-
async function deleteRollupsBySource(remote, source, machine) {
|
|
11414
|
+
async function deleteRollupsBySource(remote, source, machine, options = {}) {
|
|
11415
|
+
const query = `source=${encodeURIComponent(source)}${options.preserveTokens ? "&preserveTokens=1" : ""}`;
|
|
11246
11416
|
const response = await remote.fetchImpl(
|
|
11247
|
-
joinUrl(remote.baseUrl, `/v3/agent/sessions
|
|
11417
|
+
joinUrl(remote.baseUrl, `/v3/agent/sessions?${query}`),
|
|
11248
11418
|
{ method: "DELETE", headers: buildHeaders(remote.token, machine) }
|
|
11249
11419
|
);
|
|
11250
11420
|
if (!response.ok) {
|
|
@@ -11367,7 +11537,7 @@ function createCli(ctx, registry) {
|
|
|
11367
11537
|
cli.command("hook", "Read agent hook JSON from stdin and report a throttled event").option("--agent <name>", "Agent name").option("--project <name>", "Project name").option("--min-interval <seconds>", "Minimum seconds between similar hook reports").action((options) => hookCommand(normalizeOptions(options), ctx));
|
|
11368
11538
|
cli.command("sync-local-trigger", "Trigger one background local sync with throttle and locking").option("--min-interval <seconds>", "Minimum seconds between sync triggers").action((options) => syncLocalTriggerCommand(normalizeOptions(options), ctx, registry));
|
|
11369
11539
|
cli.command("sync-local-runner", "Internal background local sync runner").option("--lock-file <path>", "Lock file for the active sync").option("--state-file <path>", "State file for trigger metadata").action((options) => syncLocalRunnerCommand(normalizeOptions(options), ctx, registry));
|
|
11370
|
-
cli.command("backfill [action]", "Inspect local history import candidates").option("--source <source>", "Backfill source").option("--since <time>", "Only include history after this time").option("--until <time>", "Only include history before this time").option("--project <name>", "Project filter").option("--source-root <path>", "Override source history root").option("--include-source-path", "Include local source paths in output").option("--import-run <id>", "Import run id for verify/resume workflows").option("--limit <count>", "Maximum session files to parse").option("--batch-size <count>", "Max rollups per request (also bounded by --batch-bytes)").option("--batch-bytes <bytes>", "Soft byte cap for the JSON body of a single ingest POST").option("--replace", "Replace conflicting records during import (default)").option("--skip-conflicts", "Skip conflicting records instead of replacing them").option("--force", "Force full re-import: clear watermark and re-process all files").action((action, options) => backfillCommand({ ...normalizeOptions(options), action }, ctx, registry));
|
|
11540
|
+
cli.command("backfill [action]", "Inspect local history import candidates").option("--source <source>", "Backfill source").option("--since <time>", "Only include history after this time").option("--until <time>", "Only include history before this time").option("--project <name>", "Project filter").option("--source-root <path>", "Override source history root").option("--include-source-path", "Include local source paths in output").option("--import-run <id>", "Import run id for verify/resume workflows").option("--limit <count>", "Maximum session files to parse").option("--batch-size <count>", "Max rollups per request (also bounded by --batch-bytes)").option("--batch-bytes <bytes>", "Soft byte cap for the JSON body of a single ingest POST").option("--replace", "Replace conflicting records during import (default)").option("--skip-conflicts", "Skip conflicting records instead of replacing them").option("--force", "Force full re-import: clear watermark and re-process all files (keeps server rollups that already have tokens)").option("--purge-all", "With --force: also delete token-bearing server rollups before re-importing (DANGEROUS \u2014 tokens whose local sources were rotated away are lost forever)").action((action, options) => backfillCommand({ ...normalizeOptions(options), action }, ctx, registry));
|
|
11371
11541
|
cli.command("token [action] [value]", "Set, show, or clear the persisted API token").option("--remote <url>", "Override API base URL when setting a token").action((action, value, options) => tokenCommand(action, value, normalizeOptions(options), ctx));
|
|
11372
11542
|
cli.command("machine [action]", "List or rename machines (requires login)").option("--name <name>", "New display name (used by `machine rename`)").option("--id <id>", "Machine id (defaults to current machine)").action((action, options) => machineCommand(action, normalizeOptions(options), ctx));
|
|
11373
11543
|
return cli;
|
|
@@ -11386,7 +11556,8 @@ function normalizeOptions(options) {
|
|
|
11386
11556
|
importRun: "import-run",
|
|
11387
11557
|
batchSize: "batch-size",
|
|
11388
11558
|
batchBytes: "batch-bytes",
|
|
11389
|
-
skipConflicts: "skip-conflicts"
|
|
11559
|
+
skipConflicts: "skip-conflicts",
|
|
11560
|
+
purgeAll: "purge-all"
|
|
11390
11561
|
};
|
|
11391
11562
|
for (const [camel, dashed] of Object.entries(aliases)) {
|
|
11392
11563
|
if (normalized[camel] !== void 0 && normalized[dashed] === void 0) {
|
|
@@ -11958,11 +12129,13 @@ async function purgeForcedSources(sourceDefs, home, remoteKey, options, ctx) {
|
|
|
11958
12129
|
debug(ctx, `Failed to clear backfill watermark: ${error.message}
|
|
11959
12130
|
`);
|
|
11960
12131
|
}
|
|
12132
|
+
const preserveTokens = !options["purge-all"];
|
|
11961
12133
|
for (const item of sourceDefs) {
|
|
11962
12134
|
try {
|
|
11963
|
-
const deleted = await deleteSessionRollupsBySourceAPI(item.id, options, ctx);
|
|
12135
|
+
const deleted = await deleteSessionRollupsBySourceAPI(item.id, options, ctx, preserveTokens);
|
|
11964
12136
|
if (!options.json) {
|
|
11965
|
-
|
|
12137
|
+
const suffix = preserveTokens ? " (token-bearing rollups kept)" : "";
|
|
12138
|
+
write(ctx.stdout, `purged ${item.id}: ${deleted} old rollups${suffix}
|
|
11966
12139
|
`);
|
|
11967
12140
|
}
|
|
11968
12141
|
} catch (error) {
|
|
@@ -12092,7 +12265,7 @@ async function sendSessionRollupBatch(rollups, options, ctx) {
|
|
|
12092
12265
|
});
|
|
12093
12266
|
return result;
|
|
12094
12267
|
}
|
|
12095
|
-
async function deleteSessionRollupsBySourceAPI(source, options, ctx) {
|
|
12268
|
+
async function deleteSessionRollupsBySourceAPI(source, options, ctx, preserveTokens) {
|
|
12096
12269
|
const remote = resolveRemoteFromOptions(options, ctx);
|
|
12097
12270
|
if (!remote) {
|
|
12098
12271
|
throw new Error("No fetch available for HTTP delete");
|
|
@@ -12102,7 +12275,7 @@ async function deleteSessionRollupsBySourceAPI(source, options, ctx) {
|
|
|
12102
12275
|
id: ensureLocalMachineId(home),
|
|
12103
12276
|
hostname: defaultMachineName(),
|
|
12104
12277
|
platform: process.platform
|
|
12105
|
-
});
|
|
12278
|
+
}, { preserveTokens });
|
|
12106
12279
|
}
|
|
12107
12280
|
function shouldUseIncrementalBackfill(options) {
|
|
12108
12281
|
return !stringOption(options.since) && !stringOption(options.until) && !stringOption(options["source-root"]) && numberOption(options.limit) === void 0;
|
|
@@ -12561,7 +12734,7 @@ Usage:
|
|
|
12561
12734
|
vibetime install [--target codex,claude,opencode,pi] [--all] [--dry-run] [--force] [--home <path>]
|
|
12562
12735
|
vibetime upgrade [--check]
|
|
12563
12736
|
vibetime hook --agent <name>
|
|
12564
|
-
vibetime backfill discover|plan|import|verify --source codex|claude-code|opencode|pi|all --dry-run [--json] [--batch-size <count>]
|
|
12737
|
+
vibetime backfill discover|plan|import|verify --source codex|claude-code|opencode|pi|all --dry-run [--json] [--batch-size <count>] [--force [--purge-all]]
|
|
12565
12738
|
vibetime token set <token>
|
|
12566
12739
|
vibetime token show
|
|
12567
12740
|
vibetime token clear
|
package/package.json
CHANGED