@agent-native/core 0.84.67 → 0.85.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +20 -0
- package/corpus/core/docs/content/locales/ar-SA/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/de-DE/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/es-ES/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/fr-FR/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/hi-IN/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/ja-JP/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/ko-KR/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/pt-BR/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/zh-CN/tracking.mdx +55 -0
- package/corpus/core/docs/content/locales/zh-TW/tracking.mdx +55 -0
- package/corpus/core/docs/content/tracking.mdx +2 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/action-continuation-guidance.ts +1 -1
- package/corpus/core/src/agent/production-agent.ts +222 -38
- package/corpus/core/src/agent/run-manager.ts +79 -0
- package/corpus/core/src/agent/run-store.ts +122 -8
- package/corpus/core/src/agent/types.ts +13 -0
- package/corpus/core/src/client/RunStuckBanner.tsx +14 -0
- package/corpus/core/src/client/agent-chat-adapter.ts +475 -0
- package/corpus/core/src/client/analytics.ts +2 -0
- package/corpus/core/src/client/error-format.ts +4 -0
- package/corpus/core/src/client/extensions/ExtensionsSidebarSection.tsx +1 -1
- package/corpus/core/src/client/session-replay.ts +774 -2
- package/corpus/core/src/client/sse-event-processor.ts +65 -9
- package/corpus/core/src/client/use-run-stuck-detection.ts +31 -7
- package/corpus/core/src/db/runtime-diagnostics.ts +331 -0
- package/corpus/core/src/server/agent-chat-plugin.ts +115 -2
- package/corpus/core/src/server/core-routes-plugin.ts +91 -4
- package/corpus/core/src/server/http-response-telemetry.ts +189 -0
- package/corpus/core/src/server/self-dispatch.ts +29 -0
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +53 -1
- package/corpus/templates/analytics/AGENTS.md +13 -0
- package/corpus/templates/analytics/actions/view-screen.ts +25 -0
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +20 -0
- package/corpus/templates/analytics/app/global.css +55 -0
- package/corpus/templates/analytics/app/i18n/zh-TW.ts +100 -1
- package/corpus/templates/analytics/app/i18n-data.ts +1226 -0
- package/corpus/templates/analytics/app/pages/Settings.tsx +3 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/EmailReportDialog.tsx +182 -144
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +11 -18
- package/corpus/templates/analytics/app/pages/analyses/AnalysisDetail.tsx +11 -18
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +49 -1
- package/corpus/templates/analytics/app/pages/sessions/SessionDevToolsPanel.tsx +557 -0
- package/corpus/templates/analytics/app/pages/sessions/session-replay-devtools.ts +274 -0
- package/corpus/templates/analytics/app/pages/settings/AlertRulesSettingsCard.tsx +974 -0
- package/corpus/templates/analytics/changelog/2026-07-02-alert-rules-can-now-be-viewed-and-managed-from-settings.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-02-recent-ask-chats-show-a-loading-placeholder-while-your-chat-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-02-session-replays-now-capture-console-logs-and-network-request.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-02-sharing-status-labels-use-neutral-icons-instead-of-colored-d.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-02-the-session-replay-viewer-has-a-dev-tools-panel-with-console.md +6 -0
- package/corpus/templates/analytics/server/db/schema.ts +3 -0
- package/corpus/templates/analytics/server/jobs/analytics-alerts.ts +4 -0
- package/corpus/templates/analytics/server/lib/analytics-alerts.ts +142 -1
- package/corpus/templates/analytics/server/lib/session-replay-agent-context.ts +369 -8
- package/corpus/templates/analytics/server/lib/session-replay.ts +76 -2
- package/corpus/templates/analytics/server/routes/api/session-replay/agent-diagnostics.json.get.ts +101 -0
- package/corpus/templates/analytics/shared/session-replay-diagnostics.ts +78 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +24 -12
- package/corpus/templates/design/changelog/2026-07-02-the-temporary-code-tab-is-hidden-from-the-design-editor-side.md +6 -0
- package/corpus/templates/plan/actions/create-visual-recap.ts +1 -1
- package/corpus/templates/plan/actions/update-local-plan-folder.ts +3 -1
- package/corpus/templates/plan/actions/update-visual-plan.ts +1 -1
- package/corpus/templates/plan/changelog/2026-07-02-trying-to-edit-a-recap-or-plan-you-can-only-view-now-explain.md +6 -0
- package/corpus/templates/plan/server/plans.ts +26 -9
- package/dist/agent/action-continuation-guidance.js +1 -1
- package/dist/agent/action-continuation-guidance.js.map +1 -1
- package/dist/agent/production-agent.d.ts +9 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +168 -34
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-manager.d.ts +29 -0
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +73 -0
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/run-store.d.ts +41 -0
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +105 -7
- package/dist/agent/run-store.js.map +1 -1
- package/dist/agent/types.d.ts +13 -0
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/client/RunStuckBanner.d.ts.map +1 -1
- package/dist/client/RunStuckBanner.js +13 -0
- package/dist/client/RunStuckBanner.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +410 -0
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/analytics.d.ts +1 -1
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/error-format.d.ts.map +1 -1
- package/dist/client/error-format.js +2 -0
- package/dist/client/error-format.js.map +1 -1
- package/dist/client/extensions/ExtensionsSidebarSection.js +1 -1
- package/dist/client/extensions/ExtensionsSidebarSection.js.map +1 -1
- package/dist/client/session-replay.d.ts +36 -0
- package/dist/client/session-replay.d.ts.map +1 -1
- package/dist/client/session-replay.js +579 -1
- package/dist/client/session-replay.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts +20 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +42 -11
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/client/use-run-stuck-detection.d.ts +14 -4
- package/dist/client/use-run-stuck-detection.d.ts.map +1 -1
- package/dist/client/use-run-stuck-detection.js +18 -4
- package/dist/client/use-run-stuck-detection.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +2 -2
- package/dist/db/runtime-diagnostics.d.ts +52 -0
- package/dist/db/runtime-diagnostics.d.ts.map +1 -0
- package/dist/db/runtime-diagnostics.js +250 -0
- package/dist/db/runtime-diagnostics.js.map +1 -0
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/secrets/routes.d.ts +3 -3
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +101 -2
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts +17 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +65 -5
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/http-response-telemetry.d.ts +4 -0
- package/dist/server/http-response-telemetry.d.ts.map +1 -0
- package/dist/server/http-response-telemetry.js +174 -0
- package/dist/server/http-response-telemetry.js.map +1 -0
- package/dist/server/self-dispatch.d.ts +17 -0
- package/dist/server/self-dispatch.d.ts.map +1 -1
- package/dist/server/self-dispatch.js +11 -0
- package/dist/server/self-dispatch.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/docs/content/locales/ar-SA/tracking.mdx +55 -0
- package/docs/content/locales/de-DE/tracking.mdx +55 -0
- package/docs/content/locales/es-ES/tracking.mdx +55 -0
- package/docs/content/locales/fr-FR/tracking.mdx +55 -0
- package/docs/content/locales/hi-IN/tracking.mdx +55 -0
- package/docs/content/locales/ja-JP/tracking.mdx +55 -0
- package/docs/content/locales/ko-KR/tracking.mdx +55 -0
- package/docs/content/locales/pt-BR/tracking.mdx +55 -0
- package/docs/content/locales/zh-CN/tracking.mdx +55 -0
- package/docs/content/locales/zh-TW/tracking.mdx +55 -0
- package/docs/content/tracking.mdx +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isFailedSessionReplayNetworkStatus,
|
|
3
|
+
SESSION_REPLAY_CONSOLE_EVENT_TAG,
|
|
4
|
+
SESSION_REPLAY_NETWORK_EVENT_TAG,
|
|
5
|
+
type SessionReplayConsoleLevel,
|
|
6
|
+
type SessionReplayConsoleSource,
|
|
7
|
+
} from "@shared/session-replay-diagnostics";
|
|
8
|
+
|
|
9
|
+
type AnyReplayEvent = Record<string, any>;
|
|
10
|
+
|
|
11
|
+
const RRWEB_CUSTOM_EVENT_TYPE = 5;
|
|
12
|
+
|
|
13
|
+
const CONSOLE_LEVELS: ReadonlySet<SessionReplayConsoleLevel> = new Set([
|
|
14
|
+
"log",
|
|
15
|
+
"info",
|
|
16
|
+
"warn",
|
|
17
|
+
"error",
|
|
18
|
+
"debug",
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
const CONSOLE_SOURCES: ReadonlySet<SessionReplayConsoleSource> = new Set([
|
|
22
|
+
"console",
|
|
23
|
+
"window-error",
|
|
24
|
+
"unhandledrejection",
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
export type ReplayConsoleEntry = {
|
|
28
|
+
id: string;
|
|
29
|
+
offsetMs: number;
|
|
30
|
+
timestamp: number;
|
|
31
|
+
level: SessionReplayConsoleLevel;
|
|
32
|
+
source: SessionReplayConsoleSource;
|
|
33
|
+
message: string;
|
|
34
|
+
args: string[];
|
|
35
|
+
stack?: string;
|
|
36
|
+
url?: string;
|
|
37
|
+
repeat: number;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type ReplayNetworkEntry = {
|
|
41
|
+
id: string;
|
|
42
|
+
offsetMs: number;
|
|
43
|
+
timestamp: number;
|
|
44
|
+
api: "fetch" | "xhr";
|
|
45
|
+
method: string;
|
|
46
|
+
url: string;
|
|
47
|
+
status: number;
|
|
48
|
+
ok: boolean;
|
|
49
|
+
failed: boolean;
|
|
50
|
+
durationMs: number;
|
|
51
|
+
error?: string;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export type ReplayDevToolsDiagnostics = {
|
|
55
|
+
console: ReplayConsoleEntry[];
|
|
56
|
+
network: ReplayNetworkEntry[];
|
|
57
|
+
consoleErrorCount: number;
|
|
58
|
+
networkFailedCount: number;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export type ConsoleLevelFilter = "all" | "log" | "info" | "warn" | "error";
|
|
62
|
+
export type NetworkKindFilter = "all" | "fetch" | "xhr" | "failed";
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Pulls the embedded `agent-native.console` / `agent-native.network` rrweb
|
|
66
|
+
* custom events out of a sanitized replay event stream. Offsets are relative
|
|
67
|
+
* to the first replay event, matching how the player timeline computes time.
|
|
68
|
+
*/
|
|
69
|
+
export function extractReplayDiagnostics(
|
|
70
|
+
events: readonly AnyReplayEvent[],
|
|
71
|
+
): ReplayDevToolsDiagnostics {
|
|
72
|
+
const startedAt = firstReplayTimestamp(events);
|
|
73
|
+
const consoleEntries: ReplayConsoleEntry[] = [];
|
|
74
|
+
const networkEntries: ReplayNetworkEntry[] = [];
|
|
75
|
+
|
|
76
|
+
for (const event of events) {
|
|
77
|
+
if (!event || event.type !== RRWEB_CUSTOM_EVENT_TYPE) continue;
|
|
78
|
+
const timestamp = Number(event.timestamp ?? 0);
|
|
79
|
+
if (!Number.isFinite(timestamp) || timestamp <= 0) continue;
|
|
80
|
+
const tag = event.data?.tag;
|
|
81
|
+
const payload = event.data?.payload;
|
|
82
|
+
if (!payload || typeof payload !== "object") continue;
|
|
83
|
+
const offsetMs = Math.max(0, timestamp - startedAt);
|
|
84
|
+
|
|
85
|
+
if (tag === SESSION_REPLAY_CONSOLE_EVENT_TAG) {
|
|
86
|
+
consoleEntries.push(
|
|
87
|
+
normalizeConsoleEntry(
|
|
88
|
+
payload,
|
|
89
|
+
timestamp,
|
|
90
|
+
offsetMs,
|
|
91
|
+
consoleEntries.length,
|
|
92
|
+
),
|
|
93
|
+
);
|
|
94
|
+
} else if (tag === SESSION_REPLAY_NETWORK_EVENT_TAG) {
|
|
95
|
+
networkEntries.push(
|
|
96
|
+
normalizeNetworkEntry(
|
|
97
|
+
payload,
|
|
98
|
+
timestamp,
|
|
99
|
+
offsetMs,
|
|
100
|
+
networkEntries.length,
|
|
101
|
+
),
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
consoleEntries.sort((a, b) => a.offsetMs - b.offsetMs);
|
|
107
|
+
networkEntries.sort((a, b) => a.offsetMs - b.offsetMs);
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
console: consoleEntries,
|
|
111
|
+
network: networkEntries,
|
|
112
|
+
consoleErrorCount: consoleEntries.filter((entry) => entry.level === "error")
|
|
113
|
+
.length,
|
|
114
|
+
networkFailedCount: networkEntries.filter((entry) => entry.failed).length,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function normalizeConsoleEntry(
|
|
119
|
+
payload: AnyReplayEvent,
|
|
120
|
+
timestamp: number,
|
|
121
|
+
offsetMs: number,
|
|
122
|
+
index: number,
|
|
123
|
+
): ReplayConsoleEntry {
|
|
124
|
+
const level = CONSOLE_LEVELS.has(payload.level) ? payload.level : "log";
|
|
125
|
+
const source = CONSOLE_SOURCES.has(payload.source)
|
|
126
|
+
? payload.source
|
|
127
|
+
: "console";
|
|
128
|
+
const repeat = Math.max(1, Math.floor(Number(payload.repeat)) || 1);
|
|
129
|
+
return {
|
|
130
|
+
id: `console-${timestamp}-${index}`,
|
|
131
|
+
offsetMs,
|
|
132
|
+
timestamp,
|
|
133
|
+
level,
|
|
134
|
+
source,
|
|
135
|
+
message: typeof payload.message === "string" ? payload.message : "",
|
|
136
|
+
args: Array.isArray(payload.args)
|
|
137
|
+
? payload.args.filter((arg): arg is string => typeof arg === "string")
|
|
138
|
+
: [],
|
|
139
|
+
stack: typeof payload.stack === "string" ? payload.stack : undefined,
|
|
140
|
+
url: typeof payload.url === "string" ? payload.url : undefined,
|
|
141
|
+
repeat,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function normalizeNetworkEntry(
|
|
146
|
+
payload: AnyReplayEvent,
|
|
147
|
+
timestamp: number,
|
|
148
|
+
offsetMs: number,
|
|
149
|
+
index: number,
|
|
150
|
+
): ReplayNetworkEntry {
|
|
151
|
+
const status = Number.isFinite(Number(payload.status))
|
|
152
|
+
? Math.max(0, Math.floor(Number(payload.status)))
|
|
153
|
+
: 0;
|
|
154
|
+
const ok = payload.ok === true;
|
|
155
|
+
return {
|
|
156
|
+
id: `network-${timestamp}-${index}`,
|
|
157
|
+
offsetMs,
|
|
158
|
+
timestamp,
|
|
159
|
+
api: payload.api === "xhr" ? "xhr" : "fetch",
|
|
160
|
+
method:
|
|
161
|
+
typeof payload.method === "string" && payload.method
|
|
162
|
+
? payload.method.toUpperCase()
|
|
163
|
+
: "GET",
|
|
164
|
+
url: typeof payload.url === "string" ? payload.url : "",
|
|
165
|
+
status,
|
|
166
|
+
ok,
|
|
167
|
+
failed: !ok || isFailedSessionReplayNetworkStatus(status),
|
|
168
|
+
durationMs: Math.max(0, Math.round(Number(payload.durationMs) || 0)),
|
|
169
|
+
error: typeof payload.error === "string" ? payload.error : undefined,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function firstReplayTimestamp(events: readonly AnyReplayEvent[]): number {
|
|
174
|
+
for (const event of events) {
|
|
175
|
+
const timestamp = Number(event?.timestamp);
|
|
176
|
+
if (Number.isFinite(timestamp) && timestamp > 0) return timestamp;
|
|
177
|
+
}
|
|
178
|
+
return 0;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Chip bucket for a console level; `debug` folds into the Log chip. */
|
|
182
|
+
export function consoleLevelBucket(
|
|
183
|
+
level: SessionReplayConsoleLevel,
|
|
184
|
+
): Exclude<ConsoleLevelFilter, "all"> {
|
|
185
|
+
if (level === "debug") return "log";
|
|
186
|
+
return level;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function filterConsoleEntries(
|
|
190
|
+
entries: readonly ReplayConsoleEntry[],
|
|
191
|
+
level: ConsoleLevelFilter,
|
|
192
|
+
query: string,
|
|
193
|
+
): ReplayConsoleEntry[] {
|
|
194
|
+
const needle = query.trim().toLowerCase();
|
|
195
|
+
return entries.filter((entry) => {
|
|
196
|
+
if (level !== "all" && consoleLevelBucket(entry.level) !== level) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
if (!needle) return true;
|
|
200
|
+
return (
|
|
201
|
+
entry.message.toLowerCase().includes(needle) ||
|
|
202
|
+
entry.args.some((arg) => arg.toLowerCase().includes(needle)) ||
|
|
203
|
+
(entry.url ?? "").toLowerCase().includes(needle)
|
|
204
|
+
);
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function filterNetworkEntries(
|
|
209
|
+
entries: readonly ReplayNetworkEntry[],
|
|
210
|
+
kind: NetworkKindFilter,
|
|
211
|
+
query: string,
|
|
212
|
+
): ReplayNetworkEntry[] {
|
|
213
|
+
const needle = query.trim().toLowerCase();
|
|
214
|
+
return entries.filter((entry) => {
|
|
215
|
+
if (kind === "failed" && !entry.failed) return false;
|
|
216
|
+
if ((kind === "fetch" || kind === "xhr") && entry.api !== kind) {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
if (!needle) return true;
|
|
220
|
+
return (
|
|
221
|
+
entry.url.toLowerCase().includes(needle) ||
|
|
222
|
+
entry.method.toLowerCase().includes(needle) ||
|
|
223
|
+
String(entry.status).includes(needle)
|
|
224
|
+
);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Index of the latest entry at/before the current playback time (250ms
|
|
230
|
+
* tolerance, matching the timeline's active-marker window), or -1.
|
|
231
|
+
* Entries must be sorted by offsetMs ascending.
|
|
232
|
+
*/
|
|
233
|
+
export function latestEntryIndexAt(
|
|
234
|
+
entries: ReadonlyArray<{ offsetMs: number }>,
|
|
235
|
+
timeMs: number,
|
|
236
|
+
): number {
|
|
237
|
+
let active = -1;
|
|
238
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
239
|
+
if (entries[index].offsetMs <= timeMs + 250) active = index;
|
|
240
|
+
else break;
|
|
241
|
+
}
|
|
242
|
+
return active;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** Middle-truncates long strings, keeping the start and the tail visible. */
|
|
246
|
+
export function middleTruncate(value: string, max = 64): string {
|
|
247
|
+
if (value.length <= max) return value;
|
|
248
|
+
const head = Math.ceil((max - 1) * 0.6);
|
|
249
|
+
const tail = max - 1 - head;
|
|
250
|
+
return `${value.slice(0, head)}…${value.slice(value.length - tail)}`;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** Prefers path + query for parseable URLs so rows read like a network tab. */
|
|
254
|
+
export function networkDisplayUrl(url: string): string {
|
|
255
|
+
try {
|
|
256
|
+
const parsed = new URL(url);
|
|
257
|
+
return `${parsed.pathname}${parsed.search}` || url;
|
|
258
|
+
} catch {
|
|
259
|
+
return url;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function formatOffsetClock(ms: number): string {
|
|
264
|
+
const totalSeconds = Math.max(0, Math.floor(ms / 1000));
|
|
265
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
266
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
267
|
+
const seconds = totalSeconds % 60;
|
|
268
|
+
if (hours > 0) {
|
|
269
|
+
return `${hours}:${minutes.toString().padStart(2, "0")}:${seconds
|
|
270
|
+
.toString()
|
|
271
|
+
.padStart(2, "0")}`;
|
|
272
|
+
}
|
|
273
|
+
return `${minutes}:${seconds.toString().padStart(2, "0")}`;
|
|
274
|
+
}
|