fix(usage): roll up session lineage history

Summary:
- Roll up transcript-backed session usage across logical session lineage.
- Preserve lineage through /new and /reset rotations.
- Add Control UI usage scope controls with legacy gateway fallback.
- Refresh generated protocol and Control UI locale fallback surfaces.

Verification:
- pnpm test src/auto-reply/reply/session.test.ts ui/src/ui/controllers/usage.node.test.ts src/gateway/server-methods/usage.sessions-usage.test.ts
- pnpm protocol:check
- pnpm ui:i18n:check
- pnpm ui:build
- git diff --check
- PR CI green on 10f10850ee

Closes #50701.
This commit is contained in:
Val Alexander
2026-05-07 22:38:11 -05:00
committed by GitHub
parent 737e5707f1
commit d12c92c216
76 changed files with 1299 additions and 145 deletions

View File

@@ -6,6 +6,7 @@ Docs: https://docs.openclaw.ai
### Changes
- Control UI/usage: add transcript-backed historical lineage rollups for rotated logical sessions, with current-instance vs historical-lineage scope controls and long-range presets so usage history stays visible after restarts and updates. Fixes #50701. Thanks @dev-gideon-llc and @BunsDev.
- Agents/failover: harden state-aware lane suspension by persisting quota resume transitions, restoring configured lane concurrency, preserving non-quota failure reasons, and exporting model failover events through diagnostics OTLP. Thanks @BunsDev.
- Channels/streaming: make progress draft labels scroll away with other progress lines, render structured tool rows as compact emoji/title/details, show web-search queries from provider-native argument shapes, and skip empty Discord apply-patch starts until a patch summary exists. (#79146)
- Telegram: preserve the channel-specific 10-option poll cap in the unified outbound adapter so over-limit polls are rejected before send. (#78762) Thanks @obviyus.

View File

@@ -2243,6 +2243,9 @@ public struct SessionsUsageParams: Codable, Sendable {
public let startdate: String?
public let enddate: String?
public let mode: AnyCodable?
public let range: AnyCodable?
public let groupby: AnyCodable?
public let includehistorical: Bool?
public let utcoffset: String?
public let limit: Int?
public let includecontextweight: Bool?
@@ -2252,6 +2255,9 @@ public struct SessionsUsageParams: Codable, Sendable {
startdate: String?,
enddate: String?,
mode: AnyCodable?,
range: AnyCodable?,
groupby: AnyCodable?,
includehistorical: Bool?,
utcoffset: String?,
limit: Int?,
includecontextweight: Bool?)
@@ -2260,6 +2266,9 @@ public struct SessionsUsageParams: Codable, Sendable {
self.startdate = startdate
self.enddate = enddate
self.mode = mode
self.range = range
self.groupby = groupby
self.includehistorical = includehistorical
self.utcoffset = utcoffset
self.limit = limit
self.includecontextweight = includecontextweight
@@ -2270,6 +2279,9 @@ public struct SessionsUsageParams: Codable, Sendable {
case startdate = "startDate"
case enddate = "endDate"
case mode
case range
case groupby = "groupBy"
case includehistorical = "includeHistorical"
case utcoffset = "utcOffset"
case limit
case includecontextweight = "includeContextWeight"

View File

@@ -62,6 +62,10 @@ export async function resetReplyRunSession(params: {
sessionId: nextSessionId,
updatedAt: now,
sessionStartedAt: now,
usageFamilyKey: prevEntry.usageFamilyKey ?? params.sessionKey,
usageFamilySessionIds: Array.from(
new Set([...(prevEntry.usageFamilySessionIds ?? []), prevEntry.sessionId, nextSessionId]),
),
lastInteractionAt: now,
systemSent: false,
abortedLastRun: false,

View File

@@ -278,6 +278,10 @@ export async function incrementCompactionCount(params: {
storePath,
newSessionId,
});
updates.usageFamilyKey = entry.usageFamilyKey ?? sessionKey;
updates.usageFamilySessionIds = Array.from(
new Set([...(entry.usageFamilySessionIds ?? []), entry.sessionId, newSessionId]),
);
} else if (sessionFileChanged && explicitNewSessionFile) {
updates.sessionFile = explicitNewSessionFile;
}

View File

@@ -2178,6 +2178,69 @@ describe("initSessionState preserves behavior overrides across /new and /reset",
}
});
it("preserves usage family metadata across /new and /reset", async () => {
const storePath = await createStorePath("openclaw-reset-usage-family-");
const sessionKey = "agent:main:telegram:dm:user-usage-family";
const existingSessionId = "existing-session-usage-family";
const cases = [
{
name: "new preserves usage family metadata",
body: "/new",
},
{
name: "reset preserves usage family metadata",
body: "/reset",
},
] as const;
for (const testCase of cases) {
await seedSessionStoreWithOverrides({
storePath,
sessionKey,
sessionId: existingSessionId,
overrides: {
usageFamilyKey: "family:user-usage-family",
usageFamilySessionIds: ["ancestor-session", existingSessionId],
},
});
const result = await initSessionState({
ctx: {
Body: testCase.body,
RawBody: testCase.body,
CommandBody: testCase.body,
From: "user-usage-family",
To: "bot",
ChatType: "direct",
SessionKey: sessionKey,
Provider: "telegram",
Surface: "telegram",
},
cfg: {
session: { store: storePath, idleMinutes: 999 },
} as OpenClawConfig,
commandAuthorized: true,
});
expect(result.resetTriggered, testCase.name).toBe(true);
expect(result.sessionId, testCase.name).not.toBe(existingSessionId);
expect(result.sessionEntry.usageFamilyKey, testCase.name).toBe("family:user-usage-family");
expect(result.sessionEntry.usageFamilySessionIds, testCase.name).toEqual([
"ancestor-session",
existingSessionId,
result.sessionId,
]);
const stored = JSON.parse(await fs.readFile(storePath, "utf-8"));
expect(stored[sessionKey].usageFamilyKey, testCase.name).toBe("family:user-usage-family");
expect(stored[sessionKey].usageFamilySessionIds, testCase.name).toEqual([
"ancestor-session",
existingSessionId,
result.sessionId,
]);
}
});
it("preserves selected auth profile overrides across /new and /reset", async () => {
const storePath = await createStorePath("openclaw-reset-model-auth-");
const sessionKey = "agent:main:telegram:dm:user-model-auth";

View File

@@ -544,6 +544,18 @@ export async function initSessionState(params: {
}
const baseEntry = !isNewSession && freshEntry ? entry : undefined;
const usageFamilyKey = previousSessionEntry
? (previousSessionEntry.usageFamilyKey ?? sessionKey)
: baseEntry?.usageFamilyKey;
const usageFamilySessionIds = previousSessionEntry
? Array.from(
new Set([
...(previousSessionEntry.usageFamilySessionIds ?? []),
previousSessionEntry.sessionId,
sessionId,
]),
)
: baseEntry?.usageFamilySessionIds;
// Track the originating channel/to for announce routing (subagent announce-back).
const originatingChannelRaw = ctx.OriginatingChannel as string | undefined;
const isInterSession = isInterSessionInputProvenance(ctx.InputProvenance);
@@ -626,6 +638,8 @@ export async function initSessionState(params: {
reasoningLevel: persistedReasoning ?? baseEntry?.reasoningLevel,
ttsAuto: persistedTtsAuto ?? baseEntry?.ttsAuto,
responseUsage: baseEntry?.responseUsage,
usageFamilyKey,
usageFamilySessionIds,
modelOverride: persistedModelOverride ?? baseEntry?.modelOverride,
providerOverride: persistedProviderOverride ?? baseEntry?.providerOverride,
modelOverrideSource: persistedModelOverrideSource ?? baseEntry?.modelOverrideSource,

View File

@@ -220,6 +220,10 @@ export type SessionEntry = {
quotaSuspension?: QuotaSuspension;
/** Timestamp (ms) when the current sessionId first became active. */
sessionStartedAt?: number;
/** Stable usage lineage key for transcript-backed rollups across sessionId rotations. */
usageFamilyKey?: string;
/** Session ids known to belong to this usage lineage, including archived predecessors. */
usageFamilySessionIds?: string[];
/** Timestamp (ms) of the last user/channel interaction that should extend idle lifetime. */
lastInteractionAt?: number;
/** Stable first-run start time for subagent sessions, persisted after completion. */

View File

@@ -346,6 +346,20 @@ export const SessionsUsageParamsSchema = Type.Object(
mode: Type.Optional(
Type.Union([Type.Literal("utc"), Type.Literal("gateway"), Type.Literal("specific")]),
),
/** Preset range for usage queries when explicit start/end dates are omitted. */
range: Type.Optional(
Type.Union([
Type.Literal("7d"),
Type.Literal("30d"),
Type.Literal("90d"),
Type.Literal("1y"),
Type.Literal("all"),
]),
),
/** Usage row grouping. `family` rolls up known rotated session ids for a logical key. */
groupBy: Type.Optional(Type.Union([Type.Literal("instance"), Type.Literal("family")])),
/** Backward-compatible alias for requesting family grouping. */
includeHistorical: Type.Optional(Type.Boolean()),
/** UTC offset to use when mode is `specific` (for example, UTC-4 or UTC+5:30). */
utcOffset: Type.Optional(Type.String({ pattern: "^UTC[+-]\\d{1,2}(?::[0-5]\\d)?$" })),
/** Maximum sessions to return (default 50). */

View File

@@ -214,6 +214,98 @@ describe("sessions.usage", () => {
}
});
it("rolls up known session family ids when historical usage is requested", async () => {
const storeKey = "agent:opus:main";
const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-usage-test-"));
try {
await withEnvAsync({ OPENCLAW_STATE_DIR: stateDir }, async () => {
const agentSessionsDir = path.join(stateDir, "agents", "opus", "sessions");
fs.mkdirSync(agentSessionsDir, { recursive: true });
fs.writeFileSync(path.join(agentSessionsDir, "current.jsonl"), "", "utf-8");
fs.writeFileSync(
path.join(agentSessionsDir, "old.jsonl.reset.2026-02-01T00-00-00.000Z"),
"",
"utf-8",
);
vi.mocked(loadCombinedSessionStoreForGateway).mockReturnValue({
storePath: "(multiple)",
store: {
[storeKey]: {
sessionId: "current",
sessionFile: "current.jsonl",
updatedAt: 1_000,
usageFamilyKey: storeKey,
usageFamilySessionIds: ["old", "current"],
},
},
});
vi.mocked(loadSessionCostSummaryFromCache).mockImplementation(async ({ sessionId }) => ({
summary: {
input: sessionId === "old" ? 10 : 20,
output: 0,
cacheRead: 0,
cacheWrite: 0,
totalTokens: sessionId === "old" ? 10 : 20,
totalCost: sessionId === "old" ? 0.01 : 0.02,
inputCost: sessionId === "old" ? 0.01 : 0.02,
outputCost: 0,
cacheReadCost: 0,
cacheWriteCost: 0,
missingCostEntries: 0,
messageCounts: {
total: 1,
user: 1,
assistant: 0,
toolCalls: 0,
toolResults: 0,
errors: 0,
},
},
cacheStatus: {
status: "fresh",
cachedFiles: 1,
pendingFiles: 0,
staleFiles: 0,
},
}));
const respond = await runSessionsUsage({
...BASE_USAGE_RANGE,
key: storeKey,
groupBy: "family",
includeHistorical: true,
});
expect(respond).toHaveBeenCalledTimes(1);
expect(respond.mock.calls[0]?.[0]).toBe(true);
const result = respond.mock.calls[0]?.[1] as {
sessions: Array<{
key: string;
scope?: string;
includedSessionIds?: string[];
usage?: { totalTokens: number; totalCost: number; messageCounts?: { total: number } };
}>;
totals: { totalTokens: number; totalCost: number };
};
expect(result.sessions).toHaveLength(1);
expect(result.sessions[0]).toMatchObject({
key: storeKey,
scope: "family",
includedSessionIds: ["current", "old"],
});
expect(result.sessions[0]?.usage?.totalTokens).toBe(30);
expect(result.sessions[0]?.usage?.totalCost).toBeCloseTo(0.03);
expect(result.sessions[0]?.usage?.messageCounts?.total).toBe(2);
expect(result.totals.totalTokens).toBe(30);
expect(result.totals.totalCost).toBeCloseTo(0.03);
});
} finally {
fs.rmSync(stateDir, { recursive: true, force: true });
}
});
it("prefers the deterministic store key when duplicate sessionIds exist", async () => {
const preferredKey = "agent:opus:acp:run-dup";
const stateDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-usage-test-"));

View File

@@ -8,6 +8,7 @@ import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { loadProviderUsageSummary } from "../../infra/provider-usage.js";
import type {
CostUsageSummary,
SessionCostSummary,
SessionDailyModelUsage,
SessionMessageCounts,
SessionModelUsage,
@@ -237,6 +238,25 @@ const parseDays = (raw: unknown): number | undefined => {
return undefined;
};
const resolveRangeDays = (raw: unknown): number | "all" | undefined => {
if (raw === "all") {
return "all";
}
if (raw === "7d") {
return 7;
}
if (raw === "30d") {
return 30;
}
if (raw === "90d") {
return 90;
}
if (raw === "1y") {
return 365;
}
return undefined;
};
/**
* Get date range from params (startDate/endDate or days).
* Falls back to last 30 days if not provided.
@@ -245,6 +265,7 @@ const parseDateRange = (params: {
startDate?: unknown;
endDate?: unknown;
days?: unknown;
range?: unknown;
mode?: unknown;
utcOffset?: unknown;
}): DateRange => {
@@ -261,6 +282,15 @@ const parseDateRange = (params: {
return { startMs, endMs: endMs + DAY_MS - 1 };
}
const rangeDays = resolveRangeDays(params.range);
if (rangeDays === "all") {
return { startMs: 0, endMs: todayEndMs };
}
if (rangeDays !== undefined) {
const start = todayStartMs - (rangeDays - 1) * DAY_MS;
return { startMs: start, endMs: todayEndMs };
}
const days = parseDays(params.days);
if (days !== undefined) {
const clampedDays = Math.max(1, days);
@@ -274,6 +304,21 @@ const parseDateRange = (params: {
};
type DiscoveredSessionWithAgent = DiscoveredSession & { agentId: string };
type UsageGroupingMode = "instance" | "family";
type MergedEntry = {
key: string;
sessionId: string;
sessionFile: string;
label?: string;
updatedAt: number;
storeEntry?: SessionEntry;
firstUserMessage?: string;
scope?: "instance" | "family";
sessionFamilyKey?: string;
currentSessionId?: string;
includedSessionIds?: string[];
};
function buildStoreBySessionId(
store: Record<string, SessionEntry>,
@@ -322,6 +367,323 @@ async function discoverAllSessionsForUsage(params: {
return results.flat().toSorted((a, b) => b.mtime - a.mtime);
}
function addUniqueSessionIds(target: string[], ids: Array<string | undefined>): string[] {
const seen = new Set(target);
for (const id of ids) {
const normalized = normalizeOptionalString(id);
if (normalized && !seen.has(normalized)) {
seen.add(normalized);
target.push(normalized);
}
}
return target;
}
function resolveUsageFamilySessionIds(entry: SessionEntry | undefined, currentSessionId: string) {
return addUniqueSessionIds([], [currentSessionId, ...(entry?.usageFamilySessionIds ?? [])]);
}
function resolveUsageFamilyKey(params: {
key: string;
entry: SessionEntry | undefined;
sessionId: string;
}): string {
return params.entry?.usageFamilyKey ?? params.key ?? params.sessionId;
}
function maybeMergeFamilyEntry(params: {
mergedEntries: MergedEntry[];
base: MergedEntry;
groupingMode: UsageGroupingMode;
}) {
if (params.groupingMode !== "family") {
params.mergedEntries.push(params.base);
return;
}
const includedSessionIds = resolveUsageFamilySessionIds(
params.base.storeEntry,
params.base.sessionId,
);
const sessionFamilyKey = resolveUsageFamilyKey({
key: params.base.key,
entry: params.base.storeEntry,
sessionId: params.base.sessionId,
});
params.mergedEntries.push({
...params.base,
scope: "family",
sessionFamilyKey,
currentSessionId: params.base.sessionId,
includedSessionIds,
});
}
function createEmptySessionCostSummary(): SessionCostSummary {
return {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 0,
totalCost: 0,
inputCost: 0,
outputCost: 0,
cacheReadCost: 0,
cacheWriteCost: 0,
missingCostEntries: 0,
};
}
function mergeSessionUsageInto(target: SessionCostSummary, source: SessionCostSummary): void {
target.input += source.input;
target.output += source.output;
target.cacheRead += source.cacheRead;
target.cacheWrite += source.cacheWrite;
target.totalTokens += source.totalTokens;
target.totalCost += source.totalCost;
target.inputCost += source.inputCost;
target.outputCost += source.outputCost;
target.cacheReadCost += source.cacheReadCost;
target.cacheWriteCost += source.cacheWriteCost;
target.missingCostEntries += source.missingCostEntries;
target.firstActivity =
target.firstActivity === undefined
? source.firstActivity
: source.firstActivity === undefined
? target.firstActivity
: Math.min(target.firstActivity, source.firstActivity);
target.lastActivity =
target.lastActivity === undefined
? source.lastActivity
: source.lastActivity === undefined
? target.lastActivity
: Math.max(target.lastActivity, source.lastActivity);
if (target.firstActivity !== undefined && target.lastActivity !== undefined) {
target.durationMs = Math.max(0, target.lastActivity - target.firstActivity);
}
const activityDates = new Set([...(target.activityDates ?? []), ...(source.activityDates ?? [])]);
if (activityDates.size > 0) {
target.activityDates = Array.from(activityDates).toSorted();
}
target.dailyBreakdown = mergeDailyRows(target.dailyBreakdown, source.dailyBreakdown, [
"tokens",
"cost",
]);
target.dailyMessageCounts = mergeDailyRows(target.dailyMessageCounts, source.dailyMessageCounts, [
"total",
"user",
"assistant",
"toolCalls",
"toolResults",
"errors",
]);
target.utcQuarterHourMessageCounts = mergeQuarterRows(
target.utcQuarterHourMessageCounts,
source.utcQuarterHourMessageCounts,
["total", "user", "assistant", "toolCalls", "toolResults", "errors"],
);
target.utcQuarterHourTokenUsage = mergeQuarterRows(
target.utcQuarterHourTokenUsage,
source.utcQuarterHourTokenUsage,
["input", "output", "cacheRead", "cacheWrite", "totalTokens", "totalCost"],
);
target.dailyLatency = mergeDailyLatencyRows(target.dailyLatency, source.dailyLatency);
target.dailyModelUsage = mergeDailyModelRows(target.dailyModelUsage, source.dailyModelUsage);
target.messageCounts = mergeMessageCounts(target.messageCounts, source.messageCounts);
target.toolUsage = mergeToolUsage(target.toolUsage, source.toolUsage);
target.modelUsage = mergeModelUsage(target.modelUsage, source.modelUsage);
target.latency = mergeLatency(target.latency, source.latency);
}
function mergeDailyRows<T extends { date: string }>(
left: T[] | undefined,
right: T[] | undefined,
fields: Array<keyof T>,
): T[] | undefined {
const map = new Map<string, T>();
for (const row of [...(left ?? []), ...(right ?? [])]) {
const existing = map.get(row.date);
if (!existing) {
map.set(row.date, { ...row });
continue;
}
for (const field of fields) {
existing[field] = (((existing[field] as number | undefined) ?? 0) +
((row[field] as number | undefined) ?? 0)) as T[keyof T];
}
}
return map.size > 0
? Array.from(map.values()).toSorted((a, b) => a.date.localeCompare(b.date))
: undefined;
}
function mergeQuarterRows<T extends { date: string; quarterIndex: number }>(
left: T[] | undefined,
right: T[] | undefined,
fields: Array<keyof T>,
): T[] | undefined {
const map = new Map<string, T>();
for (const row of [...(left ?? []), ...(right ?? [])]) {
const key = `${row.date}:${row.quarterIndex}`;
const existing = map.get(key);
if (!existing) {
map.set(key, { ...row });
continue;
}
for (const field of fields) {
existing[field] = (((existing[field] as number | undefined) ?? 0) +
((row[field] as number | undefined) ?? 0)) as T[keyof T];
}
}
return map.size > 0
? Array.from(map.values()).toSorted(
(a, b) => a.date.localeCompare(b.date) || a.quarterIndex - b.quarterIndex,
)
: undefined;
}
function mergeMessageCounts(
left: SessionMessageCounts | undefined,
right: SessionMessageCounts | undefined,
): SessionMessageCounts | undefined {
if (!left && !right) {
return undefined;
}
return {
total: (left?.total ?? 0) + (right?.total ?? 0),
user: (left?.user ?? 0) + (right?.user ?? 0),
assistant: (left?.assistant ?? 0) + (right?.assistant ?? 0),
toolCalls: (left?.toolCalls ?? 0) + (right?.toolCalls ?? 0),
toolResults: (left?.toolResults ?? 0) + (right?.toolResults ?? 0),
errors: (left?.errors ?? 0) + (right?.errors ?? 0),
};
}
function mergeToolUsage(
left: SessionCostSummary["toolUsage"],
right: SessionCostSummary["toolUsage"],
): SessionCostSummary["toolUsage"] {
const map = new Map<string, number>();
for (const tool of [...(left?.tools ?? []), ...(right?.tools ?? [])]) {
map.set(tool.name, (map.get(tool.name) ?? 0) + tool.count);
}
return map.size > 0
? {
totalCalls: Array.from(map.values()).reduce((sum, count) => sum + count, 0),
uniqueTools: map.size,
tools: Array.from(map.entries())
.map(([name, count]) => ({ name, count }))
.toSorted((a, b) => b.count - a.count),
}
: undefined;
}
function mergeModelUsage(
left: SessionCostSummary["modelUsage"],
right: SessionCostSummary["modelUsage"],
): SessionCostSummary["modelUsage"] {
const map = new Map<string, SessionModelUsage>();
const mergeTotals = (target: CostUsageSummary["totals"], source: CostUsageSummary["totals"]) => {
target.input += source.input;
target.output += source.output;
target.cacheRead += source.cacheRead;
target.cacheWrite += source.cacheWrite;
target.totalTokens += source.totalTokens;
target.totalCost += source.totalCost;
target.inputCost += source.inputCost;
target.outputCost += source.outputCost;
target.cacheReadCost += source.cacheReadCost;
target.cacheWriteCost += source.cacheWriteCost;
target.missingCostEntries += source.missingCostEntries;
};
for (const entry of [...(left ?? []), ...(right ?? [])]) {
const key = `${entry.provider ?? "unknown"}::${entry.model ?? "unknown"}`;
const existing =
map.get(key) ??
({
provider: entry.provider,
model: entry.model,
count: 0,
totals: createEmptySessionCostSummary(),
} as SessionModelUsage);
existing.count += entry.count;
mergeTotals(existing.totals, entry.totals);
map.set(key, existing);
}
return map.size > 0 ? Array.from(map.values()) : undefined;
}
function mergeLatency(
left: SessionCostSummary["latency"],
right: SessionCostSummary["latency"],
): SessionCostSummary["latency"] {
if (!left && !right) {
return undefined;
}
const leftCount = left?.count ?? 0;
const rightCount = right?.count ?? 0;
const count = leftCount + rightCount;
return {
count,
avgMs:
count > 0 ? ((left?.avgMs ?? 0) * leftCount + (right?.avgMs ?? 0) * rightCount) / count : 0,
p95Ms: Math.max(left?.p95Ms ?? 0, right?.p95Ms ?? 0),
minMs: Math.min(
left?.minMs ?? Number.POSITIVE_INFINITY,
right?.minMs ?? Number.POSITIVE_INFINITY,
),
maxMs: Math.max(left?.maxMs ?? 0, right?.maxMs ?? 0),
};
}
function mergeDailyLatencyRows(
left: SessionCostSummary["dailyLatency"],
right: SessionCostSummary["dailyLatency"],
): SessionCostSummary["dailyLatency"] {
const map = new Map<string, NonNullable<SessionCostSummary["dailyLatency"]>[number]>();
for (const row of [...(left ?? []), ...(right ?? [])]) {
const existing = map.get(row.date);
if (!existing) {
map.set(row.date, { ...row });
continue;
}
const count = existing.count + row.count;
existing.avgMs =
count > 0 ? (existing.avgMs * existing.count + row.avgMs * row.count) / count : 0;
existing.count = count;
existing.p95Ms = Math.max(existing.p95Ms, row.p95Ms);
existing.minMs = Math.min(existing.minMs, row.minMs);
existing.maxMs = Math.max(existing.maxMs, row.maxMs);
}
return map.size > 0
? Array.from(map.values()).toSorted((a, b) => a.date.localeCompare(b.date))
: undefined;
}
function mergeDailyModelRows(
left: SessionCostSummary["dailyModelUsage"],
right: SessionCostSummary["dailyModelUsage"],
): SessionCostSummary["dailyModelUsage"] {
const map = new Map<string, NonNullable<SessionCostSummary["dailyModelUsage"]>[number]>();
for (const row of [...(left ?? []), ...(right ?? [])]) {
const key = `${row.date}:${row.provider ?? "unknown"}:${row.model ?? "unknown"}`;
const existing = map.get(key);
if (!existing) {
map.set(key, { ...row });
continue;
}
existing.tokens += row.tokens;
existing.cost += row.cost;
existing.count += row.count;
}
return map.size > 0
? Array.from(map.values()).toSorted((a, b) => a.date.localeCompare(b.date))
: undefined;
}
async function loadCostUsageSummaryCached(params: {
startMs: number;
endMs: number;
@@ -433,6 +795,7 @@ export const usageHandlers: GatewayRequestHandlers = {
startDate: params?.startDate,
endDate: params?.endDate,
days: params?.days,
range: params?.range,
mode: params?.mode,
utcOffset: params?.utcOffset,
});
@@ -457,28 +820,20 @@ export const usageHandlers: GatewayRequestHandlers = {
const { startMs, endMs } = parseDateRange({
startDate: p.startDate,
endDate: p.endDate,
range: p.range,
mode: p.mode,
utcOffset: p.utcOffset,
});
const limit = typeof p.limit === "number" && Number.isFinite(p.limit) ? p.limit : 50;
const includeContextWeight = p.includeContextWeight ?? false;
const specificKey = normalizeOptionalString(p.key) ?? null;
const groupingMode: UsageGroupingMode =
p.groupBy === "family" || p.includeHistorical === true ? "family" : "instance";
// Load session store for named sessions
const { storePath, store } = loadCombinedSessionStoreForGateway(config);
const now = Date.now();
// Merge discovered sessions with store entries
type MergedEntry = {
key: string;
sessionId: string;
sessionFile: string;
label?: string;
updatedAt: number;
storeEntry?: SessionEntry;
firstUserMessage?: string;
};
const mergedEntries: MergedEntry[] = [];
// Optimization: If a specific key is requested, skip full directory scan
@@ -525,13 +880,17 @@ export const usageHandlers: GatewayRequestHandlers = {
try {
const stats = fs.statSync(sessionFile);
if (stats.isFile()) {
mergedEntries.push({
key: resolvedStoreKey,
sessionId,
sessionFile,
label: storeEntry?.label,
updatedAt: storeEntry?.updatedAt ?? stats.mtimeMs,
storeEntry,
maybeMergeFamilyEntry({
mergedEntries,
groupingMode,
base: {
key: resolvedStoreKey,
sessionId,
sessionFile,
label: storeEntry?.label,
updatedAt: storeEntry?.updatedAt ?? stats.mtimeMs,
storeEntry,
},
});
}
} catch {
@@ -548,20 +907,35 @@ export const usageHandlers: GatewayRequestHandlers = {
// Build a map of sessionId -> store entry for quick lookup
const storeBySessionId = buildStoreBySessionId(store);
const storeFamilySessionIds = new Set<string>();
if (groupingMode === "family") {
for (const entry of Object.values(store)) {
for (const sessionId of entry?.usageFamilySessionIds ?? []) {
storeFamilySessionIds.add(sessionId);
}
}
}
for (const discovered of discoveredSessions) {
const storeMatch = storeBySessionId.get(discovered.sessionId);
if (storeMatch) {
// Named session from store
mergedEntries.push({
key: storeMatch.key,
sessionId: discovered.sessionId,
sessionFile: discovered.sessionFile,
label: storeMatch.entry.label,
updatedAt: storeMatch.entry.updatedAt ?? discovered.mtime,
storeEntry: storeMatch.entry,
maybeMergeFamilyEntry({
mergedEntries,
groupingMode,
base: {
key: storeMatch.key,
sessionId: discovered.sessionId,
sessionFile: discovered.sessionFile,
label: storeMatch.entry.label,
updatedAt: storeMatch.entry.updatedAt ?? discovered.mtime,
storeEntry: storeMatch.entry,
},
});
} else {
if (groupingMode === "family" && storeFamilySessionIds.has(discovered.sessionId)) {
continue;
}
// Unnamed session - use session ID as key, no label
mergedEntries.push({
// Keep agentId in the key so the dashboard can attribute sessions and later fetch logs.
@@ -570,6 +944,7 @@ export const usageHandlers: GatewayRequestHandlers = {
sessionFile: discovered.sessionFile,
label: undefined, // No label for unnamed sessions
updatedAt: discovered.mtime,
scope: "instance",
});
}
}
@@ -666,18 +1041,41 @@ export const usageHandlers: GatewayRequestHandlers = {
for (const merged of limitedEntries) {
const agentId = parseAgentSessionKey(merged.key)?.agentId;
const cachedUsage = await loadSessionCostSummaryFromCache({
sessionId: merged.sessionId,
sessionEntry: merged.storeEntry,
sessionFile: merged.sessionFile,
config,
agentId,
startMs,
endMs,
refreshMode: "sync-when-empty",
});
cacheStatus = mergeUsageCacheStatus(cacheStatus, cachedUsage.cacheStatus);
const usage = cachedUsage.summary;
let usage: SessionCostSummary | null = null;
const includedSessionIds = merged.includedSessionIds ?? [merged.sessionId];
for (const includedSessionId of includedSessionIds) {
const isCurrentSession = includedSessionId === merged.sessionId;
const includedSessionFile = isCurrentSession
? merged.sessionFile
: resolveExistingUsageSessionFile({
sessionId: includedSessionId,
agentId,
});
if (!includedSessionFile) {
continue;
}
const cachedUsage = await loadSessionCostSummaryFromCache({
sessionId: includedSessionId,
sessionEntry: isCurrentSession ? merged.storeEntry : undefined,
sessionFile: includedSessionFile,
config,
agentId,
startMs,
endMs,
refreshMode: "sync-when-empty",
});
cacheStatus = mergeUsageCacheStatus(cacheStatus, cachedUsage.cacheStatus);
const includedUsage = cachedUsage.summary;
if (!includedUsage) {
continue;
}
if (!usage) {
usage = createEmptySessionCostSummary();
usage.sessionId = merged.sessionId;
usage.sessionFile = merged.sessionFile;
}
mergeSessionUsageInto(usage, includedUsage);
}
if (usage) {
aggregateTotals.input += usage.input;
@@ -815,6 +1213,11 @@ export const usageHandlers: GatewayRequestHandlers = {
key: merged.key,
label: merged.label,
sessionId: merged.sessionId,
scope: merged.scope ?? "instance",
sessionFamilyKey: merged.sessionFamilyKey,
currentSessionId: merged.currentSessionId,
includedSessionIds: merged.includedSessionIds,
historicalInstanceCount: merged.includedSessionIds?.length,
updatedAt: merged.updatedAt,
agentId,
channel,

View File

@@ -48,6 +48,8 @@ const SESSION_ENTRY_RESERVED_SLOT_KEY_LIST = [
"execAsk",
"execNode",
"responseUsage",
"usageFamilyKey",
"usageFamilySessionIds",
"providerOverride",
"modelOverride",
"agentRuntimeOverride",

View File

@@ -14,6 +14,11 @@ export type SessionUsageEntry = {
key: string;
label?: string;
sessionId?: string;
scope?: "instance" | "family";
sessionFamilyKey?: string;
currentSessionId?: string;
includedSessionIds?: string[];
historicalInstanceCount?: number;
updatedAt?: number;
agentId?: string;
channel?: string;

View File

@@ -1,11 +1,19 @@
{
"fallbackKeys": [],
"generatedAt": "2026-05-06T03:20:52.601Z",
"fallbackKeys": [
"usage.presets.last1y",
"usage.presets.last90d",
"usage.scope.family",
"usage.scope.familyHint",
"usage.scope.familyIncluded",
"usage.scope.instance",
"usage.scope.instanceHint"
],
"generatedAt": "2026-05-08T03:32:07.878Z",
"locale": "ar",
"model": "gpt-5.5",
"provider": "openai",
"sourceHash": "c97d50965a8485bb290aa7f158bae5dbadf3642e71bf4712207555f0abea23c2",
"totalKeys": 1017,
"translatedKeys": 1017,
"sourceHash": "b4c8279f087e4a589dd3a6e59c9e06f2686df58f904a678c5a773a0df05fe563",
"totalKeys": 1025,
"translatedKeys": 1018,
"workflow": 1
}

View File

@@ -759,6 +759,7 @@
{"cache_key":"bcad99fe4034d6c5a8aa0c5334fcf41e1c32cd62ed7d06e924c8f4be2876a867","model":"gpt-5.5","provider":"openai","segment_id":"cron.jobs.schedule","source_path":"ui/src/i18n/locales/ar.ts","src_lang":"en","text":"Schedule","text_hash":"f4830a1dae2980447c716bd4b5779b7013575ef09f70ef4731457218792487b3","tgt_lang":"ar","translated":"الجدول","updated_at":"2026-04-29T17:39:51.444Z"}
{"cache_key":"bce6fdf39225c3da1c114665ca66ca3b9d01691ba337515bf065e7b19cc0d8f4","model":"gpt-5.5","provider":"openai","segment_id":"overview.snapshot.title","source_path":"ui/src/i18n/locales/ar.ts","src_lang":"en","text":"Snapshot","text_hash":"6ad27bd4ec33b079208334dfea86ff96900f95ca640dda1d2638d694d077668b","tgt_lang":"ar","translated":"لقطة","updated_at":"2026-04-29T17:37:46.915Z"}
{"cache_key":"be686086f359acd3820ddcfe5beb4c83696f2f9c58987a6aa6cc27e5dcdffe9e","model":"gpt-5.5","provider":"openai","segment_id":"agents.files.words","source_path":"ui/src/i18n/locales/ar.ts","src_lang":"en","text":"{count} words","text_hash":"caab2939348211270cf707c28b881251d4cf42057fc19cfee56211dbd7b28eb1","tgt_lang":"ar","translated":"{count} كلمة","updated_at":"2026-04-29T19:26:31.017Z"}
{"cache_key":"bee1c741d7b66a643851c3b28ce4dc2e6f17500ea32ef924a2a21c707a928911","model":"gpt-5.5","provider":"openai","segment_id":"usage.presets.all","source_path":"ui/src/i18n/locales/ar.ts","src_lang":"en","text":"All","text_hash":"a52ace420f2175d08b1577a1bea5445e36801229c074ef9ed6c55a73401fd9c2","tgt_lang":"ar","translated":"الكل","updated_at":"2026-04-29T17:39:51.444Z"}
{"cache_key":"bf4c64621efe6fd0f8523f2654fb8d2059e6cb95bf956c6553342736a0aafa0d","model":"gpt-5.5","provider":"openai","segment_id":"overview.connection.insecureHttpDocsLink","source_path":"ui/src/i18n/locales/ar.ts","src_lang":"en","text":"Docs: Insecure HTTP","text_hash":"203c0a5d2a6d0e5f4fb9aece80770f6b56642c5731997b9f9afcda31936a63f0","tgt_lang":"ar","translated":"المستندات: HTTP غير الآمن","updated_at":"2026-04-29T17:38:02.501Z"}
{"cache_key":"bfa5ba2cc8219e5e4645f31e5205136e58415f71ec620f0e2eed513fc24501ff","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.sourceFilters","source_path":"ui/src/i18n/locales/ar.ts","src_lang":"en","text":"Session source filters","text_hash":"4a8b410fc82e910fb1b8c579ad3286a4987b7c97d4ef1f790bf771410652b341","tgt_lang":"ar","translated":"عوامل تصفية مصدر الجلسة","updated_at":"2026-05-04T07:16:37.779Z"}
{"cache_key":"bfc084de7139cd7774f150b6450fb2917cedeee0bb0e939658cfd10ba3770465","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.noSummary","source_path":"ui/src/i18n/locales/ar.ts","src_lang":"en","text":"No summary captured.","text_hash":"790bca2371e3208a263a19ab9fb07c2625ccc77728f3c5604db32363e6060857","tgt_lang":"ar","translated":"لم يتم التقاط أي ملخص.","updated_at":"2026-04-29T20:14:44.891Z"}

View File

@@ -1,11 +1,19 @@
{
"fallbackKeys": [],
"generatedAt": "2026-05-06T03:19:00.390Z",
"fallbackKeys": [
"usage.presets.last1y",
"usage.presets.last90d",
"usage.scope.family",
"usage.scope.familyHint",
"usage.scope.familyIncluded",
"usage.scope.instance",
"usage.scope.instanceHint"
],
"generatedAt": "2026-05-08T03:32:06.270Z",
"locale": "de",
"model": "gpt-5.5",
"provider": "openai",
"sourceHash": "c97d50965a8485bb290aa7f158bae5dbadf3642e71bf4712207555f0abea23c2",
"totalKeys": 1017,
"translatedKeys": 1017,
"sourceHash": "b4c8279f087e4a589dd3a6e59c9e06f2686df58f904a678c5a773a0df05fe563",
"totalKeys": 1025,
"translatedKeys": 1018,
"workflow": 1
}

View File

@@ -574,6 +574,7 @@
{"cache_key":"b22a094dc34da1093363f01001b90fc03d7c9b062fd6fe06cf2035855d117e8c","model":"gpt-5.4","provider":"openai","segment_id":"cron.form.session","source_path":"ui/src/i18n/locales/de.ts","src_lang":"en","text":"Session","text_hash":"6959b4159575d8dd76d9f3bbe2c6437904f861e7860c35abd18deffb1c3425a0","tgt_lang":"de","translated":"Sitzung","updated_at":"2026-04-05T17:12:43.392Z"}
{"cache_key":"b25b8e413f0fee41162a3da52c2d3540f7a8e3920e39b120efbb1217563a83a1","model":"gpt-5.4","provider":"openai","segment_id":"nodes.binding.formModeHint","source_path":"ui/src/i18n/locales/de.ts","src_lang":"en","text":"Switch the Config tab to Form mode to edit bindings here.","text_hash":"af8526a5a7a925ecaa127907fc4e377373054036b27f99251767b5e4a2a135f8","tgt_lang":"de","translated":"Wechseln Sie im Tab „Konfiguration“ in den Formularmodus, um Bindungen hier zu bearbeiten.","updated_at":"2026-04-06T02:47:46.753Z"}
{"cache_key":"b33eacdfc6c3cf61ff5bfab604b020263740d472583401c59b06089f820015a1","model":"gpt-5.4","provider":"openai","segment_id":"usage.filters.timeZoneLocal","source_path":"ui/src/i18n/locales/de.ts","src_lang":"en","text":"Local","text_hash":"8c31e6e7223097e2e4847773c47a4efab6aaf79deeecc92a7759891c74976dde","tgt_lang":"de","translated":"Lokal","updated_at":"2026-04-05T17:11:30.927Z"}
{"cache_key":"b3e574fe6ea7df786a0aaef5077bbd4de1116de21ad7134bc6479cd3208cea06","model":"gpt-5.4","provider":"openai","segment_id":"usage.presets.all","source_path":"ui/src/i18n/locales/de.ts","src_lang":"en","text":"All","text_hash":"a52ace420f2175d08b1577a1bea5445e36801229c074ef9ed6c55a73401fd9c2","tgt_lang":"de","translated":"Alle","updated_at":"2026-04-05T17:11:30.927Z"}
{"cache_key":"b4011e4e74942c3e315c924381314b48e7f93df43f5d15d43793245032bff5ff","model":"gpt-5.4","provider":"openai","segment_id":"cron.form.every","source_path":"ui/src/i18n/locales/de.ts","src_lang":"en","text":"Every","text_hash":"9b8617fdfbba933d9a0f87450dfd77b7c34fcb08ae284029523e0ca20e0811c9","tgt_lang":"de","translated":"Alle","updated_at":"2026-04-05T17:12:39.118Z"}
{"cache_key":"b4730d70a7f68b2c5cf864ea30894095c439edc4a0b68c8f9504fe06c9a1932b","model":"gpt-5.4","provider":"openai","segment_id":"usage.overview.sessionsInRange","source_path":"ui/src/i18n/locales/de.ts","src_lang":"en","text":"of {count} in range","text_hash":"6e63cea82a473651b00fb46a523cb60e7aeb7a937012c33f46313e28fc685a44","tgt_lang":"de","translated":"von {count} im Bereich","updated_at":"2026-04-05T17:11:43.279Z"}
{"cache_key":"b4c02ded1cdfc059b762abc3688ab77ffda749bc138ebcf984b6ffb383252e68","model":"gpt-5.5","provider":"openai","segment_id":"cron.quickCreate.namePlaceholder","source_path":"ui/src/i18n/locales/de.ts","src_lang":"en","text":"e.g., Morning inbox check","text_hash":"149ef2da53b9dbcd4cb688e9d86fdb3780f50a88886ae841004fa3993ccd4e9f","tgt_lang":"de","translated":"z. B. Morgendliche Postfachprüfung","updated_at":"2026-04-29T20:12:27.640Z"}

View File

@@ -1,11 +1,19 @@
{
"fallbackKeys": [],
"generatedAt": "2026-05-06T03:19:53.011Z",
"fallbackKeys": [
"usage.presets.last1y",
"usage.presets.last90d",
"usage.scope.family",
"usage.scope.familyHint",
"usage.scope.familyIncluded",
"usage.scope.instance",
"usage.scope.instanceHint"
],
"generatedAt": "2026-05-08T03:32:06.593Z",
"locale": "es",
"model": "gpt-5.5",
"provider": "openai",
"sourceHash": "c97d50965a8485bb290aa7f158bae5dbadf3642e71bf4712207555f0abea23c2",
"totalKeys": 1017,
"translatedKeys": 1017,
"sourceHash": "b4c8279f087e4a589dd3a6e59c9e06f2686df58f904a678c5a773a0df05fe563",
"totalKeys": 1025,
"translatedKeys": 1018,
"workflow": 1
}

View File

@@ -221,6 +221,7 @@
{"cache_key":"5ad307e83d6ea58fcf58b397449ab63f16d063bb1e3cc336d46f7d758c11a7ed","model":"gpt-5.4","provider":"openai","segment_id":"usage.details.noUsageData","source_path":"ui/src/i18n/locales/es.ts","src_lang":"en","text":"No usage data for this session.","text_hash":"0d7e8a36956a3962062b10bbb0b251514111f2bdc4ec943693f48f768043c6ca","tgt_lang":"es","translated":"No hay datos de uso para esta sesión.","updated_at":"2026-04-05T17:12:34.011Z"}
{"cache_key":"5b5bb87c9c2964b18034b56801a92684aa32e2d5165da975b96b615df78f8941","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.unknownTooltip","source_path":"ui/src/i18n/locales/es.ts","src_lang":"en","text":"Include unknown sessions.","text_hash":"d7841049eac695e8aa4e318ea09dc4ae7afe6caea896a02ecde5b4c306801f08","tgt_lang":"es","translated":"Incluir sesiones desconocidas.","updated_at":"2026-05-04T07:15:25.153Z"}
{"cache_key":"5c7dbfc5f2e22acdba05c928c06010dd9c2007bb00c9abef60c470734334c8a4","model":"gpt-5.4","provider":"openai","segment_id":"usage.filters.clearAll","source_path":"ui/src/i18n/locales/es.ts","src_lang":"en","text":"Clear All","text_hash":"ddceb7adfdb8816e4747bc48a2221702e830340e5596a701dc0993766eba5e60","tgt_lang":"es","translated":"Borrar todo","updated_at":"2026-04-05T17:12:12.392Z"}
{"cache_key":"5ce271d9ff5c3c6483a87578659516379f53577ad6c02462b12f4207b5fb1170","model":"gpt-5.4","provider":"openai","segment_id":"usage.presets.all","source_path":"ui/src/i18n/locales/es.ts","src_lang":"en","text":"All","text_hash":"a52ace420f2175d08b1577a1bea5445e36801229c074ef9ed6c55a73401fd9c2","tgt_lang":"es","translated":"Todas","updated_at":"2026-04-05T17:12:30.161Z"}
{"cache_key":"5d09736e7244a7d198eadc10aba12f9bbb2a417445e8682a3500332891f7a4f2","model":"gpt-5.4","provider":"openai","segment_id":"usage.daily.byType","source_path":"ui/src/i18n/locales/es.ts","src_lang":"en","text":"By Type","text_hash":"26901eeda3b27dae03e02ed92d2af1757fefe9929a2cbaf8bc17e193256d1ba8","tgt_lang":"es","translated":"Por tipo","updated_at":"2026-04-05T17:12:17.081Z"}
{"cache_key":"5d403f319dd1663ec2e2163ed5f30ec8a2c52364c517ac5b9da9eea937e15db2","model":"gpt-5.4","provider":"openai","segment_id":"common.lastInbound","source_path":"ui/src/i18n/locales/es.ts","src_lang":"en","text":"Last inbound","text_hash":"2df9c4ccfa36d15b18ab6a0d9268cc247a28626bda9566d4aecc2c3285f9c5b6","tgt_lang":"es","translated":"Última entrada","updated_at":"2026-04-06T02:48:45.038Z"}
{"cache_key":"5da7ec68c22c0cb54309cd4cd9abf16168dbb53456003f94a6a99cfa27b3aa83","model":"gpt-5.4","provider":"openai","segment_id":"usage.details.reset","source_path":"ui/src/i18n/locales/es.ts","src_lang":"en","text":"Reset","text_hash":"daee7606b339f3c339076fe2c9f372a3ff40c8ee896005d829c7481b64ca5303","tgt_lang":"es","translated":"Restablecer","updated_at":"2026-04-05T17:12:34.012Z"}

View File

@@ -1,11 +1,19 @@
{
"fallbackKeys": [],
"generatedAt": "2026-05-06T03:22:51.765Z",
"fallbackKeys": [
"usage.presets.last1y",
"usage.presets.last90d",
"usage.scope.family",
"usage.scope.familyHint",
"usage.scope.familyIncluded",
"usage.scope.instance",
"usage.scope.instanceHint"
],
"generatedAt": "2026-05-08T03:32:10.766Z",
"locale": "fa",
"model": "gpt-5.5",
"provider": "openai",
"sourceHash": "c97d50965a8485bb290aa7f158bae5dbadf3642e71bf4712207555f0abea23c2",
"totalKeys": 1017,
"translatedKeys": 1017,
"sourceHash": "b4c8279f087e4a589dd3a6e59c9e06f2686df58f904a678c5a773a0df05fe563",
"totalKeys": 1025,
"translatedKeys": 1018,
"workflow": 1
}

View File

@@ -532,6 +532,7 @@
{"cache_key":"8632cde0f8c8ee9d2eb7f0aa0988d45e3eb9b0c9beaf3158c677e100201d4644","model":"gpt-5.5","provider":"openai","segment_id":"tabs.channels","source_path":"ui/src/i18n/locales/fa.ts","src_lang":"en","text":"Channels","text_hash":"4c8906cf76f5740ab8792aef9f0033fe21a92045e90b357816064e9f6860a03e","tgt_lang":"fa","translated":"کانال‌ها","updated_at":"2026-04-29T17:41:42.715Z"}
{"cache_key":"868e7b794478ac2de91dcd2167277d6a2d28dbc7719f9c0b42de277ca8129a7f","model":"gpt-5.5","provider":"openai","segment_id":"dreaming.diary.waitingHint","source_path":"ui/src/i18n/locales/fa.ts","src_lang":"en","text":"Narrative entries will appear after the next dreaming cycle.","text_hash":"c183c67ee0ad3800a518c6eac25bb58b19d4c9f944a961f2c1e371f581a465cd","tgt_lang":"fa","translated":"ورودی‌های روایی پس از چرخه رؤیاپردازی بعدی ظاهر می‌شوند.","updated_at":"2026-04-29T17:43:08.525Z"}
{"cache_key":"8693f79298cfb7c451dc7216548602a49ace3a79175d6c89be4ca1bf95aea269","model":"gpt-5.5","provider":"openai","segment_id":"agents.context.identityName","source_path":"ui/src/i18n/locales/fa.ts","src_lang":"en","text":"Identity Name","text_hash":"d84785a85db54b51e0410c02d7b691f92d08ecf7677378cf43ad82ae4e8595f3","tgt_lang":"fa","translated":"نام هویت","updated_at":"2026-04-29T19:28:44.177Z"}
{"cache_key":"86b6e60499e1cad668c08c3d3249285be6bd150dcc938dcf27b6357563b8cd83","model":"gpt-5.5","provider":"openai","segment_id":"usage.presets.all","source_path":"ui/src/i18n/locales/fa.ts","src_lang":"en","text":"All","text_hash":"a52ace420f2175d08b1577a1bea5445e36801229c074ef9ed6c55a73401fd9c2","tgt_lang":"fa","translated":"همه","updated_at":"2026-04-29T17:44:01.097Z"}
{"cache_key":"86c1436119bfeb3b8fd4675fd6edf42e539d192d1356ec1cb65e1ab2125f9e82","model":"gpt-5.5","provider":"openai","segment_id":"usage.mosaic.eightPm","source_path":"ui/src/i18n/locales/fa.ts","src_lang":"en","text":"8pm","text_hash":"232df857db5e72521b783719e674c41bce48738283c637b44ed2a80fa81ec56c","tgt_lang":"fa","translated":"۸ شب","updated_at":"2026-04-29T17:44:23.626Z"}
{"cache_key":"86da2f5a9e1ffb85005169ba5886c7f7a3e63fb20c90e22d2c04aa52aa51128e","model":"gpt-5.5","provider":"openai","segment_id":"tabs.overview","source_path":"ui/src/i18n/locales/fa.ts","src_lang":"en","text":"Overview","text_hash":"d4b1ea5708dd532930a85188b45aff6f0a3ed458500c7577e0127a538eb0d100","tgt_lang":"fa","translated":"نمای کلی","updated_at":"2026-04-29T17:41:42.715Z"}
{"cache_key":"86f3ca95a83fc25a5c930d63414a0781fd441c6f1b1dfba8b7b67ba1a77d7a7b","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.selectAllOnPage","source_path":"ui/src/i18n/locales/fa.ts","src_lang":"en","text":"Select all on page","text_hash":"f47f99dde01bd07bd800879220c76522d006ac17a7fdd02ac92191f72b419a7f","tgt_lang":"fa","translated":"انتخاب همه در صفحه","updated_at":"2026-04-29T20:17:19.091Z"}

View File

@@ -1,11 +1,19 @@
{
"fallbackKeys": [],
"generatedAt": "2026-05-06T03:20:27.102Z",
"fallbackKeys": [
"usage.presets.last1y",
"usage.presets.last90d",
"usage.scope.family",
"usage.scope.familyHint",
"usage.scope.familyIncluded",
"usage.scope.instance",
"usage.scope.instanceHint"
],
"generatedAt": "2026-05-08T03:32:07.558Z",
"locale": "fr",
"model": "gpt-5.5",
"provider": "openai",
"sourceHash": "c97d50965a8485bb290aa7f158bae5dbadf3642e71bf4712207555f0abea23c2",
"totalKeys": 1017,
"translatedKeys": 1017,
"sourceHash": "b4c8279f087e4a589dd3a6e59c9e06f2686df58f904a678c5a773a0df05fe563",
"totalKeys": 1025,
"translatedKeys": 1018,
"workflow": 1
}

View File

@@ -489,6 +489,7 @@
{"cache_key":"8e27df2e90c99eef14dfac92463adedfdb67a65c146db360a5ab5f2d2c937726","model":"gpt-5.4","provider":"openai","segment_id":"common.saving","source_path":"ui/src/i18n/locales/fr.ts","src_lang":"en","text":"Saving…","text_hash":"23e39291d6135814ed7c936e278974544b0df5fbf0eb0427b6700979b7472a93","tgt_lang":"fr","translated":"Enregistrement…","updated_at":"2026-04-06T02:49:37.962Z"}
{"cache_key":"8e47b9219ba2d7df743a740e5ebdd92b4328ece1cdbf80e9d490f5bc2e6f1f36","model":"gpt-5.4","provider":"openai","segment_id":"usage.filters.channel","source_path":"ui/src/i18n/locales/fr.ts","src_lang":"en","text":"Channel","text_hash":"ce4683e7013a18cdf3d224bfcb4e9594ea8f559e946a837c633defe7d3c32172","tgt_lang":"fr","translated":"Canal","updated_at":"2026-04-05T17:14:12.481Z"}
{"cache_key":"8eaa0b87f196e2d776942c3a993ab9fdf7b20858f1480996ce429135e05efa90","model":"gpt-5.4","provider":"openai","segment_id":"usage.overview.topProviders","source_path":"ui/src/i18n/locales/fr.ts","src_lang":"en","text":"Top Providers","text_hash":"2e8b08a8d152483960de5a1090251cb17ce0a20e51d5c291a6cf2cccec2b0079","tgt_lang":"fr","translated":"Principaux providers","updated_at":"2026-04-05T17:14:21.908Z"}
{"cache_key":"8f6107ccb11a5057abeab411866e088bed41132f30a1a7f870327be7051ca320","model":"gpt-5.4","provider":"openai","segment_id":"usage.presets.all","source_path":"ui/src/i18n/locales/fr.ts","src_lang":"en","text":"All","text_hash":"a52ace420f2175d08b1577a1bea5445e36801229c074ef9ed6c55a73401fd9c2","tgt_lang":"fr","translated":"Tous","updated_at":"2026-04-05T17:14:12.481Z"}
{"cache_key":"8f6485261bf9ce203441787622d94e50779a33c6fbd3eca671b68fdf9a7c75ff","model":"gpt-5.4","provider":"openai","segment_id":"usage.filters.clear","source_path":"ui/src/i18n/locales/fr.ts","src_lang":"en","text":"Clear","text_hash":"83b12c2216efb4fdc924e1deb5182e905e4926ed0c1c324d467107f46d5a26a9","tgt_lang":"fr","translated":"Effacer","updated_at":"2026-04-05T17:14:09.807Z"}
{"cache_key":"8f92709f288be12401f02cb7c79dc35f9629631265f3a5d30a00ed82895a16ab","model":"gpt-5.4","provider":"openai","segment_id":"dreaming.trace.emptyShortTerm","source_path":"ui/src/i18n/locales/fr.ts","src_lang":"en","text":"No active short-term items.","text_hash":"e3a71c5ac02b76384ed603efc99062bf70b21092fd094fb3a7c0b3e2647ee757","tgt_lang":"fr","translated":"Aucun élément à court terme actif.","updated_at":"2026-04-08T18:37:54.810Z"}
{"cache_key":"8fc5f2660e6371a1d6554ac583d2c1471088f83475abb699c78469d674d08317","model":"gpt-5.4","provider":"openai","segment_id":"cron.form.bestEffortHelp","source_path":"ui/src/i18n/locales/fr.ts","src_lang":"en","text":"Do not fail the job if delivery itself fails.","text_hash":"8918ef73561c96327b9a787e29004f468e5641b126fe2d28991df4020e5b7859","tgt_lang":"fr","translated":"Ne faites pas échouer la tâche si la distribution elle-même échoue.","updated_at":"2026-04-05T17:15:59.853Z"}

View File

@@ -1,11 +1,19 @@
{
"fallbackKeys": [],
"generatedAt": "2026-05-06T03:21:45.382Z",
"fallbackKeys": [
"usage.presets.last1y",
"usage.presets.last90d",
"usage.scope.family",
"usage.scope.familyHint",
"usage.scope.familyIncluded",
"usage.scope.instance",
"usage.scope.instanceHint"
],
"generatedAt": "2026-05-08T03:32:09.164Z",
"locale": "id",
"model": "gpt-5.5",
"provider": "openai",
"sourceHash": "c97d50965a8485bb290aa7f158bae5dbadf3642e71bf4712207555f0abea23c2",
"totalKeys": 1017,
"translatedKeys": 1017,
"sourceHash": "b4c8279f087e4a589dd3a6e59c9e06f2686df58f904a678c5a773a0df05fe563",
"totalKeys": 1025,
"translatedKeys": 1018,
"workflow": 1
}

View File

@@ -390,6 +390,7 @@
{"cache_key":"744e23c746c549b4b4ddfb7da05b8c9010ec81ff4affa4449f238d3e16dfde75","model":"gpt-5.4","provider":"openai","segment_id":"common.probeFailed","source_path":"ui/src/i18n/locales/id.ts","src_lang":"en","text":"Probe failed","text_hash":"450e4a86d32cc99604a33165c0f71dbd9b3d353a82ef73b931667da22c925abc","tgt_lang":"id","translated":"Probe gagal","updated_at":"2026-04-06T02:50:40.390Z"}
{"cache_key":"74645d213b0e0b8ea8d970b69ba25dc6114f29850dc960d53639477f046cbf22","model":"gpt-5.5","provider":"openai","segment_id":"lazyView.errorSubtitle","source_path":"ui/src/i18n/locales/id.ts","src_lang":"en","text":"Reload the page to load the latest Control UI bundle, or retry if the network request failed.","text_hash":"7070c57acbe9a8991e3d1c91cd713d34b1351c166f92a9c7eeeb07a6e45e7b42","tgt_lang":"id","translated":"Muat ulang halaman untuk memuat bundel Control UI terbaru, atau coba lagi jika permintaan jaringan gagal.","updated_at":"2026-04-27T12:12:51.573Z"}
{"cache_key":"74927f2b3d550d5de300009c2c8589cb28b1f415035236627ecd0129e94e5dfb","model":"gpt-5.4","provider":"openai","segment_id":"dreaming.advanced.updatedPrefix","source_path":"ui/src/i18n/locales/id.ts","src_lang":"en","text":"updated","text_hash":"27eb5e51506c911f6fc4bb345c0d9db6f60415fceab7c18e1e9b862637415777","tgt_lang":"id","translated":"diperbarui","updated_at":"2026-04-10T07:59:45.707Z"}
{"cache_key":"74effe71fe002e8879798633d273cded18d0ae49ec7ee7ceace246f79ca1a35a","model":"gpt-5.4","provider":"openai","segment_id":"usage.presets.all","source_path":"ui/src/i18n/locales/id.ts","src_lang":"en","text":"All","text_hash":"a52ace420f2175d08b1577a1bea5445e36801229c074ef9ed6c55a73401fd9c2","tgt_lang":"id","translated":"Semua","updated_at":"2026-04-05T17:15:40.941Z"}
{"cache_key":"759f09127f3d180262469fa11c7cffe09bda3fe90c12067189927b4fe1044fa2","model":"gpt-5.4","provider":"openai","segment_id":"nav.chat","source_path":"ui/src/i18n/locales/id.ts","src_lang":"en","text":"Chat","text_hash":"460b3a7da007b7af9d35bca54181dc91382263b2bf133ca214871ca1fed1fc1c","tgt_lang":"id","translated":"Chat","updated_at":"2026-04-06T03:00:14.591Z"}
{"cache_key":"76ecd2f03ffcf6b2ccfbc2bb383d921477a6e12240dc27c1b5e6500a1362673a","model":"gpt-5.4","provider":"openai","segment_id":"common.refreshing","source_path":"ui/src/i18n/locales/id.ts","src_lang":"en","text":"Refreshing…","text_hash":"1c0def7be0607b966b89e4974da38090472d8ada625f5b4c89f25b09d39683bd","tgt_lang":"id","translated":"Menyegarkan…","updated_at":"2026-04-06T02:50:37.350Z"}
{"cache_key":"77189098751c86b0995cc047ce795f7d0389a6200f59e144a0e6609a426595cb","model":"gpt-5.4","provider":"openai","segment_id":"cron.runs.subtitleAll","source_path":"ui/src/i18n/locales/id.ts","src_lang":"en","text":"Latest runs across all jobs.","text_hash":"518357fee0ecb18cbbd2f1d29ea0fdda418f839ce47a3a0c0613aa9f92eedd89","tgt_lang":"id","translated":"Proses terbaru di semua tugas.","updated_at":"2026-04-05T17:15:58.217Z"}

View File

@@ -1,11 +1,19 @@
{
"fallbackKeys": [],
"generatedAt": "2026-05-06T03:20:54.698Z",
"fallbackKeys": [
"usage.presets.last1y",
"usage.presets.last90d",
"usage.scope.family",
"usage.scope.familyHint",
"usage.scope.familyIncluded",
"usage.scope.instance",
"usage.scope.instanceHint"
],
"generatedAt": "2026-05-08T03:32:08.202Z",
"locale": "it",
"model": "gpt-5.5",
"provider": "openai",
"sourceHash": "c97d50965a8485bb290aa7f158bae5dbadf3642e71bf4712207555f0abea23c2",
"totalKeys": 1017,
"translatedKeys": 1017,
"sourceHash": "b4c8279f087e4a589dd3a6e59c9e06f2686df58f904a678c5a773a0df05fe563",
"totalKeys": 1025,
"translatedKeys": 1018,
"workflow": 1
}

View File

@@ -596,6 +596,7 @@
{"cache_key":"99d2fd1de51b0fc28de69b204730677eb9322dca9ac192dcfca1b7561f575b2c","model":"gpt-5.5","provider":"openai","segment_id":"subtitles.nodes","source_path":"ui/src/i18n/locales/it.ts","src_lang":"en","text":"Paired devices and commands.","text_hash":"ba01dcb6cd16e3bde83b9bbeeda4a6bf8031dc599a60d41ee6c8cba63c847d17","tgt_lang":"it","translated":"Dispositivi associati e comandi.","updated_at":"2026-04-29T17:37:35.636Z"}
{"cache_key":"9a18f69f6a9a36210f9ccd782d967409a9754393b4fdd53be9215303b49dc510","model":"gpt-5.5","provider":"openai","segment_id":"overview.cards.modelAuthAttentionExpiredTitle","source_path":"ui/src/i18n/locales/it.ts","src_lang":"en","text":"Model auth expired","text_hash":"0c37d888df561b1ff2a86a41b7297f5935431ea0c56d3c983942912387e496ad","tgt_lang":"it","translated":"Autenticazione modello scaduta","updated_at":"2026-04-29T17:38:00.538Z"}
{"cache_key":"9a22e2cfb660b56bdeba7a408ef8d18cd953a451cc01b3e62c1c9b3bde53f638","model":"gpt-5.5","provider":"openai","segment_id":"usage.overview.tokensPerMinute","source_path":"ui/src/i18n/locales/it.ts","src_lang":"en","text":"tok/min","text_hash":"313de81ab59056211afd431da067fe437d905d9f29f51d64b016222a777c9526","tgt_lang":"it","translated":"tok/min","updated_at":"2026-04-29T17:38:44.414Z"}
{"cache_key":"9a5376d13c2e42ca80ab6b54da34061780821552d83eed8cf98befe3e6f9688f","model":"gpt-5.5","provider":"openai","segment_id":"usage.presets.all","source_path":"ui/src/i18n/locales/it.ts","src_lang":"en","text":"All","text_hash":"a52ace420f2175d08b1577a1bea5445e36801229c074ef9ed6c55a73401fd9c2","tgt_lang":"it","translated":"Tutte","updated_at":"2026-04-29T17:38:48.692Z"}
{"cache_key":"9b28d95c15eea2af8eda7e448d9b19993818b21a515a6a5894fe6d8736d7b726","model":"gpt-5.5","provider":"openai","segment_id":"usage.overview.sessionsHint","source_path":"ui/src/i18n/locales/it.ts","src_lang":"en","text":"Distinct sessions in the range.","text_hash":"03ac814eb939f3f67105d4862c3c3b47a36dc5906b2fa1fbf50c8e2ff2ec1255","tgt_lang":"it","translated":"Sessioni distinte nell'intervallo.","updated_at":"2026-04-29T17:38:44.414Z"}
{"cache_key":"9b73ac5571fe31f85f6f0e43eafe6172695f351f7603ba18a3f8fdef4e22258a","model":"gpt-5.5","provider":"openai","segment_id":"cron.quickCreate.schedules.once.description","source_path":"ui/src/i18n/locales/it.ts","src_lang":"en","text":"One-time, delete after run","text_hash":"19694753e141db752658d22dfb4c494a4f5452640d87b6cbcbb64bd665a13cd2","tgt_lang":"it","translated":"Una tantum, elimina dopo l'esecuzione","updated_at":"2026-04-29T20:14:51.694Z"}
{"cache_key":"9b7a81f008c51d0cc93435f3b25384d10ed1406b9b5e9a95b495a5114ce338e3","model":"gpt-5.5","provider":"openai","segment_id":"subtitles.chat","source_path":"ui/src/i18n/locales/it.ts","src_lang":"en","text":"Gateway chat for quick interventions.","text_hash":"21296a7a8d725afc38e01df21bfd249bd2a3da77b38b522634983b2bbe1eaa94","tgt_lang":"it","translated":"Chat Gateway per interventi rapidi.","updated_at":"2026-04-29T17:37:35.636Z"}

View File

@@ -1,11 +1,19 @@
{
"fallbackKeys": [],
"generatedAt": "2026-05-06T03:19:59.299Z",
"fallbackKeys": [
"usage.presets.last1y",
"usage.presets.last90d",
"usage.scope.family",
"usage.scope.familyHint",
"usage.scope.familyIncluded",
"usage.scope.instance",
"usage.scope.instanceHint"
],
"generatedAt": "2026-05-08T03:32:06.912Z",
"locale": "ja-JP",
"model": "gpt-5.5",
"provider": "openai",
"sourceHash": "c97d50965a8485bb290aa7f158bae5dbadf3642e71bf4712207555f0abea23c2",
"totalKeys": 1017,
"translatedKeys": 1017,
"sourceHash": "b4c8279f087e4a589dd3a6e59c9e06f2686df58f904a678c5a773a0df05fe563",
"totalKeys": 1025,
"translatedKeys": 1018,
"workflow": 1
}

View File

@@ -328,6 +328,7 @@
{"cache_key":"5e228f6732d5271c6ad62628de1eab4bde413e85871d1a743aa2c1e587554544","model":"gpt-5.4","provider":"openai","segment_id":"cron.form.timeoutPlaceholder","source_path":"ui/src/i18n/locales/ja-JP.ts","src_lang":"en","text":"Optional, e.g. 90","text_hash":"6df8499092f2542448e280448a6915fe0d1b5354749ad0170108e193bfd23583","tgt_lang":"ja-JP","translated":"任意、例: 90","updated_at":"2026-04-05T17:13:55.724Z"}
{"cache_key":"5e9542ffd0592fbc9c94470efc5a7bf6945b992b3ced7d6776eae9baf90a226f","model":"gpt-5.4","provider":"openai","segment_id":"cron.errors.timeoutInvalid","source_path":"ui/src/i18n/locales/ja-JP.ts","src_lang":"en","text":"If set, timeout must be greater than 0 seconds.","text_hash":"0764500a498eaaaaec3489e0850a815efb7cf0adafcb92f37ea6ee779d281ee3","tgt_lang":"ja-JP","translated":"設定する場合、タイムアウトは 0 秒より大きくする必要があります。","updated_at":"2026-04-05T17:14:09.401Z"}
{"cache_key":"5ea50847b7f5bb35fe2f81ff8f34bf7b385a89d9087808981bc10caeef5108b6","model":"gpt-5.4","provider":"openai","segment_id":"cron.jobs.title","source_path":"ui/src/i18n/locales/ja-JP.ts","src_lang":"en","text":"Jobs","text_hash":"2f17a0f8d518e491c5a0c490b2c1991828dd87d173994ba40996e1da59d4e368","tgt_lang":"ja-JP","translated":"ジョブ","updated_at":"2026-04-05T17:13:38.296Z"}
{"cache_key":"5eba80386ef52aa1247cb32a152ca60f9f9cc6b6942f50e9a449ee68f2fdb2bd","model":"gpt-5.4","provider":"openai","segment_id":"usage.presets.all","source_path":"ui/src/i18n/locales/ja-JP.ts","src_lang":"en","text":"All","text_hash":"a52ace420f2175d08b1577a1bea5445e36801229c074ef9ed6c55a73401fd9c2","tgt_lang":"ja-JP","translated":"すべて","updated_at":"2026-04-05T17:13:23.087Z"}
{"cache_key":"5ed74bd18b779b42dbb2de95949f487c814b4c84ec0388ce6a9e4c7bd84a6e2f","model":"gpt-5.4","provider":"openai","segment_id":"cron.form.noneInternal","source_path":"ui/src/i18n/locales/ja-JP.ts","src_lang":"en","text":"None (internal)","text_hash":"f6820177591201d55e4b4c69520b46b4877c998d9ab3861bf0020a680c449397","tgt_lang":"ja-JP","translated":"なし(内部)","updated_at":"2026-04-05T17:13:55.724Z"}
{"cache_key":"5f9e98b2ec45b8878d29c2f7cbe65225001101270a353fd2b9b58d89f84f332d","model":"gpt-5.4","provider":"openai","segment_id":"usage.breakdown.tokensByType","source_path":"ui/src/i18n/locales/ja-JP.ts","src_lang":"en","text":"Tokens by Type","text_hash":"d27ec373ce7c31e25b570de9efd370c081820fa0469371072c6b200168eb8603","tgt_lang":"ja-JP","translated":"種類別トークン","updated_at":"2026-04-05T17:13:12.579Z"}
{"cache_key":"5ffeeb0c37c3123f57ab4fe657fa3081c2913e647bcc0ff101b269a6f8d13c48","model":"gpt-5.4","provider":"openai","segment_id":"overview.pairing.docsTitle","source_path":"ui/src/i18n/locales/ja-JP.ts","src_lang":"en","text":"Device pairing docs (opens in new tab)","text_hash":"4177ade2659cf1572b125131d05b31acb82f462f6bccaa4a136abff574e14a60","tgt_lang":"ja-JP","translated":"デバイスのペアリングに関するドキュメント(新しいタブで開きます)","updated_at":"2026-04-20T06:26:30.900Z"}

View File

@@ -1,11 +1,19 @@
{
"fallbackKeys": [],
"generatedAt": "2026-05-06T03:20:01.884Z",
"fallbackKeys": [
"usage.presets.last1y",
"usage.presets.last90d",
"usage.scope.family",
"usage.scope.familyHint",
"usage.scope.familyIncluded",
"usage.scope.instance",
"usage.scope.instanceHint"
],
"generatedAt": "2026-05-08T03:32:07.242Z",
"locale": "ko",
"model": "gpt-5.5",
"provider": "openai",
"sourceHash": "c97d50965a8485bb290aa7f158bae5dbadf3642e71bf4712207555f0abea23c2",
"totalKeys": 1017,
"translatedKeys": 1017,
"sourceHash": "b4c8279f087e4a589dd3a6e59c9e06f2686df58f904a678c5a773a0df05fe563",
"totalKeys": 1025,
"translatedKeys": 1018,
"workflow": 1
}

View File

@@ -92,6 +92,7 @@
{"cache_key":"19041cc96b2bb131d7fd408b51648bcdef4a521946f036d03d081911bed7e272","model":"gpt-5.4","provider":"openai","segment_id":"cron.jobs.lastRun","source_path":"ui/src/i18n/locales/ko.ts","src_lang":"en","text":"Last run","text_hash":"512a48218ba2179153629504206e7d54a7767e19ee2aa21574a7c614e5c92537","tgt_lang":"ko","translated":"마지막 실행","updated_at":"2026-04-05T17:14:40.640Z"}
{"cache_key":"192f761659b6c5ec7592e89510d21f8637a0d629c0135d44f43cd9b78c5b3809","model":"gpt-5.4","provider":"openai","segment_id":"usage.details.tokensWrittenToCache","source_path":"ui/src/i18n/locales/ko.ts","src_lang":"en","text":"Tokens written to cache","text_hash":"7abf026d6ca218c915b61286a73e94b7c71c6744b63702eab9bc41b4a3b20797","tgt_lang":"ko","translated":"캐시에 기록된 토큰","updated_at":"2026-04-05T17:14:28.018Z"}
{"cache_key":"193eb1ea420ed8e7dfaf7c37d46d3a0bce5217d8cf653a66bdff7c57e6329f27","model":"gpt-5.5","provider":"openai","segment_id":"cron.quickCreate.delivery.silent.label","source_path":"ui/src/i18n/locales/ko.ts","src_lang":"en","text":"Silent","text_hash":"ddbcf06726488a43af36838754808ac5041b05ab6434735615979d820725b56f","tgt_lang":"ko","translated":"무음","updated_at":"2026-04-29T20:13:59.874Z"}
{"cache_key":"19de83323950bb257e949a8a973ee09ed5b83dcc197a43247211d1eb63b18e76","model":"gpt-5.4","provider":"openai","segment_id":"usage.presets.all","source_path":"ui/src/i18n/locales/ko.ts","src_lang":"en","text":"All","text_hash":"a52ace420f2175d08b1577a1bea5445e36801229c074ef9ed6c55a73401fd9c2","tgt_lang":"ko","translated":"전체","updated_at":"2026-04-05T17:14:10.229Z"}
{"cache_key":"1a59d0752c44042d52b400de71e95e31da2af2038f097f72c542af2f88e7cb3c","model":"gpt-5.4","provider":"openai","segment_id":"dreaming.phase.rem","source_path":"ui/src/i18n/locales/ko.ts","src_lang":"en","text":"Rem","text_hash":"4c14dc4d912623b7710f1cd7038895f720aa9f374e34e82492fe6e5a16b513cf","tgt_lang":"ko","translated":"렘","updated_at":"2026-04-10T07:59:09.199Z"}
{"cache_key":"1aa06d1b60406f35a4fb3d06e3cfc78d4d1957f6f46ce34977460eb2c1ac518b","model":"gpt-5.4","provider":"openai","segment_id":"usage.mosaic.eightPm","source_path":"ui/src/i18n/locales/ko.ts","src_lang":"en","text":"8pm","text_hash":"232df857db5e72521b783719e674c41bce48738283c637b44ed2a80fa81ec56c","tgt_lang":"ko","translated":"오후 8시","updated_at":"2026-04-05T17:14:34.546Z"}
{"cache_key":"1aa1bd6583d9ae9fb7e80a87b8e52d15d313fc197310ea06f356fb95e8ebd797","model":"gpt-5.4","provider":"openai","segment_id":"cron.jobs.name","source_path":"ui/src/i18n/locales/ko.ts","src_lang":"en","text":"Name","text_hash":"dcd1d5223f73b3a965c07e3ff5dbee3eedcfedb806686a05b9b3868a2c3d6d50","tgt_lang":"ko","translated":"이름","updated_at":"2026-04-05T17:14:43.522Z"}

View File

@@ -1,11 +1,19 @@
{
"fallbackKeys": [],
"generatedAt": "2026-05-06T03:22:51.348Z",
"fallbackKeys": [
"usage.presets.last1y",
"usage.presets.last90d",
"usage.scope.family",
"usage.scope.familyHint",
"usage.scope.familyIncluded",
"usage.scope.instance",
"usage.scope.instanceHint"
],
"generatedAt": "2026-05-08T03:32:10.445Z",
"locale": "nl",
"model": "gpt-5.5",
"provider": "openai",
"sourceHash": "c97d50965a8485bb290aa7f158bae5dbadf3642e71bf4712207555f0abea23c2",
"totalKeys": 1017,
"translatedKeys": 1017,
"sourceHash": "b4c8279f087e4a589dd3a6e59c9e06f2686df58f904a678c5a773a0df05fe563",
"totalKeys": 1025,
"translatedKeys": 1018,
"workflow": 1
}

View File

@@ -950,6 +950,7 @@
{"cache_key":"ed2d6fc9bddc05d348a5c8d8c0c087d2a1a7bdd90db48c652e2d61b3423cb8b6","model":"gpt-5.5","provider":"openai","segment_id":"agents.context.skillsFilter","source_path":"ui/src/i18n/locales/nl.ts","src_lang":"en","text":"Skills Filter","text_hash":"55adfafb5397bbb183fd28a9fc9cee00c327d45ae1a9ed4841be66cd4658e99e","tgt_lang":"nl","translated":"Skills-filter","updated_at":"2026-04-29T19:28:40.901Z"}
{"cache_key":"eec283681cc86575321bbf7ed585e6ebd492fe698fdcd1b7105ead0967089827","model":"gpt-5.5","provider":"openai","segment_id":"cron.runs.runStatusError","source_path":"ui/src/i18n/locales/nl.ts","src_lang":"en","text":"Error","text_hash":"54a0e8c17ebb21a11f8a25b8042786ef7efe52441e6cc87e92c67e0c4c0c6e78","tgt_lang":"nl","translated":"Fout","updated_at":"2026-04-29T17:41:38.443Z"}
{"cache_key":"ef9a8961e47cd4e0c454c31af1f684500c19d2990b1133272dd84c13574b1d5a","model":"gpt-5.5","provider":"openai","segment_id":"usage.details.systemPromptBreakdown","source_path":"ui/src/i18n/locales/nl.ts","src_lang":"en","text":"System Prompt Breakdown","text_hash":"9dc260464a352943528d0a21d4618925331553f1248e17e3fbfdc103e50c82cb","tgt_lang":"nl","translated":"Uitsplitsing van systeemprompt","updated_at":"2026-04-29T17:41:14.532Z"}
{"cache_key":"efb7c1df24dbefb499370b2b334d89e1055c576c32a9749385ae6e46fb9234c0","model":"gpt-5.5","provider":"openai","segment_id":"usage.presets.all","source_path":"ui/src/i18n/locales/nl.ts","src_lang":"en","text":"All","text_hash":"a52ace420f2175d08b1577a1bea5445e36801229c074ef9ed6c55a73401fd9c2","tgt_lang":"nl","translated":"Alle","updated_at":"2026-04-29T17:40:55.440Z"}
{"cache_key":"efc3ef6f93dae293bbb4d7e4eaa0877b53f2d6100b803a7e01aef925149f106d","model":"gpt-5.5","provider":"openai","segment_id":"cron.runEntry.runAt","source_path":"ui/src/i18n/locales/nl.ts","src_lang":"en","text":"Run at","text_hash":"4b4c31294fb5b71b1b7b022c0fcc15a8295e19ecf0788db48cdeeab0d5623433","tgt_lang":"nl","translated":"Uitvoeren om","updated_at":"2026-04-29T17:42:05.342Z"}
{"cache_key":"efde83b5b5e9972f85361013c39a9f5561beeb0b7cc5623e23734e615c8b8c66","model":"gpt-5.5","provider":"openai","segment_id":"debug.eventLogTitle","source_path":"ui/src/i18n/locales/nl.ts","src_lang":"en","text":"Event Log","text_hash":"ad46380cee0c03bd2d8f9c6d0d91b724118c796a9d9eb5f167fc8da4d7cfd2b7","tgt_lang":"nl","translated":"Gebeurtenislogboek","updated_at":"2026-04-29T19:28:54.400Z"}
{"cache_key":"f029642e429d8437ef0a78ed0c887ccd7a2860d99a59827f0a23106bb913eb44","model":"gpt-5.5","provider":"openai","segment_id":"tabs.skills","source_path":"ui/src/i18n/locales/nl.ts","src_lang":"en","text":"Skills","text_hash":"66d0f523a379b2de6f8d5fba3a817ebc395f7bcaa54cc132ca9dfa665d1e9378","tgt_lang":"nl","translated":"Skills","updated_at":"2026-04-29T17:40:06.931Z"}

View File

@@ -1,11 +1,19 @@
{
"fallbackKeys": [],
"generatedAt": "2026-05-06T03:21:59.198Z",
"fallbackKeys": [
"usage.presets.last1y",
"usage.presets.last90d",
"usage.scope.family",
"usage.scope.familyHint",
"usage.scope.familyIncluded",
"usage.scope.instance",
"usage.scope.instanceHint"
],
"generatedAt": "2026-05-08T03:32:09.483Z",
"locale": "pl",
"model": "gpt-5.5",
"provider": "openai",
"sourceHash": "c97d50965a8485bb290aa7f158bae5dbadf3642e71bf4712207555f0abea23c2",
"totalKeys": 1017,
"translatedKeys": 1017,
"sourceHash": "b4c8279f087e4a589dd3a6e59c9e06f2686df58f904a678c5a773a0df05fe563",
"totalKeys": 1025,
"translatedKeys": 1018,
"workflow": 1
}

View File

@@ -199,6 +199,7 @@
{"cache_key":"3e75ea3ac0c52a02c83df6ed0c7b76b5683a21f73ed56a72edc57bc4e6b7254f","model":"gpt-5.4","provider":"openai","segment_id":"common.configured","source_path":"ui/src/i18n/locales/pl.ts","src_lang":"en","text":"Configured","text_hash":"84aebc69a1bf739a343be9c66edfd3160f77220ea69789a8147dd4ae261fd188","tgt_lang":"pl","translated":"Skonfigurowano","updated_at":"2026-04-06T02:50:57.426Z"}
{"cache_key":"3ec689b9884a5fe161ea1c4dadd29486b8efb145a8422b3da2bb3f38356d2aef","model":"gpt-5.4","provider":"openai","segment_id":"usage.details.close","source_path":"ui/src/i18n/locales/pl.ts","src_lang":"en","text":"Close session details","text_hash":"6f8d91841e5b0c970dc5f7620be8c6388b04f1e03f2896d33b81583a1e617abe","tgt_lang":"pl","translated":"Zamknij szczegóły sesji","updated_at":"2026-04-05T17:16:59.033Z"}
{"cache_key":"3f276a2883ecc335f209a8e2d8ba7bf8fb0bf4e519fe8ddf43aa7d019bbf6121","model":"gpt-5.4","provider":"openai","segment_id":"cron.form.resultDelivery","source_path":"ui/src/i18n/locales/pl.ts","src_lang":"en","text":"Result delivery","text_hash":"5c3dc0d7b06d54b07b7e063a8cc675baf44327d6bcdbfac874c94700afbc887b","tgt_lang":"pl","translated":"Dostarczanie wyników","updated_at":"2026-04-05T17:17:28.910Z"}
{"cache_key":"3f29564d70e87d2ebeee9f03029d596a75c169fc8ee04adada9f78481631b8c2","model":"gpt-5.4","provider":"openai","segment_id":"usage.presets.all","source_path":"ui/src/i18n/locales/pl.ts","src_lang":"en","text":"All","text_hash":"a52ace420f2175d08b1577a1bea5445e36801229c074ef9ed6c55a73401fd9c2","tgt_lang":"pl","translated":"Wszystkie","updated_at":"2026-04-05T17:17:11.922Z"}
{"cache_key":"3fabc8372735f0d2593e492987d9d24cb0954ec30c26430bc586dcdca268da8a","model":"gpt-5.4","provider":"openai","segment_id":"usage.mosaic.dayOfWeek","source_path":"ui/src/i18n/locales/pl.ts","src_lang":"en","text":"Day of Week","text_hash":"0f2148a98fb2064bb5194ba8ed3b453cd5e2bfdb8f1549509e16e8b9e94acb71","tgt_lang":"pl","translated":"Dzień tygodnia","updated_at":"2026-04-05T17:17:05.932Z"}
{"cache_key":"412cd647f7ded642b05cf29238a77f30e54238d6f4194e89bc30ebf8d3a6c349","model":"gpt-5.5","provider":"openai","segment_id":"cron.quickCreate.delivery.silent.description","source_path":"ui/src/i18n/locales/pl.ts","src_lang":"en","text":"Run without notification","text_hash":"4ec9ec8312db9f6fe8f47f01f859e1872642f3590d379821cb517b028f808083","tgt_lang":"pl","translated":"Uruchom bez powiadomienia","updated_at":"2026-04-29T20:16:11.487Z"}
{"cache_key":"4133905a576e3a5303d4697bb09a167589be24f092374c5f4c6938462a8aa55c","model":"gpt-5.4","provider":"openai","segment_id":"cron.form.hours","source_path":"ui/src/i18n/locales/pl.ts","src_lang":"en","text":"Hours","text_hash":"21e8492938abc179410c21f3598f141c4c59a8bf2d3b4e475b7d83e10adfc00f","tgt_lang":"pl","translated":"Godziny","updated_at":"2026-04-05T17:17:24.450Z"}

View File

@@ -1,11 +1,19 @@
{
"fallbackKeys": [],
"generatedAt": "2026-05-06T03:18:53.927Z",
"fallbackKeys": [
"usage.presets.last1y",
"usage.presets.last90d",
"usage.scope.family",
"usage.scope.familyHint",
"usage.scope.familyIncluded",
"usage.scope.instance",
"usage.scope.instanceHint"
],
"generatedAt": "2026-05-08T03:32:05.951Z",
"locale": "pt-BR",
"model": "gpt-5.5",
"provider": "openai",
"sourceHash": "c97d50965a8485bb290aa7f158bae5dbadf3642e71bf4712207555f0abea23c2",
"totalKeys": 1017,
"translatedKeys": 1017,
"sourceHash": "b4c8279f087e4a589dd3a6e59c9e06f2686df58f904a678c5a773a0df05fe563",
"totalKeys": 1025,
"translatedKeys": 1018,
"workflow": 1
}

View File

@@ -541,6 +541,7 @@
{"cache_key":"bc40b33b65f05e40dfc017a6233a1de5b42c969992b630ae6159b40ba83567a8","model":"gpt-5.4","provider":"openai","segment_id":"channels.nostr.account","source_path":"ui/src/i18n/locales/pt-BR.ts","src_lang":"en","text":"Account","text_hash":"7e1b0d5641f2640ce9a953ec231eea2c27a2a7633f7d3c273e5735e2b30c10b7","tgt_lang":"pt-BR","translated":"Conta","updated_at":"2026-04-06T02:47:44.853Z"}
{"cache_key":"bccd4be93fcbc8ce8ddb5172ec17a4e6a641ccc3175f903705d2c377e1a11b3f","model":"gpt-5.4","provider":"openai","segment_id":"usage.metrics.session","source_path":"ui/src/i18n/locales/pt-BR.ts","src_lang":"en","text":"session","text_hash":"3f3af1ecebbd1410ab417ec0d27bbfcb5d340e177ae159b59fc8626c2dfd9175","tgt_lang":"pt-BR","translated":"sessão","updated_at":"2026-04-05T17:10:40.263Z"}
{"cache_key":"bcebeba442bae3d5634b62ab47f4afaf93f050b285af71527bf10ba82f1559fd","model":"gpt-5.4","provider":"openai","segment_id":"cron.form.to","source_path":"ui/src/i18n/locales/pt-BR.ts","src_lang":"en","text":"To","text_hash":"f4b06ef6d3c81436f60a318c81c42f8f7e2d774d45a22f3b9b5f3b6980d28146","tgt_lang":"pt-BR","translated":"Para","updated_at":"2026-04-05T17:11:56.599Z"}
{"cache_key":"bd4ea2a92602806193a491c7406397551cfacb5b6de42c29fe16f261b68c3837","model":"gpt-5.4","provider":"openai","segment_id":"usage.presets.all","source_path":"ui/src/i18n/locales/pt-BR.ts","src_lang":"en","text":"All","text_hash":"a52ace420f2175d08b1577a1bea5445e36801229c074ef9ed6c55a73401fd9c2","tgt_lang":"pt-BR","translated":"Todas","updated_at":"2026-04-05T17:11:09.199Z"}
{"cache_key":"bd9726d7c1d625327efd42779513864fe83382af368f701a717baa356b1af31a","model":"gpt-5.5","provider":"openai","segment_id":"cron.quickCreate.schedules.hourly.label","source_path":"ui/src/i18n/locales/pt-BR.ts","src_lang":"en","text":"Hourly","text_hash":"eab0cd8fdf9bccecc74e5a010b32af35986e3929184a599f5aea3b8195340ee2","tgt_lang":"pt-BR","translated":"A cada hora","updated_at":"2026-04-29T20:12:23.444Z"}
{"cache_key":"bdd891ef8dce2792bc7a3c438b4d92e6e3e35839cdb1d24b43a8a35278ef2c97","model":"gpt-5.4","provider":"openai","segment_id":"cron.jobs.enabled","source_path":"ui/src/i18n/locales/pt-BR.ts","src_lang":"en","text":"Enabled","text_hash":"92c1cdfdf4cb9cf6fcca962f206de36fd5d60db1178bc9461052f8de703a0e06","tgt_lang":"pt-BR","translated":"Ativado","updated_at":"2026-04-05T17:11:32.154Z"}
{"cache_key":"be40f86b7fa34d9115e70abe5bf6cedde1234b155acd2e8aabf77c26cac1392d","model":"gpt-5.4","provider":"openai","segment_id":"cron.form.requiredSr","source_path":"ui/src/i18n/locales/pt-BR.ts","src_lang":"en","text":"required","text_hash":"d0a3630555bbec7fc05a98d311c23b00fd1ab4d8296ac4a4125976d80b6a6959","tgt_lang":"pt-BR","translated":"obrigatório","updated_at":"2026-04-05T17:11:43.511Z"}

View File

@@ -1,11 +1,19 @@
{
"fallbackKeys": [],
"generatedAt": "2026-05-06T03:22:08.293Z",
"fallbackKeys": [
"usage.presets.last1y",
"usage.presets.last90d",
"usage.scope.family",
"usage.scope.familyHint",
"usage.scope.familyIncluded",
"usage.scope.instance",
"usage.scope.instanceHint"
],
"generatedAt": "2026-05-08T03:32:09.804Z",
"locale": "th",
"model": "gpt-5.5",
"provider": "openai",
"sourceHash": "c97d50965a8485bb290aa7f158bae5dbadf3642e71bf4712207555f0abea23c2",
"totalKeys": 1017,
"translatedKeys": 1017,
"sourceHash": "b4c8279f087e4a589dd3a6e59c9e06f2686df58f904a678c5a773a0df05fe563",
"totalKeys": 1025,
"translatedKeys": 1018,
"workflow": 1
}

View File

@@ -788,6 +788,7 @@
{"cache_key":"e21f59274bb9655ba7988881505217eb28a6c049c8643d96c77245a566cbe982","model":"gpt-5.4","provider":"openai","segment_id":"login.toggleTokenVisibility","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"Toggle token visibility","text_hash":"81fc4b962be0e4a4748879f1645272c8f2302e101c59544f1fac347b3f892f26","tgt_lang":"th","translated":"สลับการแสดงโทเค็น","updated_at":"2026-04-23T06:28:07.264Z"}
{"cache_key":"e2627430c05b23902ee53046dff663d95d2f7fa8a5f3032195ca97ee3f234174","model":"gpt-5.4","provider":"openai","segment_id":"usage.filters.endDate","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"End date","text_hash":"14303aa0c4a08d390e1180d9ed4ecbad43d4c4176d82ea8b8ae3f4b648b07380","tgt_lang":"th","translated":"วันที่สิ้นสุด","updated_at":"2026-04-23T06:27:29.651Z"}
{"cache_key":"e29ae0fdba75ce2bd3cd7a3a056a722cc9e41c39c0ffa67a6692d72d08ad2576","model":"gpt-5.4","provider":"openai","segment_id":"cron.jobDetail.system","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"System","text_hash":"6725e7bbcd28f3a8a586fa34bf191fd72dde8b61756932cd3237c17a6f196f1a","tgt_lang":"th","translated":"ระบบ","updated_at":"2026-04-23T06:28:55.072Z"}
{"cache_key":"e2f57b411b7bb7c24c446ffdc41bfa65bd8c32b01db01e8ac712e4eff28ac5bc","model":"gpt-5.4","provider":"openai","segment_id":"usage.presets.all","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"All","text_hash":"a52ace420f2175d08b1577a1bea5445e36801229c074ef9ed6c55a73401fd9c2","tgt_lang":"th","translated":"ทั้งหมด","updated_at":"2026-04-23T06:27:29.651Z"}
{"cache_key":"e3fec551d724b60ae6424fba62c6f01b1f66cb447f673d2ea16de7d5d5d43fbf","model":"gpt-5.5","provider":"openai","segment_id":"common.dismiss","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"Dismiss","text_hash":"48845bff334a50a59aaecf499f28a7a24c3b4b891b8b18a9f1169ad8e8a6b261","tgt_lang":"th","translated":"ปิด","updated_at":"2026-04-29T20:16:08.353Z"}
{"cache_key":"e41165efffe12df129474b211bcbfdacbc20b3a0307f3c858a973af151a84c1c","model":"gpt-5.4","provider":"openai","segment_id":"cron.form.webhookPost","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"Webhook POST","text_hash":"d723454d0dc5c8e14aa37fc971854acea7aebcff2f323d537dac4732aacb0aa3","tgt_lang":"th","translated":"Webhook POST","updated_at":"2026-04-23T06:28:41.624Z"}
{"cache_key":"e4b90de67585e3dfe9332323367acbb284a5f1b21cfbfe9c14a0cd10deffbc01","model":"gpt-5.4","provider":"openai","segment_id":"cron.jobs.sort","source_path":"ui/src/i18n/locales/th.ts","src_lang":"en","text":"Sort","text_hash":"bec69036aa27e7fab7d44cad3909477b76631c39ba46fd7841ea71aae7e5a735","tgt_lang":"th","translated":"เรียงลำดับ","updated_at":"2026-04-23T06:28:22.345Z"}

View File

@@ -1,11 +1,19 @@
{
"fallbackKeys": [],
"generatedAt": "2026-05-06T03:20:53.895Z",
"fallbackKeys": [
"usage.presets.last1y",
"usage.presets.last90d",
"usage.scope.family",
"usage.scope.familyHint",
"usage.scope.familyIncluded",
"usage.scope.instance",
"usage.scope.instanceHint"
],
"generatedAt": "2026-05-08T03:32:08.524Z",
"locale": "tr",
"model": "gpt-5.5",
"provider": "openai",
"sourceHash": "c97d50965a8485bb290aa7f158bae5dbadf3642e71bf4712207555f0abea23c2",
"totalKeys": 1017,
"translatedKeys": 1017,
"sourceHash": "b4c8279f087e4a589dd3a6e59c9e06f2686df58f904a678c5a773a0df05fe563",
"totalKeys": 1025,
"translatedKeys": 1018,
"workflow": 1
}

View File

@@ -674,6 +674,7 @@
{"cache_key":"c5be3919cde1c5cf5e3ee3e55fb6df0f39275221dda4ca735b8cc1558ef3d233","model":"gpt-5.4","provider":"openai","segment_id":"cron.errors.timeoutInvalid","source_path":"ui/src/i18n/locales/tr.ts","src_lang":"en","text":"If set, timeout must be greater than 0 seconds.","text_hash":"0764500a498eaaaaec3489e0850a815efb7cf0adafcb92f37ea6ee779d281ee3","tgt_lang":"tr","translated":"Ayarlanırsa zaman aşımı 0 saniyeden büyük olmalıdır.","updated_at":"2026-04-05T17:16:38.206Z"}
{"cache_key":"c5ff262485017a358e87fc8108dcb8fe1d46ca7973d79ea9f350fd1ab9413c62","model":"gpt-5.4","provider":"openai","segment_id":"usage.sessions.noRecent","source_path":"ui/src/i18n/locales/tr.ts","src_lang":"en","text":"No recent sessions","text_hash":"100ac08064a6d5867a400a56b2949f9de3f6da4602a99461ee3a300c20273c1b","tgt_lang":"tr","translated":"Son oturum yok","updated_at":"2026-04-05T17:15:36.684Z"}
{"cache_key":"c6077a1680b8eb0006eb8b77e34d3c56081a286b7f8ed0e343f449387642f821","model":"gpt-5.4","provider":"openai","segment_id":"cron.runs.allDelivery","source_path":"ui/src/i18n/locales/tr.ts","src_lang":"en","text":"All delivery","text_hash":"41ae1c2395e52fa33ba7df91afec0e316cd9e36a74a39b87a825f65a7dce707b","tgt_lang":"tr","translated":"Tüm teslimatlar","updated_at":"2026-04-05T17:16:06.352Z"}
{"cache_key":"c642b2b59f986fc7d0bd065aa0afaa9df61797fe2c911f6370641d8365f3f200","model":"gpt-5.4","provider":"openai","segment_id":"usage.presets.all","source_path":"ui/src/i18n/locales/tr.ts","src_lang":"en","text":"All","text_hash":"a52ace420f2175d08b1577a1bea5445e36801229c074ef9ed6c55a73401fd9c2","tgt_lang":"tr","translated":"Tümü","updated_at":"2026-04-05T17:15:57.661Z"}
{"cache_key":"c64f76af0563010991e3967893bcb0547bb0c2cafdd4e8e357aaad8089e94b0b","model":"gpt-5.4","provider":"openai","segment_id":"usage.details.cumulative","source_path":"ui/src/i18n/locales/tr.ts","src_lang":"en","text":"Cumulative","text_hash":"cecf2aade089366e0a1d7c3dfc5acb40de8bb0d84c71b890d96da2f2de96c152","tgt_lang":"tr","translated":"Kümülatif","updated_at":"2026-04-05T17:15:40.851Z"}
{"cache_key":"c687a6ea17b472419214f5175b73096449e79473bd6cea3298a69bc0f599f6d7","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.hideSessionDetails","source_path":"ui/src/i18n/locales/tr.ts","src_lang":"en","text":"Hide session details for {count}","text_hash":"b087cfae8608379df7c7cbb35354d004b7b2f8b457b37ab578d7fd0f9e6a6798","tgt_lang":"tr","translated":"{count} için oturum ayrıntılarını gizle","updated_at":"2026-05-06T03:20:53.741Z"}
{"cache_key":"c693c412e33f13f1945b1a4715d9bfda9bb7769cc63afed6f3bde2ddf2cd4c13","model":"gpt-5.5","provider":"openai","segment_id":"chat.openCommandPalette","source_path":"ui/src/i18n/locales/tr.ts","src_lang":"en","text":"Open command palette","text_hash":"c022b19a38a632d9f0981df1407ed11743b7fd8a80b159b76a7cf78ad61a43b1","tgt_lang":"tr","translated":"Komut paletini aç","updated_at":"2026-04-29T20:15:07.759Z"}

View File

@@ -1,11 +1,19 @@
{
"fallbackKeys": [],
"generatedAt": "2026-05-06T03:21:36.719Z",
"fallbackKeys": [
"usage.presets.last1y",
"usage.presets.last90d",
"usage.scope.family",
"usage.scope.familyHint",
"usage.scope.familyIncluded",
"usage.scope.instance",
"usage.scope.instanceHint"
],
"generatedAt": "2026-05-08T03:32:08.843Z",
"locale": "uk",
"model": "gpt-5.5",
"provider": "openai",
"sourceHash": "c97d50965a8485bb290aa7f158bae5dbadf3642e71bf4712207555f0abea23c2",
"totalKeys": 1017,
"translatedKeys": 1017,
"sourceHash": "b4c8279f087e4a589dd3a6e59c9e06f2686df58f904a678c5a773a0df05fe563",
"totalKeys": 1025,
"translatedKeys": 1018,
"workflow": 1
}

View File

@@ -816,6 +816,7 @@
{"cache_key":"ed8a55e9836eb0fbc3eb4b2bdbe9963731148ea86fd08f50034ca8de2eb0b85d","model":"gpt-5.4","provider":"openai","segment_id":"login.passwordPlaceholder","source_path":"ui/src/i18n/locales/uk.ts","src_lang":"en","text":"optional","text_hash":"ec91fdd9256cb75ae611249b50cb7eb16533f0fa91b86239ec1d439a1ea033b8","tgt_lang":"uk","translated":"необов’язково","updated_at":"2026-04-05T17:23:20.907Z"}
{"cache_key":"edc2881ef5613d7f47b28056f63264750b8ca57757257f025c1e3763b5c3da34","model":"gpt-5.4","provider":"openai","segment_id":"usage.details.collapse","source_path":"ui/src/i18n/locales/uk.ts","src_lang":"en","text":"Collapse","text_hash":"be6eb1fc3b05bf9dceebad2eac7841d1b2f40bda9aa2da34df8ca22af02bc3ed","tgt_lang":"uk","translated":"Згорнути","updated_at":"2026-04-05T17:23:13.966Z"}
{"cache_key":"ee2393a0f45d4ed3dad1d2dcb908a156669a18f971f21570b0cea28f5c31caa5","model":"gpt-5.4","provider":"openai","segment_id":"common.version","source_path":"ui/src/i18n/locales/uk.ts","src_lang":"en","text":"Version","text_hash":"dd167905de0defcaf72de673ee44c07431770d129ccffab286bd2edfdaf62396","tgt_lang":"uk","translated":"Версія","updated_at":"2026-04-05T17:22:15.408Z"}
{"cache_key":"eea3299ac5ce54c9fc2666a7eb3ed159b585c36ade10d374ae3ae24083db775d","model":"gpt-5.4","provider":"openai","segment_id":"usage.presets.all","source_path":"ui/src/i18n/locales/uk.ts","src_lang":"en","text":"All","text_hash":"a52ace420f2175d08b1577a1bea5445e36801229c074ef9ed6c55a73401fd9c2","tgt_lang":"uk","translated":"Усі","updated_at":"2026-04-05T17:22:42.565Z"}
{"cache_key":"eec491834295ebce16b551f5156c8acd07de38dcbb3effd9f821772263f74ba5","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.globalTooltip","source_path":"ui/src/i18n/locales/uk.ts","src_lang":"en","text":"Include global sessions.","text_hash":"d7e84378e823b8b8a09d445cf921ce904c257fef554a573c023e9355b5f9fdf2","tgt_lang":"uk","translated":"Включити глобальні сеанси.","updated_at":"2026-05-04T07:16:57.699Z"}
{"cache_key":"eef016b707ff5c38ddc15e553af590093ee7eba948304c5940acf8bd9121251d","model":"gpt-5.4","provider":"openai","segment_id":"common.publicKey","source_path":"ui/src/i18n/locales/uk.ts","src_lang":"en","text":"Public Key","text_hash":"a51af74c1dda1bf0f6a64455d747f7e14aa8cda977cbe7b26fb9d5323125d41a","tgt_lang":"uk","translated":"Публічний ключ","updated_at":"2026-04-06T02:50:29.304Z"}
{"cache_key":"ef3407e5eb8ac61faea4befc7d595879de23a2bae5a16465d9462f5460c82932","model":"gpt-5.4","provider":"openai","segment_id":"languages.zhCN","source_path":"ui/src/i18n/locales/uk.ts","src_lang":"en","text":"简体中文 (Simplified Chinese)","text_hash":"e34fcc9872e46b54fd22bd89aae921332644df9ff58d7778cba9c4007dbeafb2","tgt_lang":"uk","translated":"简体中文 (спрощена китайська)","updated_at":"2026-04-06T02:50:57.411Z"}

View File

@@ -1,11 +1,19 @@
{
"fallbackKeys": [],
"generatedAt": "2026-05-06T03:22:47.711Z",
"fallbackKeys": [
"usage.presets.last1y",
"usage.presets.last90d",
"usage.scope.family",
"usage.scope.familyHint",
"usage.scope.familyIncluded",
"usage.scope.instance",
"usage.scope.instanceHint"
],
"generatedAt": "2026-05-08T03:32:10.123Z",
"locale": "vi",
"model": "gpt-5.5",
"provider": "openai",
"sourceHash": "c97d50965a8485bb290aa7f158bae5dbadf3642e71bf4712207555f0abea23c2",
"totalKeys": 1017,
"translatedKeys": 1017,
"sourceHash": "b4c8279f087e4a589dd3a6e59c9e06f2686df58f904a678c5a773a0df05fe563",
"totalKeys": 1025,
"translatedKeys": 1018,
"workflow": 1
}

View File

@@ -533,6 +533,7 @@
{"cache_key":"899f904e1aa8f3eafd0e86018c2fafbf8444dff1f3abfd25ebb184c37bb7bc79","model":"gpt-5.5","provider":"openai","segment_id":"usage.mosaic.thu","source_path":"ui/src/i18n/locales/vi.ts","src_lang":"en","text":"Thu","text_hash":"7da11212ed340ea7976a39891c56c6f1e791a175a4bad537ba1cf21f5c83f6fd","tgt_lang":"vi","translated":"T5","updated_at":"2026-04-29T17:41:23.093Z"}
{"cache_key":"89a1c77bab5b000b235726d5d290d98d1335ea46eddb7532331b04d54ac96886","model":"gpt-5.5","provider":"openai","segment_id":"debug.manualRpcTitle","source_path":"ui/src/i18n/locales/vi.ts","src_lang":"en","text":"Manual RPC","text_hash":"36959009e5a3ddb7e3723e6d52b16e76cec908ae55220b8ebeff82536789a504","tgt_lang":"vi","translated":"RPC thủ công","updated_at":"2026-04-29T19:28:19.773Z"}
{"cache_key":"89b213dfbfa1add2389917a882941c884f0adab6a9df926029b81361ba5c30bf","model":"gpt-5.5","provider":"openai","segment_id":"common.configured","source_path":"ui/src/i18n/locales/vi.ts","src_lang":"en","text":"Configured","text_hash":"84aebc69a1bf739a343be9c66edfd3160f77220ea69789a8147dd4ae261fd188","tgt_lang":"vi","translated":"Đã cấu hình","updated_at":"2026-04-29T17:39:28.986Z"}
{"cache_key":"89d5cadb5c55d41b1b88ebb41c582e8c80a16df6a519c7b0dcc5997b6c062866","model":"gpt-5.5","provider":"openai","segment_id":"usage.presets.all","source_path":"ui/src/i18n/locales/vi.ts","src_lang":"en","text":"All","text_hash":"a52ace420f2175d08b1577a1bea5445e36801229c074ef9ed6c55a73401fd9c2","tgt_lang":"vi","translated":"Tất cả","updated_at":"2026-04-29T17:40:47.920Z"}
{"cache_key":"8a4492a3a0c75622292c430cc1d01df97e483f99ef68e85f6b1875004c4a22ed","model":"gpt-5.5","provider":"openai","segment_id":"common.no","source_path":"ui/src/i18n/locales/vi.ts","src_lang":"en","text":"No","text_hash":"1ea442a134b2a184bd5d40104401f2a37fbc09ccf3f4bc9da161c6099be3691d","tgt_lang":"vi","translated":"Không","updated_at":"2026-04-29T17:39:25.691Z"}
{"cache_key":"8a8f7a04bdaa5768a0b2395c8336ccb1635a5d666998e6a2ca04c4ade107f7c5","model":"gpt-5.5","provider":"openai","segment_id":"dreaming.phase.deep","source_path":"ui/src/i18n/locales/vi.ts","src_lang":"en","text":"Deep","text_hash":"c54e3625467b4fdecbd75968fc2fa16fff1e6ad1359e37d32604cadcc8947d5e","tgt_lang":"vi","translated":"Sâu","updated_at":"2026-04-29T17:40:26.886Z"}
{"cache_key":"8ac92dc36ecab554ac7ecf931b8301b50eb1ea91c8848754189393c13fb72d5f","model":"gpt-5.5","provider":"openai","segment_id":"cron.errors.webhookUrlRequired","source_path":"ui/src/i18n/locales/vi.ts","src_lang":"en","text":"Webhook URL is required.","text_hash":"a84533e7d336c2821ad97847dbe84fd1f7f0219b710e98d4e5f978485dc5008a","tgt_lang":"vi","translated":"Bắt buộc nhập URL webhook.","updated_at":"2026-04-29T17:42:13.186Z"}

View File

@@ -1,11 +1,19 @@
{
"fallbackKeys": [],
"generatedAt": "2026-05-06T03:19:11.701Z",
"fallbackKeys": [
"usage.presets.last1y",
"usage.presets.last90d",
"usage.scope.family",
"usage.scope.familyHint",
"usage.scope.familyIncluded",
"usage.scope.instance",
"usage.scope.instanceHint"
],
"generatedAt": "2026-05-08T03:32:05.293Z",
"locale": "zh-CN",
"model": "gpt-5.5",
"provider": "openai",
"sourceHash": "c97d50965a8485bb290aa7f158bae5dbadf3642e71bf4712207555f0abea23c2",
"totalKeys": 1017,
"translatedKeys": 1017,
"sourceHash": "b4c8279f087e4a589dd3a6e59c9e06f2686df58f904a678c5a773a0df05fe563",
"totalKeys": 1025,
"translatedKeys": 1018,
"workflow": 1
}

View File

@@ -459,6 +459,7 @@
{"cache_key":"c236de260721ddfdd0abcd8298e7420412f329ecf1d1ee79c9ed945291deeb97","model":"gpt-5.4","provider":"openai","segment_id":"common.saving","source_path":"ui/src/i18n/locales/zh-CN.ts","src_lang":"en","text":"Saving…","text_hash":"23e39291d6135814ed7c936e278974544b0df5fbf0eb0427b6700979b7472a93","tgt_lang":"zh-CN","translated":"保存中…","updated_at":"2026-04-06T02:47:30.960Z"}
{"cache_key":"c315c7c648774a2ade072ef047ba8ecbd303eb17bddf9b690a1a80c6dec65857","model":"gpt-5.4","provider":"openai","segment_id":"dreaming.status.active","source_path":"ui/src/i18n/locales/zh-CN.ts","src_lang":"en","text":"Dreaming Active","text_hash":"fd7a73177f09d63e4afe11f3ac6e028368eb1c3163b80022a9bf46b94e1b658a","tgt_lang":"zh-CN","translated":"Dreaming 运行中","updated_at":"2026-04-06T02:47:45.405Z"}
{"cache_key":"c320f6fc6daea08d97047d38816a3f914b8a5ec9c789a7c4ac7a9db4fa905277","model":"gpt-5.4","provider":"openai","segment_id":"channels.nostr.about","source_path":"ui/src/i18n/locales/zh-CN.ts","src_lang":"en","text":"About","text_hash":"4efca0d10c5feb8e9b35eb1d994f2905bb71714e6a271f511d713b539ea5faa1","tgt_lang":"zh-CN","translated":"关于","updated_at":"2026-04-06T02:47:39.053Z"}
{"cache_key":"c34b4f4f27630ae661d68e2ac39fbd81f151d49fbeaece83ce0e87ae5e9afab8","model":"gpt-5.4","provider":"openai","segment_id":"usage.presets.all","source_path":"ui/src/i18n/locales/zh-CN.ts","src_lang":"en","text":"All","text_hash":"a52ace420f2175d08b1577a1bea5445e36801229c074ef9ed6c55a73401fd9c2","tgt_lang":"zh-CN","translated":"全部","updated_at":"2026-04-05T17:10:55.291Z"}
{"cache_key":"c3a213d6a41a8a5ebcf7968a9262b2586fc75c5db8e1b4bdb8598141ac0954cb","model":"gpt-5.4","provider":"openai","segment_id":"agentTools.channelSource","source_path":"ui/src/i18n/locales/zh-CN.ts","src_lang":"en","text":"Channel: {id}","text_hash":"deeba4ed0001ba82ab20e37ea762c26095e52817c28b99b94e2e5026f88fee6c","tgt_lang":"zh-CN","translated":"频道:{id}","updated_at":"2026-04-06T02:47:42.475Z"}
{"cache_key":"c40f66f06e47020c9f84c1050790bb3fd8234e7a5dc96980f3b976fe701ee947","model":"gpt-5.4","provider":"openai","segment_id":"overview.connection.authDocsLink","source_path":"ui/src/i18n/locales/zh-CN.ts","src_lang":"en","text":"Docs: Control UI auth","text_hash":"2643725608b446e5c8c6810cb458b90d6d2d78437927acc96aa229615b2da336","tgt_lang":"zh-CN","translated":"文档Control UI 身份验证","updated_at":"2026-04-20T06:26:06.957Z"}
{"cache_key":"c42b488dc70707635bea3df488b7d45bb89599141b888b909bc6126702152284","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.stream","source_path":"ui/src/i18n/locales/zh-CN.ts","src_lang":"en","text":"stream","text_hash":"dca83e717b1f64eb141057a7415a330ad1361f51703efa2e4776f40047898a04","tgt_lang":"zh-CN","translated":"流式","updated_at":"2026-04-29T20:12:16.159Z"}

View File

@@ -1,11 +1,19 @@
{
"fallbackKeys": [],
"generatedAt": "2026-05-06T03:19:04.334Z",
"fallbackKeys": [
"usage.presets.last1y",
"usage.presets.last90d",
"usage.scope.family",
"usage.scope.familyHint",
"usage.scope.familyIncluded",
"usage.scope.instance",
"usage.scope.instanceHint"
],
"generatedAt": "2026-05-08T03:32:05.629Z",
"locale": "zh-TW",
"model": "gpt-5.5",
"provider": "openai",
"sourceHash": "c97d50965a8485bb290aa7f158bae5dbadf3642e71bf4712207555f0abea23c2",
"totalKeys": 1017,
"translatedKeys": 1017,
"sourceHash": "b4c8279f087e4a589dd3a6e59c9e06f2686df58f904a678c5a773a0df05fe563",
"totalKeys": 1025,
"translatedKeys": 1018,
"workflow": 1
}

View File

@@ -34,6 +34,7 @@
{"cache_key":"0bb07a7174f5ba69b1f100f5260d199687300e83bf4467863b0860976563bcd3","model":"gpt-5.5","provider":"openai","segment_id":"sessionsView.hideCheckpoints","source_path":"ui/src/i18n/locales/zh-TW.ts","src_lang":"en","text":"Hide checkpoints","text_hash":"b98865f6aa3e2763060bd7d3396568aefd21a78b926098424138058a461c4462","tgt_lang":"zh-TW","translated":"隱藏檢查點","updated_at":"2026-04-29T20:12:27.417Z"}
{"cache_key":"0cfe9e094c979e0c3c402aa94eb0df9ac6f34a2db13e0d7214847283cd4c9012","model":"gpt-5.4","provider":"openai","segment_id":"nodes.binding.execNodeBindingSubtitle","source_path":"ui/src/i18n/locales/zh-TW.ts","src_lang":"en","text":"Pin agents to a specific node when using exec host=node.","text_hash":"62b94f448115db671d89cd6cbb1649576ab8435e99aabee84d4bf32e7882f65e","tgt_lang":"zh-TW","translated":"使用 exec host=node 時,將代理固定到特定節點。","updated_at":"2026-04-06T02:47:40.758Z"}
{"cache_key":"0d28efbe7fb1f552b2e4fe5ce8afabd47c93d585b6806ef693ec9b2aa1bb4a80","model":"gpt-5.4","provider":"openai","segment_id":"nodes.binding.loadConfigHint","source_path":"ui/src/i18n/locales/zh-TW.ts","src_lang":"en","text":"Load config to edit bindings.","text_hash":"075f4d7948e28bf0f85baefbdfe31e6a11a86d94ac38cbc3c100fdf8981c8839","tgt_lang":"zh-TW","translated":"載入設定以編輯綁定。","updated_at":"2026-04-06T02:47:40.758Z"}
{"cache_key":"0dfe5b4d987200f6bf152d3eeada578bfa95b72e94441af1fd8439ec76155220","model":"gpt-5.4","provider":"openai","segment_id":"usage.presets.all","source_path":"ui/src/i18n/locales/zh-TW.ts","src_lang":"en","text":"All","text_hash":"a52ace420f2175d08b1577a1bea5445e36801229c074ef9ed6c55a73401fd9c2","tgt_lang":"zh-TW","translated":"全部","updated_at":"2026-04-05T17:11:19.131Z"}
{"cache_key":"0eaffe6ecf848ee55c8e715eca30bdd7b7dddb4d2caa3a5d7d364d2a41c400ee","model":"gpt-5.4","provider":"openai","segment_id":"usage.query.placeholder","source_path":"ui/src/i18n/locales/zh-TW.ts","src_lang":"en","text":"Filter sessions (e.g. key:agent:main:cron* model:gpt-4o has:errors minTokens:2000)","text_hash":"cba9bff34c8bfb3e2c1c034d6c95355c1770d661b8702435a4ca31cc58623bd7","tgt_lang":"zh-TW","translated":"篩選工作階段(例如 key:agent:main:cron* model:gpt-4o has:errors minTokens:2000","updated_at":"2026-04-05T17:10:38.462Z"}
{"cache_key":"0f627f4e2f22cde5bbb94c971cc1a41950ff1120b04ca51e88bc3cb778a80d7b","model":"gpt-5.4","provider":"openai","segment_id":"overview.pairing.roleUpgradeSummary","source_path":"ui/src/i18n/locales/zh-TW.ts","src_lang":"en","text":"This device is already paired, but the requested role change is waiting for approval.","text_hash":"6be065f7872c9da91207eaac047a8e9d1638e980449baa4746f51c69d1197695","tgt_lang":"zh-TW","translated":"此裝置已完成配對,但所要求的角色變更仍在等待核准。","updated_at":"2026-04-20T08:08:40.806Z"}
{"cache_key":"0ff49ed8eaa19335b58ad35a99d21c34c277e79190b183fe93375a5a5ab551d7","model":"gpt-5.4","provider":"openai","segment_id":"nodes.binding.defaultBinding","source_path":"ui/src/i18n/locales/zh-TW.ts","src_lang":"en","text":"Default binding","text_hash":"ce2cc6f09a11b7087293c651a72a308715d38aee5875150ff00907b9443bad4e","tgt_lang":"zh-TW","translated":"預設綁定","updated_at":"2026-04-06T02:47:40.758Z"}

View File

@@ -699,6 +699,16 @@ export const ar: TranslationMap = {
today: "اليوم",
last7d: "7 أيام",
last30d: "30 يومًا",
last90d: "90d",
last1y: "1y",
all: "الكل",
},
scope: {
instance: "Current instance",
instanceHint: "Show only the active session id for each logical session.",
family: "Historical lineage",
familyHint: "Roll up known rotated transcript-backed session ids.",
familyIncluded: "Historical lineage includes {count} session instances.",
},
filters: {
title: "عوامل التصفية",

View File

@@ -712,6 +712,16 @@ export const de: TranslationMap = {
today: "Heute",
last7d: "7d",
last30d: "30d",
last90d: "90d",
last1y: "1y",
all: "Alle",
},
scope: {
instance: "Current instance",
instanceHint: "Show only the active session id for each logical session.",
family: "Historical lineage",
familyHint: "Roll up known rotated transcript-backed session ids.",
familyIncluded: "Historical lineage includes {count} session instances.",
},
filters: {
title: "Filter",

View File

@@ -701,6 +701,16 @@ export const en: TranslationMap = {
today: "Today",
last7d: "7d",
last30d: "30d",
last90d: "90d",
last1y: "1y",
all: "All",
},
scope: {
instance: "Current instance",
instanceHint: "Show only the active session id for each logical session.",
family: "Historical lineage",
familyHint: "Roll up known rotated transcript-backed session ids.",
familyIncluded: "Historical lineage includes {count} session instances.",
},
filters: {
title: "Filters",

View File

@@ -709,6 +709,16 @@ export const es: TranslationMap = {
today: "Hoy",
last7d: "7d",
last30d: "30d",
last90d: "90d",
last1y: "1y",
all: "Todas",
},
scope: {
instance: "Current instance",
instanceHint: "Show only the active session id for each logical session.",
family: "Historical lineage",
familyHint: "Roll up known rotated transcript-backed session ids.",
familyIncluded: "Historical lineage includes {count} session instances.",
},
filters: {
title: "Filtros",

View File

@@ -707,6 +707,16 @@ export const fa: TranslationMap = {
today: "امروز",
last7d: "۷روز",
last30d: "۳۰روز",
last90d: "90d",
last1y: "1y",
all: "همه",
},
scope: {
instance: "Current instance",
instanceHint: "Show only the active session id for each logical session.",
family: "Historical lineage",
familyHint: "Roll up known rotated transcript-backed session ids.",
familyIncluded: "Historical lineage includes {count} session instances.",
},
filters: {
title: "فیلترها",

View File

@@ -711,6 +711,16 @@ export const fr: TranslationMap = {
today: "Aujourdhui",
last7d: "7 j",
last30d: "30 j",
last90d: "90d",
last1y: "1y",
all: "Tous",
},
scope: {
instance: "Current instance",
instanceHint: "Show only the active session id for each logical session.",
family: "Historical lineage",
familyHint: "Roll up known rotated transcript-backed session ids.",
familyIncluded: "Historical lineage includes {count} session instances.",
},
filters: {
title: "Filtres",

View File

@@ -706,6 +706,16 @@ export const id: TranslationMap = {
today: "Hari ini",
last7d: "7h",
last30d: "30h",
last90d: "90d",
last1y: "1y",
all: "Semua",
},
scope: {
instance: "Current instance",
instanceHint: "Show only the active session id for each logical session.",
family: "Historical lineage",
familyHint: "Roll up known rotated transcript-backed session ids.",
familyIncluded: "Historical lineage includes {count} session instances.",
},
filters: {
title: "Filter",

View File

@@ -709,6 +709,16 @@ export const it: TranslationMap = {
today: "Oggi",
last7d: "7 gg",
last30d: "30 gg",
last90d: "90d",
last1y: "1y",
all: "Tutte",
},
scope: {
instance: "Current instance",
instanceHint: "Show only the active session id for each logical session.",
family: "Historical lineage",
familyHint: "Roll up known rotated transcript-backed session ids.",
familyIncluded: "Historical lineage includes {count} session instances.",
},
filters: {
title: "Filtri",

View File

@@ -708,6 +708,16 @@ export const ja_JP: TranslationMap = {
today: "今日",
last7d: "7日",
last30d: "30日",
last90d: "90d",
last1y: "1y",
all: "すべて",
},
scope: {
instance: "Current instance",
instanceHint: "Show only the active session id for each logical session.",
family: "Historical lineage",
familyHint: "Roll up known rotated transcript-backed session ids.",
familyIncluded: "Historical lineage includes {count} session instances.",
},
filters: {
title: "フィルター",

View File

@@ -705,6 +705,16 @@ export const ko: TranslationMap = {
today: "오늘",
last7d: "7일",
last30d: "30일",
last90d: "90d",
last1y: "1y",
all: "전체",
},
scope: {
instance: "Current instance",
instanceHint: "Show only the active session id for each logical session.",
family: "Historical lineage",
familyHint: "Roll up known rotated transcript-backed session ids.",
familyIncluded: "Historical lineage includes {count} session instances.",
},
filters: {
title: "필터",

View File

@@ -709,6 +709,16 @@ export const nl: TranslationMap = {
today: "Vandaag",
last7d: "7d",
last30d: "30d",
last90d: "90d",
last1y: "1y",
all: "Alle",
},
scope: {
instance: "Current instance",
instanceHint: "Show only the active session id for each logical session.",
family: "Historical lineage",
familyHint: "Roll up known rotated transcript-backed session ids.",
familyIncluded: "Historical lineage includes {count} session instances.",
},
filters: {
title: "Filters",

View File

@@ -709,6 +709,16 @@ export const pl: TranslationMap = {
today: "Dzisiaj",
last7d: "7d",
last30d: "30d",
last90d: "90d",
last1y: "1y",
all: "Wszystkie",
},
scope: {
instance: "Current instance",
instanceHint: "Show only the active session id for each logical session.",
family: "Historical lineage",
familyHint: "Roll up known rotated transcript-backed session ids.",
familyIncluded: "Historical lineage includes {count} session instances.",
},
filters: {
title: "Filtry",

View File

@@ -706,6 +706,16 @@ export const pt_BR: TranslationMap = {
today: "Hoje",
last7d: "7d",
last30d: "30d",
last90d: "90d",
last1y: "1y",
all: "Todas",
},
scope: {
instance: "Current instance",
instanceHint: "Show only the active session id for each logical session.",
family: "Historical lineage",
familyHint: "Roll up known rotated transcript-backed session ids.",
familyIncluded: "Historical lineage includes {count} session instances.",
},
filters: {
title: "Filtros",

View File

@@ -696,6 +696,16 @@ export const th: TranslationMap = {
today: "วันนี้",
last7d: "7 วัน",
last30d: "30 วัน",
last90d: "90d",
last1y: "1y",
all: "ทั้งหมด",
},
scope: {
instance: "Current instance",
instanceHint: "Show only the active session id for each logical session.",
family: "Historical lineage",
familyHint: "Roll up known rotated transcript-backed session ids.",
familyIncluded: "Historical lineage includes {count} session instances.",
},
filters: {
title: "ตัวกรอง",

View File

@@ -711,6 +711,16 @@ export const tr: TranslationMap = {
today: "Bugün",
last7d: "7g",
last30d: "30g",
last90d: "90d",
last1y: "1y",
all: "Tümü",
},
scope: {
instance: "Current instance",
instanceHint: "Show only the active session id for each logical session.",
family: "Historical lineage",
familyHint: "Roll up known rotated transcript-backed session ids.",
familyIncluded: "Historical lineage includes {count} session instances.",
},
filters: {
title: "Filtreler",

View File

@@ -711,6 +711,16 @@ export const uk: TranslationMap = {
today: "Сьогодні",
last7d: "7 дн.",
last30d: "30 дн.",
last90d: "90d",
last1y: "1y",
all: "Усі",
},
scope: {
instance: "Current instance",
instanceHint: "Show only the active session id for each logical session.",
family: "Historical lineage",
familyHint: "Roll up known rotated transcript-backed session ids.",
familyIncluded: "Historical lineage includes {count} session instances.",
},
filters: {
title: "Фільтри",

View File

@@ -704,6 +704,16 @@ export const vi: TranslationMap = {
today: "Hôm nay",
last7d: "7 ngày",
last30d: "30 ngày",
last90d: "90d",
last1y: "1y",
all: "Tất cả",
},
scope: {
instance: "Current instance",
instanceHint: "Show only the active session id for each logical session.",
family: "Historical lineage",
familyHint: "Roll up known rotated transcript-backed session ids.",
familyIncluded: "Historical lineage includes {count} session instances.",
},
filters: {
title: "Bộ lọc",

View File

@@ -696,6 +696,16 @@ export const zh_CN: TranslationMap = {
today: "今天",
last7d: "7天",
last30d: "30天",
last90d: "90d",
last1y: "1y",
all: "全部",
},
scope: {
instance: "Current instance",
instanceHint: "Show only the active session id for each logical session.",
family: "Historical lineage",
familyHint: "Roll up known rotated transcript-backed session ids.",
familyIncluded: "Historical lineage includes {count} session instances.",
},
filters: {
title: "筛选",

View File

@@ -696,6 +696,16 @@ export const zh_TW: TranslationMap = {
today: "今天",
last7d: "7 天",
last30d: "30 天",
last90d: "90d",
last1y: "1y",
all: "全部",
},
scope: {
instance: "Current instance",
instanceHint: "Show only the active session id for each logical session.",
family: "Historical lineage",
familyHint: "Roll up known rotated transcript-backed session ids.",
familyIncluded: "Historical lineage includes {count} session instances.",
},
filters: {
title: "篩選條件",

View File

@@ -1311,6 +1311,12 @@ details.usage-filter-select summary::-webkit-details-marker,
font-size: 11px;
}
.usage-lineage-note {
color: var(--muted);
font-size: 12px;
margin-top: -6px;
}
.session-detail-stats {
display: flex;
flex-wrap: wrap;

View File

@@ -62,6 +62,7 @@ export function renderUsageTab(state: AppViewState) {
filters: {
startDate: state.usageStartDate,
endDate: state.usageEndDate,
scope: state.usageScope,
selectedSessions: state.usageSelectedSessions,
selectedDays: state.usageSelectedDays,
selectedHours: state.usageSelectedHours,
@@ -113,6 +114,15 @@ export function renderUsageTab(state: AppViewState) {
state.usageSelectedSessions = [];
debouncedLoadUsage(state);
},
onScopeChange: (scope) => {
state.usageScope = scope;
state.usageSelectedDays = [];
state.usageSelectedHours = [];
state.usageSelectedSessions = [];
state.usageTimeSeries = null;
state.usageSessionLogs = null;
void loadUsage(state);
},
onRefresh: () => loadUsage(state),
onTimeZoneChange: (zone) => {
state.usageTimeZone = zone;

View File

@@ -285,6 +285,7 @@ export type AppViewState = {
usageError: string | null;
usageStartDate: string;
usageEndDate: string;
usageScope: "instance" | "family";
usageSelectedSessions: string[];
usageSelectedDays: string[];
usageSelectedHours: number[];

View File

@@ -412,6 +412,7 @@ export class OpenClawApp extends LitElement {
const d = new Date();
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
})();
@state() usageScope: "instance" | "family" = "family";
@state() usageSelectedSessions: string[] = [];
@state() usageSelectedDays: string[] = [];
@state() usageSelectedHours: number[] = [];

View File

@@ -20,6 +20,7 @@ function createState(request: RequestFn, overrides: Partial<UsageState> = {}): U
usageError: null,
usageStartDate: "2026-02-16",
usageEndDate: "2026-02-16",
usageScope: "family",
usageSelectedSessions: [],
usageSelectedDays: [],
usageTimeSeries: null,
@@ -39,6 +40,8 @@ function expectSpecificTimezoneCalls(request: ReturnType<typeof vi.fn>, startCal
endDate: "2026-02-16",
mode: "specific",
utcOffset: "UTC+5:30",
groupBy: "family",
includeHistorical: true,
limit: 1000,
includeContextWeight: true,
});
@@ -85,6 +88,8 @@ describe("usage controller date interpretation params", () => {
startDate: "2026-02-16",
endDate: "2026-02-16",
mode: "utc",
groupBy: "family",
includeHistorical: true,
limit: 1000,
includeContextWeight: true,
});
@@ -139,6 +144,8 @@ describe("usage controller date interpretation params", () => {
expect(request).toHaveBeenNthCalledWith(3, "sessions.usage", {
startDate: "2026-02-16",
endDate: "2026-02-16",
groupBy: "family",
includeHistorical: true,
limit: 1000,
includeContextWeight: true,
});
@@ -153,6 +160,8 @@ describe("usage controller date interpretation params", () => {
expect(request).toHaveBeenNthCalledWith(5, "sessions.usage", {
startDate: "2026-02-16",
endDate: "2026-02-16",
groupBy: "family",
includeHistorical: true,
limit: 1000,
includeContextWeight: true,
});
@@ -167,6 +176,69 @@ describe("usage controller date interpretation params", () => {
vi.unstubAllGlobals();
});
it("falls back and remembers compatibility when sessions.usage rejects lineage params", async () => {
const storage = createStorageMock();
vi.stubGlobal("localStorage", storage as unknown as Storage);
vi.spyOn(Date.prototype, "getTimezoneOffset").mockReturnValue(-330);
const request = vi.fn(async (method: string, params?: unknown) => {
if (method === "sessions.usage") {
const record = (params ?? {}) as Record<string, unknown>;
if ("groupBy" in record || "includeHistorical" in record) {
throw new Error(
"invalid sessions.usage params: at root: unexpected property 'groupBy'; at root: unexpected property 'includeHistorical'",
);
}
return { sessions: [] };
}
return {};
});
const state = createState(request, {
usageTimeZone: "local",
settings: { gatewayUrl: "ws://127.0.0.1:18789" },
});
await loadUsage(state);
expectSpecificTimezoneCalls(request, 1);
expect(request).toHaveBeenNthCalledWith(3, "sessions.usage", {
startDate: "2026-02-16",
endDate: "2026-02-16",
mode: "specific",
utcOffset: "UTC+5:30",
limit: 1000,
includeContextWeight: true,
});
expect(request).toHaveBeenNthCalledWith(4, "usage.cost", {
startDate: "2026-02-16",
endDate: "2026-02-16",
mode: "specific",
utcOffset: "UTC+5:30",
});
// Subsequent loads for the same gateway should still send date params but skip lineage params.
await loadUsage(state);
expect(request).toHaveBeenNthCalledWith(5, "sessions.usage", {
startDate: "2026-02-16",
endDate: "2026-02-16",
mode: "specific",
utcOffset: "UTC+5:30",
limit: 1000,
includeContextWeight: true,
});
expect(request).toHaveBeenNthCalledWith(6, "usage.cost", {
startDate: "2026-02-16",
endDate: "2026-02-16",
mode: "specific",
utcOffset: "UTC+5:30",
});
vi.unstubAllGlobals();
});
it("keeps optional loaders resilient when requests fail", async () => {
const request = vi.fn(async (method: string) => {
if (method === "sessions.usage.timeseries" || method === "sessions.usage.logs") {

View File

@@ -17,6 +17,7 @@ export type UsageState = {
usageError: string | null;
usageStartDate: string;
usageEndDate: string;
usageScope: "instance" | "family";
usageSelectedSessions: string[];
usageSelectedDays: string[];
usageTimeSeries: SessionUsageTimeSeries | null;
@@ -30,14 +31,19 @@ export type UsageState = {
};
const LEGACY_USAGE_DATE_PARAMS_STORAGE_KEY = "openclaw.control.usage.date-params.v1";
const LEGACY_USAGE_SCOPE_PARAMS_STORAGE_KEY = "openclaw.control.usage.scope-params.v1";
const LEGACY_USAGE_DATE_PARAMS_MODE_RE = /unexpected property ['"]mode['"]/i;
const LEGACY_USAGE_DATE_PARAMS_OFFSET_RE = /unexpected property ['"]utcoffset['"]/i;
const LEGACY_USAGE_SCOPE_PARAMS_GROUP_BY_RE = /unexpected property ['"]groupby['"]/i;
const LEGACY_USAGE_SCOPE_PARAMS_INCLUDE_HISTORICAL_RE =
/unexpected property ['"]includehistorical['"]/i;
const LEGACY_USAGE_DATE_PARAMS_INVALID_RE = /invalid sessions\.usage params/i;
let legacyUsageDateParamsCache: Set<string> | null = null;
let legacyUsageScopeParamsCache: Set<string> | null = null;
function loadLegacyUsageDateParamsCache(): Set<string> {
const raw = getSafeLocalStorage()?.getItem(LEGACY_USAGE_DATE_PARAMS_STORAGE_KEY);
function loadLegacyGatewayParamCache(storageKey: string): Set<string> {
const raw = getSafeLocalStorage()?.getItem(storageKey);
if (!raw) {
return new Set<string>();
}
@@ -58,10 +64,10 @@ function loadLegacyUsageDateParamsCache(): Set<string> {
}
}
function persistLegacyUsageDateParamsCache(cache: Set<string>) {
function persistLegacyGatewayParamCache(storageKey: string, cache: Set<string>) {
try {
getSafeLocalStorage()?.setItem(
LEGACY_USAGE_DATE_PARAMS_STORAGE_KEY,
storageKey,
JSON.stringify({ unsupportedGatewayKeys: Array.from(cache) }),
);
} catch {
@@ -71,11 +77,20 @@ function persistLegacyUsageDateParamsCache(cache: Set<string>) {
function getLegacyUsageDateParamsCache(): Set<string> {
if (!legacyUsageDateParamsCache) {
legacyUsageDateParamsCache = loadLegacyUsageDateParamsCache();
legacyUsageDateParamsCache = loadLegacyGatewayParamCache(LEGACY_USAGE_DATE_PARAMS_STORAGE_KEY);
}
return legacyUsageDateParamsCache;
}
function getLegacyUsageScopeParamsCache(): Set<string> {
if (!legacyUsageScopeParamsCache) {
legacyUsageScopeParamsCache = loadLegacyGatewayParamCache(
LEGACY_USAGE_SCOPE_PARAMS_STORAGE_KEY,
);
}
return legacyUsageScopeParamsCache;
}
function normalizeGatewayCompatibilityKey(gatewayUrl?: string): string {
const trimmed = gatewayUrl?.trim();
if (!trimmed) {
@@ -99,7 +114,19 @@ function shouldSendLegacyDateInterpretation(state: UsageState): boolean {
function rememberLegacyDateInterpretation(state: UsageState) {
const cache = getLegacyUsageDateParamsCache();
cache.add(normalizeGatewayCompatibilityKey(state.settings?.gatewayUrl));
persistLegacyUsageDateParamsCache(cache);
persistLegacyGatewayParamCache(LEGACY_USAGE_DATE_PARAMS_STORAGE_KEY, cache);
}
function shouldSendLegacyUsageScopeParams(state: UsageState): boolean {
return !getLegacyUsageScopeParamsCache().has(
normalizeGatewayCompatibilityKey(state.settings?.gatewayUrl),
);
}
function rememberLegacyUsageScopeParams(state: UsageState) {
const cache = getLegacyUsageScopeParamsCache();
cache.add(normalizeGatewayCompatibilityKey(state.settings?.gatewayUrl));
persistLegacyGatewayParamCache(LEGACY_USAGE_SCOPE_PARAMS_STORAGE_KEY, cache);
}
function isLegacyDateInterpretationUnsupportedError(err: unknown): boolean {
@@ -111,6 +138,15 @@ function isLegacyDateInterpretationUnsupportedError(err: unknown): boolean {
);
}
function isLegacyUsageScopeUnsupportedError(err: unknown): boolean {
const message = toErrorMessage(err);
return (
LEGACY_USAGE_DATE_PARAMS_INVALID_RE.test(message) &&
(LEGACY_USAGE_SCOPE_PARAMS_GROUP_BY_RE.test(message) ||
LEGACY_USAGE_SCOPE_PARAMS_INCLUDE_HISTORICAL_RE.test(message))
);
}
const formatUtcOffset = (timezoneOffsetMinutes: number): string => {
// `Date#getTimezoneOffset()` is minutes to add to local time to reach UTC.
// Convert to UTC±H[:MM] where positive means east of UTC.
@@ -177,15 +213,22 @@ export async function loadUsage(
try {
const startDate = overrides?.startDate ?? state.usageStartDate;
const endDate = overrides?.endDate ?? state.usageEndDate;
const runUsageRequests = (includeDateInterpretation: boolean) => {
const runUsageRequests = (includeDateInterpretation: boolean, includeUsageScope: boolean) => {
const dateInterpretation = includeDateInterpretation
? buildDateInterpretationParams(state.usageTimeZone)
: undefined;
const usageScopeParams = includeUsageScope
? {
groupBy: state.usageScope,
includeHistorical: state.usageScope === "family",
}
: undefined;
return Promise.all([
client.request("sessions.usage", {
startDate,
endDate,
...dateInterpretation,
...usageScopeParams,
limit: 1000, // Cap at 1000 sessions
includeContextWeight: true,
}),
@@ -197,18 +240,31 @@ export async function loadUsage(
]);
};
const includeDateInterpretation = shouldSendLegacyDateInterpretation(state);
try {
const [sessionsRes, costRes] = await runUsageRequests(includeDateInterpretation);
applyUsageResults(state, sessionsRes, costRes);
} catch (err) {
if (includeDateInterpretation && isLegacyDateInterpretationUnsupportedError(err)) {
// Older gateways reject `mode`/`utcOffset` in `sessions.usage`.
// Remember this per gateway and retry once without those fields.
rememberLegacyDateInterpretation(state);
const [sessionsRes, costRes] = await runUsageRequests(false);
let includeDateInterpretation = shouldSendLegacyDateInterpretation(state);
let includeUsageScope = shouldSendLegacyUsageScopeParams(state);
while (true) {
try {
const [sessionsRes, costRes] = await runUsageRequests(
includeDateInterpretation,
includeUsageScope,
);
applyUsageResults(state, sessionsRes, costRes);
} else {
break;
} catch (err) {
if (includeUsageScope && isLegacyUsageScopeUnsupportedError(err)) {
// Older gateways reject `groupBy`/`includeHistorical` in `sessions.usage`.
// Remember this per gateway and retry with instance-compatible params.
rememberLegacyUsageScopeParams(state);
includeUsageScope = false;
continue;
}
if (includeDateInterpretation && isLegacyDateInterpretationUnsupportedError(err)) {
// Older gateways reject `mode`/`utcOffset` in `sessions.usage`.
// Remember this per gateway and retry once without those fields.
rememberLegacyDateInterpretation(state);
includeDateInterpretation = false;
continue;
}
throw err;
}
}
@@ -230,11 +286,15 @@ export const __test = {
buildDateInterpretationParams,
toErrorMessage,
isLegacyDateInterpretationUnsupportedError,
isLegacyUsageScopeUnsupportedError,
normalizeGatewayCompatibilityKey,
shouldSendLegacyDateInterpretation,
rememberLegacyDateInterpretation,
shouldSendLegacyUsageScopeParams,
rememberLegacyUsageScopeParams,
resetLegacyUsageDateParamsCache: () => {
legacyUsageDateParamsCache = null;
legacyUsageScopeParamsCache = null;
},
};

View File

@@ -301,6 +301,15 @@ function renderSessionDetailPanel(
×
</button>
</div>
${session.scope === "family" && session.includedSessionIds?.length
? html`
<div class="usage-lineage-note">
${t("usage.scope.familyIncluded", {
count: String(session.includedSessionIds.length),
})}
</div>
`
: nothing}
<div class="session-detail-content">
${renderSessionSummary(
session,

View File

@@ -316,6 +316,8 @@ export function renderUsage(props: UsageProps) {
{ label: t("usage.presets.today"), days: 1 },
{ label: t("usage.presets.last7d"), days: 7 },
{ label: t("usage.presets.last30d"), days: 30 },
{ label: t("usage.presets.last90d"), days: 90 },
{ label: t("usage.presets.last1y"), days: 365 },
];
const applyPreset = (days: number) => {
const end = new Date();
@@ -324,6 +326,10 @@ export function renderUsage(props: UsageProps) {
filterActions.onStartDateChange(formatIsoDate(start));
filterActions.onEndDateChange(formatIsoDate(end));
};
const applyAllRange = () => {
filterActions.onStartDateChange("1970-01-01");
filterActions.onEndDateChange(formatIsoDate(new Date()));
};
const renderFilterSelect = (key: string, label: string, options: string[]) => {
if (options.length === 0) {
return nothing;
@@ -550,6 +556,7 @@ export function renderUsage(props: UsageProps) {
</button>
`,
)}
<button class="btn btn--sm" @click=${applyAllRange}>${t("usage.presets.all")}</button>
</div>
<div class="usage-date-range">
<input
@@ -585,6 +592,22 @@ export function renderUsage(props: UsageProps) {
<option value="local">${t("usage.filters.timeZoneLocal")}</option>
<option value="utc">${t("usage.filters.timeZoneUtc")}</option>
</select>
<div class="chart-toggle">
<button
class="btn btn--sm toggle-btn ${filters.scope === "instance" ? "active" : ""}"
title=${t("usage.scope.instanceHint")}
@click=${() => filterActions.onScopeChange("instance")}
>
${t("usage.scope.instance")}
</button>
<button
class="btn btn--sm toggle-btn ${filters.scope === "family" ? "active" : ""}"
title=${t("usage.scope.familyHint")}
@click=${() => filterActions.onScopeChange("family")}
>
${t("usage.scope.family")}
</button>
</div>
<div class="chart-toggle">
<button
class="btn btn--sm toggle-btn ${isTokenMode ? "active" : ""}"

View File

@@ -37,6 +37,7 @@ export type UsageDataState = {
export type UsageFilterState = {
startDate: string;
endDate: string;
scope: "instance" | "family";
selectedSessions: string[]; // Support multiple session selection
selectedDays: string[]; // Support multiple day selection
selectedHours: number[]; // Support multiple hour selection
@@ -79,6 +80,7 @@ export type UsageCallbacks = {
filters: {
onStartDateChange: (date: string) => void;
onEndDateChange: (date: string) => void;
onScopeChange: (scope: "instance" | "family") => void;
onRefresh: () => void;
onTimeZoneChange: (zone: "local" | "utc") => void;
onToggleHeaderPinned: () => void;