diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d5395b9ea8..2564a095082 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift b/apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift index 70aca00120c..372a6bb752e 100644 --- a/apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift +++ b/apps/shared/OpenClawKit/Sources/OpenClawProtocol/GatewayModels.swift @@ -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" diff --git a/src/auto-reply/reply/agent-runner-session-reset.ts b/src/auto-reply/reply/agent-runner-session-reset.ts index 55df3cbc5a1..b9453c57006 100644 --- a/src/auto-reply/reply/agent-runner-session-reset.ts +++ b/src/auto-reply/reply/agent-runner-session-reset.ts @@ -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, diff --git a/src/auto-reply/reply/session-updates.ts b/src/auto-reply/reply/session-updates.ts index 2e2f09edcbb..42fa61f8139 100644 --- a/src/auto-reply/reply/session-updates.ts +++ b/src/auto-reply/reply/session-updates.ts @@ -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; } diff --git a/src/auto-reply/reply/session.test.ts b/src/auto-reply/reply/session.test.ts index d044335915c..bf806efb442 100644 --- a/src/auto-reply/reply/session.test.ts +++ b/src/auto-reply/reply/session.test.ts @@ -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"; diff --git a/src/auto-reply/reply/session.ts b/src/auto-reply/reply/session.ts index 78b82029196..0ab3fc0db5c 100644 --- a/src/auto-reply/reply/session.ts +++ b/src/auto-reply/reply/session.ts @@ -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, diff --git a/src/config/sessions/types.ts b/src/config/sessions/types.ts index e9b0a16e8eb..3786f40ab98 100644 --- a/src/config/sessions/types.ts +++ b/src/config/sessions/types.ts @@ -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. */ diff --git a/src/gateway/protocol/schema/sessions.ts b/src/gateway/protocol/schema/sessions.ts index 172c5323c51..626ab842ce5 100644 --- a/src/gateway/protocol/schema/sessions.ts +++ b/src/gateway/protocol/schema/sessions.ts @@ -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). */ diff --git a/src/gateway/server-methods/usage.sessions-usage.test.ts b/src/gateway/server-methods/usage.sessions-usage.test.ts index 2cb0b938ef7..3e7b8f1c96b 100644 --- a/src/gateway/server-methods/usage.sessions-usage.test.ts +++ b/src/gateway/server-methods/usage.sessions-usage.test.ts @@ -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-")); diff --git a/src/gateway/server-methods/usage.ts b/src/gateway/server-methods/usage.ts index 38252197df7..230e847de5a 100644 --- a/src/gateway/server-methods/usage.ts +++ b/src/gateway/server-methods/usage.ts @@ -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, @@ -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[] { + 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( + left: T[] | undefined, + right: T[] | undefined, + fields: Array, +): T[] | undefined { + const map = new Map(); + 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( + left: T[] | undefined, + right: T[] | undefined, + fields: Array, +): T[] | undefined { + const map = new Map(); + 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(); + 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(); + 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[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[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(); + 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, diff --git a/src/plugins/session-entry-slot-keys.ts b/src/plugins/session-entry-slot-keys.ts index 90cec341cd7..c761018f681 100644 --- a/src/plugins/session-entry-slot-keys.ts +++ b/src/plugins/session-entry-slot-keys.ts @@ -48,6 +48,8 @@ const SESSION_ENTRY_RESERVED_SLOT_KEY_LIST = [ "execAsk", "execNode", "responseUsage", + "usageFamilyKey", + "usageFamilySessionIds", "providerOverride", "modelOverride", "agentRuntimeOverride", diff --git a/src/shared/usage-types.ts b/src/shared/usage-types.ts index 0a00982aa62..a9cdc2f8b42 100644 --- a/src/shared/usage-types.ts +++ b/src/shared/usage-types.ts @@ -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; diff --git a/ui/src/i18n/.i18n/ar.meta.json b/ui/src/i18n/.i18n/ar.meta.json index d3277d6ef24..aed5c96f853 100644 --- a/ui/src/i18n/.i18n/ar.meta.json +++ b/ui/src/i18n/.i18n/ar.meta.json @@ -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 } diff --git a/ui/src/i18n/.i18n/ar.tm.jsonl b/ui/src/i18n/.i18n/ar.tm.jsonl index defe89b2b8d..b45ed6fe730 100644 --- a/ui/src/i18n/.i18n/ar.tm.jsonl +++ b/ui/src/i18n/.i18n/ar.tm.jsonl @@ -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"} diff --git a/ui/src/i18n/.i18n/de.meta.json b/ui/src/i18n/.i18n/de.meta.json index 1eb846904b4..d781bcbb4a2 100644 --- a/ui/src/i18n/.i18n/de.meta.json +++ b/ui/src/i18n/.i18n/de.meta.json @@ -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 } diff --git a/ui/src/i18n/.i18n/de.tm.jsonl b/ui/src/i18n/.i18n/de.tm.jsonl index 57a197b50e8..0bacb545a68 100644 --- a/ui/src/i18n/.i18n/de.tm.jsonl +++ b/ui/src/i18n/.i18n/de.tm.jsonl @@ -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"} diff --git a/ui/src/i18n/.i18n/es.meta.json b/ui/src/i18n/.i18n/es.meta.json index 0599fa4bf7f..5e91db8bbc9 100644 --- a/ui/src/i18n/.i18n/es.meta.json +++ b/ui/src/i18n/.i18n/es.meta.json @@ -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 } diff --git a/ui/src/i18n/.i18n/es.tm.jsonl b/ui/src/i18n/.i18n/es.tm.jsonl index 45ba2d04c22..70ed80e4614 100644 --- a/ui/src/i18n/.i18n/es.tm.jsonl +++ b/ui/src/i18n/.i18n/es.tm.jsonl @@ -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"} diff --git a/ui/src/i18n/.i18n/fa.meta.json b/ui/src/i18n/.i18n/fa.meta.json index ed64151c494..e93cc2d69aa 100644 --- a/ui/src/i18n/.i18n/fa.meta.json +++ b/ui/src/i18n/.i18n/fa.meta.json @@ -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 } diff --git a/ui/src/i18n/.i18n/fa.tm.jsonl b/ui/src/i18n/.i18n/fa.tm.jsonl index 6cc1c6c0242..7f1d60eb25a 100644 --- a/ui/src/i18n/.i18n/fa.tm.jsonl +++ b/ui/src/i18n/.i18n/fa.tm.jsonl @@ -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"} diff --git a/ui/src/i18n/.i18n/fr.meta.json b/ui/src/i18n/.i18n/fr.meta.json index a007043c443..09b26978640 100644 --- a/ui/src/i18n/.i18n/fr.meta.json +++ b/ui/src/i18n/.i18n/fr.meta.json @@ -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 } diff --git a/ui/src/i18n/.i18n/fr.tm.jsonl b/ui/src/i18n/.i18n/fr.tm.jsonl index d446f5e3724..9d346e312bd 100644 --- a/ui/src/i18n/.i18n/fr.tm.jsonl +++ b/ui/src/i18n/.i18n/fr.tm.jsonl @@ -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"} diff --git a/ui/src/i18n/.i18n/id.meta.json b/ui/src/i18n/.i18n/id.meta.json index c5e886fc4b3..26c78ee330e 100644 --- a/ui/src/i18n/.i18n/id.meta.json +++ b/ui/src/i18n/.i18n/id.meta.json @@ -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 } diff --git a/ui/src/i18n/.i18n/id.tm.jsonl b/ui/src/i18n/.i18n/id.tm.jsonl index 5964a345451..eb6a3ba6e4e 100644 --- a/ui/src/i18n/.i18n/id.tm.jsonl +++ b/ui/src/i18n/.i18n/id.tm.jsonl @@ -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"} diff --git a/ui/src/i18n/.i18n/it.meta.json b/ui/src/i18n/.i18n/it.meta.json index 025bf39206c..175a269f4c8 100644 --- a/ui/src/i18n/.i18n/it.meta.json +++ b/ui/src/i18n/.i18n/it.meta.json @@ -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 } diff --git a/ui/src/i18n/.i18n/it.tm.jsonl b/ui/src/i18n/.i18n/it.tm.jsonl index 68bd2fdba1a..461b8030e0b 100644 --- a/ui/src/i18n/.i18n/it.tm.jsonl +++ b/ui/src/i18n/.i18n/it.tm.jsonl @@ -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"} diff --git a/ui/src/i18n/.i18n/ja-JP.meta.json b/ui/src/i18n/.i18n/ja-JP.meta.json index 154ba799e43..64648c964db 100644 --- a/ui/src/i18n/.i18n/ja-JP.meta.json +++ b/ui/src/i18n/.i18n/ja-JP.meta.json @@ -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 } diff --git a/ui/src/i18n/.i18n/ja-JP.tm.jsonl b/ui/src/i18n/.i18n/ja-JP.tm.jsonl index 1699bc31861..daf4ab11da7 100644 --- a/ui/src/i18n/.i18n/ja-JP.tm.jsonl +++ b/ui/src/i18n/.i18n/ja-JP.tm.jsonl @@ -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"} diff --git a/ui/src/i18n/.i18n/ko.meta.json b/ui/src/i18n/.i18n/ko.meta.json index 2adc971177b..24694ba5b35 100644 --- a/ui/src/i18n/.i18n/ko.meta.json +++ b/ui/src/i18n/.i18n/ko.meta.json @@ -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 } diff --git a/ui/src/i18n/.i18n/ko.tm.jsonl b/ui/src/i18n/.i18n/ko.tm.jsonl index f8ecda01cb3..e72226dd730 100644 --- a/ui/src/i18n/.i18n/ko.tm.jsonl +++ b/ui/src/i18n/.i18n/ko.tm.jsonl @@ -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"} diff --git a/ui/src/i18n/.i18n/nl.meta.json b/ui/src/i18n/.i18n/nl.meta.json index 8c184059a42..646d820fdca 100644 --- a/ui/src/i18n/.i18n/nl.meta.json +++ b/ui/src/i18n/.i18n/nl.meta.json @@ -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 } diff --git a/ui/src/i18n/.i18n/nl.tm.jsonl b/ui/src/i18n/.i18n/nl.tm.jsonl index ef7cf4562e5..6ec6ec7f59a 100644 --- a/ui/src/i18n/.i18n/nl.tm.jsonl +++ b/ui/src/i18n/.i18n/nl.tm.jsonl @@ -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"} diff --git a/ui/src/i18n/.i18n/pl.meta.json b/ui/src/i18n/.i18n/pl.meta.json index 81190cb4856..f3c7bfe3a5a 100644 --- a/ui/src/i18n/.i18n/pl.meta.json +++ b/ui/src/i18n/.i18n/pl.meta.json @@ -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 } diff --git a/ui/src/i18n/.i18n/pl.tm.jsonl b/ui/src/i18n/.i18n/pl.tm.jsonl index 3603c15852c..f67e7e6b3ba 100644 --- a/ui/src/i18n/.i18n/pl.tm.jsonl +++ b/ui/src/i18n/.i18n/pl.tm.jsonl @@ -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"} diff --git a/ui/src/i18n/.i18n/pt-BR.meta.json b/ui/src/i18n/.i18n/pt-BR.meta.json index 98517113ab4..1ca282ce3c3 100644 --- a/ui/src/i18n/.i18n/pt-BR.meta.json +++ b/ui/src/i18n/.i18n/pt-BR.meta.json @@ -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 } diff --git a/ui/src/i18n/.i18n/pt-BR.tm.jsonl b/ui/src/i18n/.i18n/pt-BR.tm.jsonl index 6261d74f7d1..65e8d24d17e 100644 --- a/ui/src/i18n/.i18n/pt-BR.tm.jsonl +++ b/ui/src/i18n/.i18n/pt-BR.tm.jsonl @@ -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"} diff --git a/ui/src/i18n/.i18n/th.meta.json b/ui/src/i18n/.i18n/th.meta.json index b958b32813c..9e2fba9e983 100644 --- a/ui/src/i18n/.i18n/th.meta.json +++ b/ui/src/i18n/.i18n/th.meta.json @@ -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 } diff --git a/ui/src/i18n/.i18n/th.tm.jsonl b/ui/src/i18n/.i18n/th.tm.jsonl index 29df2cd05a9..e966eee6c27 100644 --- a/ui/src/i18n/.i18n/th.tm.jsonl +++ b/ui/src/i18n/.i18n/th.tm.jsonl @@ -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"} diff --git a/ui/src/i18n/.i18n/tr.meta.json b/ui/src/i18n/.i18n/tr.meta.json index 1d6781dc8f8..ad40ae4b116 100644 --- a/ui/src/i18n/.i18n/tr.meta.json +++ b/ui/src/i18n/.i18n/tr.meta.json @@ -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 } diff --git a/ui/src/i18n/.i18n/tr.tm.jsonl b/ui/src/i18n/.i18n/tr.tm.jsonl index 7d3ac41e23f..fa4b537b9a9 100644 --- a/ui/src/i18n/.i18n/tr.tm.jsonl +++ b/ui/src/i18n/.i18n/tr.tm.jsonl @@ -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"} diff --git a/ui/src/i18n/.i18n/uk.meta.json b/ui/src/i18n/.i18n/uk.meta.json index 9dd0a6e2c50..acd280fab3a 100644 --- a/ui/src/i18n/.i18n/uk.meta.json +++ b/ui/src/i18n/.i18n/uk.meta.json @@ -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 } diff --git a/ui/src/i18n/.i18n/uk.tm.jsonl b/ui/src/i18n/.i18n/uk.tm.jsonl index b152174421f..f7e5da92cdc 100644 --- a/ui/src/i18n/.i18n/uk.tm.jsonl +++ b/ui/src/i18n/.i18n/uk.tm.jsonl @@ -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"} diff --git a/ui/src/i18n/.i18n/vi.meta.json b/ui/src/i18n/.i18n/vi.meta.json index a7844ac2880..6ccac182d46 100644 --- a/ui/src/i18n/.i18n/vi.meta.json +++ b/ui/src/i18n/.i18n/vi.meta.json @@ -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 } diff --git a/ui/src/i18n/.i18n/vi.tm.jsonl b/ui/src/i18n/.i18n/vi.tm.jsonl index 2b369b03c43..7d37815999a 100644 --- a/ui/src/i18n/.i18n/vi.tm.jsonl +++ b/ui/src/i18n/.i18n/vi.tm.jsonl @@ -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"} diff --git a/ui/src/i18n/.i18n/zh-CN.meta.json b/ui/src/i18n/.i18n/zh-CN.meta.json index f57db0edec4..00727175187 100644 --- a/ui/src/i18n/.i18n/zh-CN.meta.json +++ b/ui/src/i18n/.i18n/zh-CN.meta.json @@ -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 } diff --git a/ui/src/i18n/.i18n/zh-CN.tm.jsonl b/ui/src/i18n/.i18n/zh-CN.tm.jsonl index 4776a731735..249bc3aa6c8 100644 --- a/ui/src/i18n/.i18n/zh-CN.tm.jsonl +++ b/ui/src/i18n/.i18n/zh-CN.tm.jsonl @@ -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"} diff --git a/ui/src/i18n/.i18n/zh-TW.meta.json b/ui/src/i18n/.i18n/zh-TW.meta.json index f90d31ea1a1..8c7223feaf2 100644 --- a/ui/src/i18n/.i18n/zh-TW.meta.json +++ b/ui/src/i18n/.i18n/zh-TW.meta.json @@ -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 } diff --git a/ui/src/i18n/.i18n/zh-TW.tm.jsonl b/ui/src/i18n/.i18n/zh-TW.tm.jsonl index dd7fae7e2e3..6fe8fa1c109 100644 --- a/ui/src/i18n/.i18n/zh-TW.tm.jsonl +++ b/ui/src/i18n/.i18n/zh-TW.tm.jsonl @@ -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"} diff --git a/ui/src/i18n/locales/ar.ts b/ui/src/i18n/locales/ar.ts index 14fcb24389b..72948843187 100644 --- a/ui/src/i18n/locales/ar.ts +++ b/ui/src/i18n/locales/ar.ts @@ -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: "عوامل التصفية", diff --git a/ui/src/i18n/locales/de.ts b/ui/src/i18n/locales/de.ts index 60d68611d57..90fd415315c 100644 --- a/ui/src/i18n/locales/de.ts +++ b/ui/src/i18n/locales/de.ts @@ -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", diff --git a/ui/src/i18n/locales/en.ts b/ui/src/i18n/locales/en.ts index 84e03ad18be..5dacd0c35c1 100644 --- a/ui/src/i18n/locales/en.ts +++ b/ui/src/i18n/locales/en.ts @@ -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", diff --git a/ui/src/i18n/locales/es.ts b/ui/src/i18n/locales/es.ts index b5b5428f4ae..f26ec8de452 100644 --- a/ui/src/i18n/locales/es.ts +++ b/ui/src/i18n/locales/es.ts @@ -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", diff --git a/ui/src/i18n/locales/fa.ts b/ui/src/i18n/locales/fa.ts index 4608fe19861..c098d36a57a 100644 --- a/ui/src/i18n/locales/fa.ts +++ b/ui/src/i18n/locales/fa.ts @@ -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: "فیلترها", diff --git a/ui/src/i18n/locales/fr.ts b/ui/src/i18n/locales/fr.ts index 1baa4b860c7..53f3d3a74de 100644 --- a/ui/src/i18n/locales/fr.ts +++ b/ui/src/i18n/locales/fr.ts @@ -711,6 +711,16 @@ export const fr: TranslationMap = { today: "Aujourd’hui", 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", diff --git a/ui/src/i18n/locales/id.ts b/ui/src/i18n/locales/id.ts index 4f419bf01ca..34e938c1ef1 100644 --- a/ui/src/i18n/locales/id.ts +++ b/ui/src/i18n/locales/id.ts @@ -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", diff --git a/ui/src/i18n/locales/it.ts b/ui/src/i18n/locales/it.ts index 1fdd6b09c32..244278031d5 100644 --- a/ui/src/i18n/locales/it.ts +++ b/ui/src/i18n/locales/it.ts @@ -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", diff --git a/ui/src/i18n/locales/ja-JP.ts b/ui/src/i18n/locales/ja-JP.ts index 5e4e6a1eb48..557df44a0fe 100644 --- a/ui/src/i18n/locales/ja-JP.ts +++ b/ui/src/i18n/locales/ja-JP.ts @@ -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: "フィルター", diff --git a/ui/src/i18n/locales/ko.ts b/ui/src/i18n/locales/ko.ts index fa6070e7771..b4e2ca1fd96 100644 --- a/ui/src/i18n/locales/ko.ts +++ b/ui/src/i18n/locales/ko.ts @@ -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: "필터", diff --git a/ui/src/i18n/locales/nl.ts b/ui/src/i18n/locales/nl.ts index eaea54914ef..a478edd8607 100644 --- a/ui/src/i18n/locales/nl.ts +++ b/ui/src/i18n/locales/nl.ts @@ -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", diff --git a/ui/src/i18n/locales/pl.ts b/ui/src/i18n/locales/pl.ts index 1f079239493..df0fddd6bb4 100644 --- a/ui/src/i18n/locales/pl.ts +++ b/ui/src/i18n/locales/pl.ts @@ -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", diff --git a/ui/src/i18n/locales/pt-BR.ts b/ui/src/i18n/locales/pt-BR.ts index ce367d0d437..515ed43fdd4 100644 --- a/ui/src/i18n/locales/pt-BR.ts +++ b/ui/src/i18n/locales/pt-BR.ts @@ -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", diff --git a/ui/src/i18n/locales/th.ts b/ui/src/i18n/locales/th.ts index 886e38ca0bc..6cdbfbc1e97 100644 --- a/ui/src/i18n/locales/th.ts +++ b/ui/src/i18n/locales/th.ts @@ -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: "ตัวกรอง", diff --git a/ui/src/i18n/locales/tr.ts b/ui/src/i18n/locales/tr.ts index 75a60b6d388..cd1eb850f65 100644 --- a/ui/src/i18n/locales/tr.ts +++ b/ui/src/i18n/locales/tr.ts @@ -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", diff --git a/ui/src/i18n/locales/uk.ts b/ui/src/i18n/locales/uk.ts index 5b186c46806..5aac2fdb94d 100644 --- a/ui/src/i18n/locales/uk.ts +++ b/ui/src/i18n/locales/uk.ts @@ -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: "Фільтри", diff --git a/ui/src/i18n/locales/vi.ts b/ui/src/i18n/locales/vi.ts index 2148c5f76c3..e389c70190b 100644 --- a/ui/src/i18n/locales/vi.ts +++ b/ui/src/i18n/locales/vi.ts @@ -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", diff --git a/ui/src/i18n/locales/zh-CN.ts b/ui/src/i18n/locales/zh-CN.ts index fa542dc70ca..be75cbd6de2 100644 --- a/ui/src/i18n/locales/zh-CN.ts +++ b/ui/src/i18n/locales/zh-CN.ts @@ -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: "筛选", diff --git a/ui/src/i18n/locales/zh-TW.ts b/ui/src/i18n/locales/zh-TW.ts index 51b6d191fcd..46f8f94bead 100644 --- a/ui/src/i18n/locales/zh-TW.ts +++ b/ui/src/i18n/locales/zh-TW.ts @@ -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: "篩選條件", diff --git a/ui/src/styles/usage.css b/ui/src/styles/usage.css index 91608dcc965..75b056089c1 100644 --- a/ui/src/styles/usage.css +++ b/ui/src/styles/usage.css @@ -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; diff --git a/ui/src/ui/app-render-usage-tab.ts b/ui/src/ui/app-render-usage-tab.ts index 4b57b583241..105d6f40b82 100644 --- a/ui/src/ui/app-render-usage-tab.ts +++ b/ui/src/ui/app-render-usage-tab.ts @@ -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; diff --git a/ui/src/ui/app-view-state.ts b/ui/src/ui/app-view-state.ts index 5b57748c1a6..37902e88ce2 100644 --- a/ui/src/ui/app-view-state.ts +++ b/ui/src/ui/app-view-state.ts @@ -285,6 +285,7 @@ export type AppViewState = { usageError: string | null; usageStartDate: string; usageEndDate: string; + usageScope: "instance" | "family"; usageSelectedSessions: string[]; usageSelectedDays: string[]; usageSelectedHours: number[]; diff --git a/ui/src/ui/app.ts b/ui/src/ui/app.ts index e0b3ca780ea..a950ea0bd88 100644 --- a/ui/src/ui/app.ts +++ b/ui/src/ui/app.ts @@ -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[] = []; diff --git a/ui/src/ui/controllers/usage.node.test.ts b/ui/src/ui/controllers/usage.node.test.ts index 378c2db4c55..95a1895bde2 100644 --- a/ui/src/ui/controllers/usage.node.test.ts +++ b/ui/src/ui/controllers/usage.node.test.ts @@ -20,6 +20,7 @@ function createState(request: RequestFn, overrides: Partial = {}): 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, 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; + 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") { diff --git a/ui/src/ui/controllers/usage.ts b/ui/src/ui/controllers/usage.ts index 71ddb096601..bfb5beab8a1 100644 --- a/ui/src/ui/controllers/usage.ts +++ b/ui/src/ui/controllers/usage.ts @@ -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 | null = null; +let legacyUsageScopeParamsCache: Set | null = null; -function loadLegacyUsageDateParamsCache(): Set { - const raw = getSafeLocalStorage()?.getItem(LEGACY_USAGE_DATE_PARAMS_STORAGE_KEY); +function loadLegacyGatewayParamCache(storageKey: string): Set { + const raw = getSafeLocalStorage()?.getItem(storageKey); if (!raw) { return new Set(); } @@ -58,10 +64,10 @@ function loadLegacyUsageDateParamsCache(): Set { } } -function persistLegacyUsageDateParamsCache(cache: Set) { +function persistLegacyGatewayParamCache(storageKey: string, cache: Set) { 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) { function getLegacyUsageDateParamsCache(): Set { if (!legacyUsageDateParamsCache) { - legacyUsageDateParamsCache = loadLegacyUsageDateParamsCache(); + legacyUsageDateParamsCache = loadLegacyGatewayParamCache(LEGACY_USAGE_DATE_PARAMS_STORAGE_KEY); } return legacyUsageDateParamsCache; } +function getLegacyUsageScopeParamsCache(): Set { + 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; }, }; diff --git a/ui/src/ui/views/usage-render-details.ts b/ui/src/ui/views/usage-render-details.ts index 703715df1a2..51f1143475b 100644 --- a/ui/src/ui/views/usage-render-details.ts +++ b/ui/src/ui/views/usage-render-details.ts @@ -301,6 +301,15 @@ function renderSessionDetailPanel( × + ${session.scope === "family" && session.includedSessionIds?.length + ? html` +
+ ${t("usage.scope.familyIncluded", { + count: String(session.includedSessionIds.length), + })} +
+ ` + : nothing}
${renderSessionSummary( session, diff --git a/ui/src/ui/views/usage.ts b/ui/src/ui/views/usage.ts index 0f306b087c0..9265b8858a3 100644 --- a/ui/src/ui/views/usage.ts +++ b/ui/src/ui/views/usage.ts @@ -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) { `, )} +
${t("usage.filters.timeZoneLocal")} +
+ + +