codex-agent-view 0.4.3 → 0.4.4
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/.codex-plugin/plugin.json +3 -6
- package/README.ko.md +28 -23
- package/README.md +27 -22
- package/package.json +1 -1
- package/public/app.js +630 -139
- package/public/index.html +62 -52
- package/public/styles.css +51 -16
- package/skills/codex-agent-view/SKILL.md +36 -35
- package/skills/show-agents/SKILL.md +46 -20
package/public/app.js
CHANGED
|
@@ -1,13 +1,402 @@
|
|
|
1
1
|
const API_STATE_URL = "/api/state";
|
|
2
2
|
const POLL_INTERVAL_MS = 2_000;
|
|
3
3
|
const SESSION_TOKEN_KEY = "codex-agent-view-access-token";
|
|
4
|
+
const EXCLUDED_SESSION_KEY = "codex-agent-view-excluded-session";
|
|
5
|
+
const LANGUAGE_KEY = "codex-agent-view-language";
|
|
6
|
+
const SUPPORTED_LANGUAGES = new Set(["en", "ko", "es"]);
|
|
4
7
|
const KNOWN_STATUSES = new Set(["running", "waiting", "completed", "unknown"]);
|
|
8
|
+
const VIEWER_TOKEN_PATTERN = /^[A-Za-z0-9_-]{43}$/;
|
|
9
|
+
const CANONICAL_SESSION_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
10
|
+
|
|
11
|
+
const MESSAGES = Object.freeze({
|
|
12
|
+
en: Object.freeze({
|
|
13
|
+
metaDescription: "Read-only local status for Codex tasks and subagents.",
|
|
14
|
+
skipToContent: "Skip to content",
|
|
15
|
+
brandHome: "Codex Agent View home",
|
|
16
|
+
brandSubtitle: "Local read-only monitor",
|
|
17
|
+
languageLabel: "Language",
|
|
18
|
+
connectionConnecting: "Connecting to local status",
|
|
19
|
+
connectionConnected: "Local monitor connected",
|
|
20
|
+
connectionRetrying: "Disconnected · retrying",
|
|
21
|
+
authenticationRequired: "Live view authentication required",
|
|
22
|
+
heroEyebrow: "LOCAL · READ ONLY",
|
|
23
|
+
heroTitle: "See active work at a glance",
|
|
24
|
+
heroCopy: "Keep using the official Codex app while viewing the current status of parent tasks and subagents locally.",
|
|
25
|
+
freshnessAria: "Refresh information",
|
|
26
|
+
lastUpdatedLabel: "Last updated",
|
|
27
|
+
notYet: "Not yet",
|
|
28
|
+
refreshIntervalLabel: "Refresh interval",
|
|
29
|
+
twoSeconds: "2 seconds",
|
|
30
|
+
metricsAria: "Task status summary",
|
|
31
|
+
parentTasks: "Parent tasks",
|
|
32
|
+
currentlyObserved: "Currently observed",
|
|
33
|
+
runningAgents: "Running subagents",
|
|
34
|
+
workingNow: "Working now",
|
|
35
|
+
waitingStatus: "Waiting",
|
|
36
|
+
waitingExplanation: "Waiting for input or the next step",
|
|
37
|
+
completedAgents: "Completed subagents",
|
|
38
|
+
currentView: "In the current view",
|
|
39
|
+
liveWork: "LIVE WORK",
|
|
40
|
+
sessionsHeading: "Parent tasks and subagents",
|
|
41
|
+
loadingState: "Loading status.",
|
|
42
|
+
toolbarAria: "Filter automatically observed parent tasks and subagents",
|
|
43
|
+
searchLabel: "Filter list (optional)",
|
|
44
|
+
searchPlaceholder: "Find a parent task or subagent",
|
|
45
|
+
statusFilterLabel: "Status filter (optional)",
|
|
46
|
+
statusAll: "All statuses",
|
|
47
|
+
statusRunning: "Running",
|
|
48
|
+
statusWaiting: "Waiting",
|
|
49
|
+
statusCompleted: "Completed",
|
|
50
|
+
statusUnknown: "Unknown",
|
|
51
|
+
connectingCopy: "Connecting to the Codex app's local status.",
|
|
52
|
+
sessionListAria: "Codex parent task list",
|
|
53
|
+
privacyPrompt: "This view does not display user requests or tool inputs.",
|
|
54
|
+
privacyLocal: "Data is read only from the local monitor on this device.",
|
|
55
|
+
timeUnknown: "Time unavailable",
|
|
56
|
+
startedUnknown: "Start time unavailable",
|
|
57
|
+
noReceivedActivity: "No activity received",
|
|
58
|
+
parentTask: "PARENT TASK",
|
|
59
|
+
projectUnknown: "Project unavailable",
|
|
60
|
+
recentActivity: "Recent activity",
|
|
61
|
+
recent: "Recent",
|
|
62
|
+
subagentsCount: "Subagents · {count}",
|
|
63
|
+
subagentName: "Subagent {ordinal}",
|
|
64
|
+
agentProfile: "Role/profile · {profile}",
|
|
65
|
+
agentProfileNote: "Verified hooks do not expose assignment descriptions. Role/profile is shown when available.",
|
|
66
|
+
noSubagents: "No subagents have been observed for this parent task.",
|
|
67
|
+
noRecentActivity: "No recent activity to display.",
|
|
68
|
+
technicalInfo: "Technical information",
|
|
69
|
+
sessionId: "Session ID",
|
|
70
|
+
agentId: "Agent ID",
|
|
71
|
+
rawProfile: "Raw role/profile",
|
|
72
|
+
rawEvent: "Raw event",
|
|
73
|
+
rawTool: "Raw tool",
|
|
74
|
+
retry: "Reconnect",
|
|
75
|
+
resultsFiltered: "Showing {visible} of {total}",
|
|
76
|
+
resultsTotal: "{count} parent tasks",
|
|
77
|
+
searchEmptyTitle: "No matching results.",
|
|
78
|
+
searchEmptyCopy: "Try changing the search term or status filter.",
|
|
79
|
+
emptyWithDiagnosticsTitle: "No parent tasks can be displayed.",
|
|
80
|
+
emptyWithDiagnosticsCopy: "The local monitor received {count} activity records but could not apply them to displayable parent tasks.",
|
|
81
|
+
emptyTitle: "No task activity has been received in this observation window.",
|
|
82
|
+
emptyCopy: "The local monitor is connected. This result alone does not mean that Codex has no running parent tasks or subagents.",
|
|
83
|
+
emptyGuidanceTitle: "If work does not appear",
|
|
84
|
+
automaticTracking: "You do not need to enter or register task IDs. Activity from trusted hooks is added automatically.",
|
|
85
|
+
emptyStep1: "After installing the plugin, fully restart the official Codex app.",
|
|
86
|
+
emptyStep2: "In a new task, review and explicitly trust the Codex Agent View hook command.",
|
|
87
|
+
emptyStep3: "After trusting it, start a new task and run subagents. New activity is added automatically.",
|
|
88
|
+
observationBoundary: "Observation starts with the first trusted hook event. Earlier activity and activity missed while local collection was stopped cannot be replayed; restarting collection opens a new observation window.",
|
|
89
|
+
diagnosticsCount: "Validation information · {count}",
|
|
90
|
+
diagnosticOccurrences: "{count} occurrences",
|
|
91
|
+
disconnectedTitle: "Local status disconnected; retrying.",
|
|
92
|
+
authTitle: "This live view cannot be authenticated.",
|
|
93
|
+
retryWithState: "Reconnecting automatically every 2 seconds while keeping the last good state visible.",
|
|
94
|
+
retryWithoutState: "Reconnecting automatically every 2 seconds. You can leave this view open in the Codex app.",
|
|
95
|
+
missingToken: "This tab has no access token. Ask Codex Agent View to open the live view again in the Codex app.",
|
|
96
|
+
expiredToken: "This live view credential is no longer valid. Ask Codex Agent View to open the live view again in the Codex app.",
|
|
97
|
+
requestFailed: "Status request failed ({status})",
|
|
98
|
+
unknownConnectionError: "An unknown connection error occurred.",
|
|
99
|
+
offline: "This device is offline.",
|
|
100
|
+
invalidState: "The status response format is invalid.",
|
|
101
|
+
durationSeconds: "{count}s",
|
|
102
|
+
durationMinutes: "{count}m",
|
|
103
|
+
durationHours: "{count}h",
|
|
104
|
+
durationHoursMinutes: "{hours}h {minutes}m",
|
|
105
|
+
toolFallback: "Tool · {name}",
|
|
106
|
+
activityFallback: "Activity · {name}",
|
|
107
|
+
nameUnknown: "Unknown name",
|
|
108
|
+
activityUnknown: "Unknown",
|
|
109
|
+
approvalRequest: "{tool} approval requested",
|
|
110
|
+
agentStarted: "Subagent {ordinal} started",
|
|
111
|
+
agentStopped: "Subagent {ordinal} completed",
|
|
112
|
+
activitySessionStarted: "Observation started",
|
|
113
|
+
activitySessionEnded: "Observation ended",
|
|
114
|
+
activityTurnStarted: "Parent task started",
|
|
115
|
+
activityTurnStopped: "Parent task response completed",
|
|
116
|
+
activitySubagentStarted: "Subagent started",
|
|
117
|
+
activitySubagentStopped: "Subagent completed",
|
|
118
|
+
activityToolStarted: "Tool activity",
|
|
119
|
+
activityToolCompleted: "Tool activity",
|
|
120
|
+
activityPermissionRequested: "User approval requested",
|
|
121
|
+
toolApplyPatch: "File edit",
|
|
122
|
+
toolBash: "Terminal activity",
|
|
123
|
+
toolFollowup: "Subagent follow-up requested",
|
|
124
|
+
toolInterrupt: "Subagent interruption requested",
|
|
125
|
+
toolList: "Subagent status checked",
|
|
126
|
+
toolMessage: "Message sent to subagent",
|
|
127
|
+
toolSpawn: "Subagent started",
|
|
128
|
+
toolWaitAgent: "Waiting for subagent",
|
|
129
|
+
toolExec: "Terminal activity",
|
|
130
|
+
toolUserInput: "User input requested",
|
|
131
|
+
toolWait: "Waiting for completion",
|
|
132
|
+
toolStdin: "Terminal input",
|
|
133
|
+
roleExplorer: "Research",
|
|
134
|
+
roleReviewer: "Review",
|
|
135
|
+
roleWorker: "Implementation",
|
|
136
|
+
roleDefault: "General agent",
|
|
137
|
+
roleUnknown: "Not reported",
|
|
138
|
+
}),
|
|
139
|
+
ko: Object.freeze({
|
|
140
|
+
metaDescription: "공식 Codex 앱의 부모 작업과 작업 에이전트 상태를 로컬에서 읽기 전용으로 확인합니다.",
|
|
141
|
+
skipToContent: "본문으로 건너뛰기",
|
|
142
|
+
brandHome: "Codex Agent View 홈",
|
|
143
|
+
brandSubtitle: "로컬 읽기 전용 모니터",
|
|
144
|
+
languageLabel: "언어",
|
|
145
|
+
connectionConnecting: "로컬 상태 연결 중",
|
|
146
|
+
connectionConnected: "로컬 모니터 연결됨",
|
|
147
|
+
connectionRetrying: "연결 끊김 · 재시도 중",
|
|
148
|
+
authenticationRequired: "실시간 화면 인증 필요",
|
|
149
|
+
heroEyebrow: "로컬 · 읽기 전용",
|
|
150
|
+
heroTitle: "작업 흐름을 한눈에",
|
|
151
|
+
heroCopy: "공식 Codex 앱은 그대로 두고, 부모 작업과 작업 에이전트의 현재 상태만 로컬에서 확인합니다.",
|
|
152
|
+
freshnessAria: "상태 갱신 정보",
|
|
153
|
+
lastUpdatedLabel: "마지막 갱신",
|
|
154
|
+
notYet: "아직 없음",
|
|
155
|
+
refreshIntervalLabel: "갱신 주기",
|
|
156
|
+
twoSeconds: "2초",
|
|
157
|
+
metricsAria: "작업 상태 요약",
|
|
158
|
+
parentTasks: "부모 작업",
|
|
159
|
+
currentlyObserved: "현재 관찰 중",
|
|
160
|
+
runningAgents: "실행 중 에이전트",
|
|
161
|
+
workingNow: "작업 수행 중",
|
|
162
|
+
waitingStatus: "대기 상태",
|
|
163
|
+
waitingExplanation: "사용자 응답 또는 다음 작업 대기",
|
|
164
|
+
completedAgents: "완료 에이전트",
|
|
165
|
+
currentView: "현재 화면 기준",
|
|
166
|
+
liveWork: "실시간 작업",
|
|
167
|
+
sessionsHeading: "부모 작업과 작업 에이전트",
|
|
168
|
+
loadingState: "상태를 불러오는 중입니다.",
|
|
169
|
+
toolbarAria: "자동 수신된 부모 작업과 작업 에이전트 목록 필터",
|
|
170
|
+
searchLabel: "목록 필터 (선택)",
|
|
171
|
+
searchPlaceholder: "부모 작업·작업 에이전트 목록에서 찾기",
|
|
172
|
+
statusFilterLabel: "상태 필터 (선택)",
|
|
173
|
+
statusAll: "모든 상태",
|
|
174
|
+
statusRunning: "실행 중",
|
|
175
|
+
statusWaiting: "대기",
|
|
176
|
+
statusCompleted: "완료",
|
|
177
|
+
statusUnknown: "알 수 없음",
|
|
178
|
+
connectingCopy: "Codex 앱의 로컬 상태에 연결하고 있습니다.",
|
|
179
|
+
sessionListAria: "Codex 부모 작업 목록",
|
|
180
|
+
privacyPrompt: "이 화면은 사용자 요청 내용과 도구 입력 내용을 표시하지 않습니다.",
|
|
181
|
+
privacyLocal: "데이터는 이 기기의 로컬 모니터에서만 읽습니다.",
|
|
182
|
+
timeUnknown: "시간 정보 없음",
|
|
183
|
+
startedUnknown: "시작 시간 없음",
|
|
184
|
+
noReceivedActivity: "수신된 활동 없음",
|
|
185
|
+
parentTask: "부모 작업",
|
|
186
|
+
projectUnknown: "프로젝트 정보 없음",
|
|
187
|
+
recentActivity: "최근 활동",
|
|
188
|
+
recent: "최근",
|
|
189
|
+
subagentsCount: "작업 에이전트 · {count}",
|
|
190
|
+
subagentName: "작업 에이전트 {ordinal}",
|
|
191
|
+
agentProfile: "역할/프로필 · {profile}",
|
|
192
|
+
agentProfileNote: "검증된 hook은 할당 작업 설명을 제공하지 않습니다. 확인 가능한 역할/프로필만 표시합니다.",
|
|
193
|
+
noSubagents: "이 부모 작업에서 관찰된 작업 에이전트가 없습니다.",
|
|
194
|
+
noRecentActivity: "표시할 최근 활동이 없습니다.",
|
|
195
|
+
technicalInfo: "기술 정보",
|
|
196
|
+
sessionId: "세션 ID",
|
|
197
|
+
agentId: "에이전트 ID",
|
|
198
|
+
rawProfile: "원본 역할/프로필",
|
|
199
|
+
rawEvent: "원본 이벤트",
|
|
200
|
+
rawTool: "원본 도구",
|
|
201
|
+
retry: "다시 연결",
|
|
202
|
+
resultsFiltered: "전체 {total}개 중 {visible}개 표시",
|
|
203
|
+
resultsTotal: "{count}개 부모 작업",
|
|
204
|
+
searchEmptyTitle: "검색 결과가 없습니다.",
|
|
205
|
+
searchEmptyCopy: "검색어나 상태 필터를 바꿔 보세요.",
|
|
206
|
+
emptyWithDiagnosticsTitle: "표시 가능한 부모 작업이 없습니다.",
|
|
207
|
+
emptyWithDiagnosticsCopy: "로컬 모니터가 활동 정보 {count}건을 받았지만 표시 가능한 부모 작업으로 적용하지 못했습니다.",
|
|
208
|
+
emptyTitle: "이 관찰 화면에서 수신된 작업 활동이 없습니다.",
|
|
209
|
+
emptyCopy: "로컬 모니터 연결은 정상입니다. 이 결과만으로 Codex에 실행 중인 부모 작업이나 작업 에이전트가 없다고 판단할 수 없습니다.",
|
|
210
|
+
emptyGuidanceTitle: "표시되지 않을 때 확인 순서",
|
|
211
|
+
automaticTracking: "작업 ID를 입력하거나 작업별로 등록할 필요가 없습니다. 신뢰한 hook의 작업 활동이 이 목록에 자동으로 추가됩니다.",
|
|
212
|
+
emptyStep1: "플러그인을 설치한 뒤 공식 Codex 앱을 완전히 재시작했는지 확인합니다.",
|
|
213
|
+
emptyStep2: "새 작업에서 표시되는 Codex Agent View hook 명령을 검토하고 직접 신뢰합니다.",
|
|
214
|
+
emptyStep3: "신뢰 설정 후 새 작업을 시작해 작업 에이전트를 실행합니다. 새 활동은 이 목록에 자동으로 추가됩니다.",
|
|
215
|
+
observationBoundary: "관찰 화면은 첫 번째로 신뢰한 hook을 받은 시점부터 시작합니다. 그 전에 이미 지나간 활동과 로컬 상태 수집이 중단된 동안의 활동은 재생되지 않으며, 수집이 다시 시작되면 새 관찰 화면이 열립니다.",
|
|
216
|
+
diagnosticsCount: "검증 정보 · {count}건",
|
|
217
|
+
diagnosticOccurrences: "{count}건",
|
|
218
|
+
disconnectedTitle: "로컬 상태 연결이 끊겨 다시 시도 중입니다.",
|
|
219
|
+
authTitle: "이 실시간 화면을 인증할 수 없습니다.",
|
|
220
|
+
retryWithState: "2초마다 자동으로 다시 연결합니다. 마지막 정상 상태를 계속 표시합니다.",
|
|
221
|
+
retryWithoutState: "2초마다 자동으로 다시 연결합니다. Codex 앱에서 이 화면을 그대로 두어도 됩니다.",
|
|
222
|
+
missingToken: "이 탭에는 접근 토큰이 없습니다. Codex 앱에서 Codex Agent View의 실시간 화면 열기를 다시 요청하세요.",
|
|
223
|
+
expiredToken: "이 실시간 화면의 인증이 더 이상 유효하지 않습니다. Codex 앱에서 Codex Agent View의 실시간 화면 열기를 다시 요청하세요.",
|
|
224
|
+
requestFailed: "상태 요청 실패 ({status})",
|
|
225
|
+
unknownConnectionError: "알 수 없는 연결 오류가 발생했습니다.",
|
|
226
|
+
offline: "이 기기가 오프라인입니다.",
|
|
227
|
+
invalidState: "상태 응답 형식이 올바르지 않습니다.",
|
|
228
|
+
durationSeconds: "{count}초",
|
|
229
|
+
durationMinutes: "{count}분",
|
|
230
|
+
durationHours: "{count}시간",
|
|
231
|
+
durationHoursMinutes: "{hours}시간 {minutes}분",
|
|
232
|
+
toolFallback: "도구 · {name}",
|
|
233
|
+
activityFallback: "활동 · {name}",
|
|
234
|
+
nameUnknown: "이름 미상",
|
|
235
|
+
activityUnknown: "알 수 없음",
|
|
236
|
+
approvalRequest: "{tool} 승인 요청",
|
|
237
|
+
agentStarted: "작업 에이전트 {ordinal} 시작",
|
|
238
|
+
agentStopped: "작업 에이전트 {ordinal} 완료",
|
|
239
|
+
activitySessionStarted: "관찰 시작",
|
|
240
|
+
activitySessionEnded: "관찰 종료",
|
|
241
|
+
activityTurnStarted: "부모 작업 시작",
|
|
242
|
+
activityTurnStopped: "부모 작업 응답 완료",
|
|
243
|
+
activitySubagentStarted: "작업 에이전트 시작",
|
|
244
|
+
activitySubagentStopped: "작업 에이전트 완료",
|
|
245
|
+
activityToolStarted: "도구 작업",
|
|
246
|
+
activityToolCompleted: "도구 작업",
|
|
247
|
+
activityPermissionRequested: "사용자 승인 요청",
|
|
248
|
+
toolApplyPatch: "파일 수정",
|
|
249
|
+
toolBash: "터미널 작업",
|
|
250
|
+
toolFollowup: "에이전트 후속 작업 요청",
|
|
251
|
+
toolInterrupt: "에이전트 작업 중단 요청",
|
|
252
|
+
toolList: "에이전트 상태 확인",
|
|
253
|
+
toolMessage: "에이전트에게 메시지 전달",
|
|
254
|
+
toolSpawn: "작업 에이전트 시작",
|
|
255
|
+
toolWaitAgent: "에이전트 응답 대기",
|
|
256
|
+
toolExec: "터미널 작업",
|
|
257
|
+
toolUserInput: "사용자 입력 요청",
|
|
258
|
+
toolWait: "작업 완료 대기",
|
|
259
|
+
toolStdin: "터미널 입력",
|
|
260
|
+
roleExplorer: "조사",
|
|
261
|
+
roleReviewer: "검토",
|
|
262
|
+
roleWorker: "구현",
|
|
263
|
+
roleDefault: "일반 에이전트",
|
|
264
|
+
roleUnknown: "보고되지 않음",
|
|
265
|
+
}),
|
|
266
|
+
es: Object.freeze({
|
|
267
|
+
metaDescription: "Estado local y de solo lectura de tareas y subagentes de Codex.",
|
|
268
|
+
skipToContent: "Saltar al contenido",
|
|
269
|
+
brandHome: "Inicio de Codex Agent View",
|
|
270
|
+
brandSubtitle: "Monitor local de solo lectura",
|
|
271
|
+
languageLabel: "Idioma",
|
|
272
|
+
connectionConnecting: "Conectando al estado local",
|
|
273
|
+
connectionConnected: "Monitor local conectado",
|
|
274
|
+
connectionRetrying: "Desconectado · reintentando",
|
|
275
|
+
authenticationRequired: "Se requiere autenticar la vista",
|
|
276
|
+
heroEyebrow: "LOCAL · SOLO LECTURA",
|
|
277
|
+
heroTitle: "Observa el trabajo activo de un vistazo",
|
|
278
|
+
heroCopy: "Sigue usando la aplicación oficial de Codex mientras consultas localmente el estado de las tareas principales y los subagentes.",
|
|
279
|
+
freshnessAria: "Información de actualización",
|
|
280
|
+
lastUpdatedLabel: "Última actualización",
|
|
281
|
+
notYet: "Aún no",
|
|
282
|
+
refreshIntervalLabel: "Intervalo de actualización",
|
|
283
|
+
twoSeconds: "2 segundos",
|
|
284
|
+
metricsAria: "Resumen del estado de las tareas",
|
|
285
|
+
parentTasks: "Tareas principales",
|
|
286
|
+
currentlyObserved: "En observación",
|
|
287
|
+
runningAgents: "Subagentes activos",
|
|
288
|
+
workingNow: "Trabajando ahora",
|
|
289
|
+
waitingStatus: "En espera",
|
|
290
|
+
waitingExplanation: "Esperando una respuesta o el siguiente paso",
|
|
291
|
+
completedAgents: "Subagentes completados",
|
|
292
|
+
currentView: "En la vista actual",
|
|
293
|
+
liveWork: "TRABAJO EN VIVO",
|
|
294
|
+
sessionsHeading: "Tareas principales y subagentes",
|
|
295
|
+
loadingState: "Cargando el estado.",
|
|
296
|
+
toolbarAria: "Filtrar tareas principales y subagentes observados automáticamente",
|
|
297
|
+
searchLabel: "Filtrar lista (opcional)",
|
|
298
|
+
searchPlaceholder: "Buscar una tarea principal o un subagente",
|
|
299
|
+
statusFilterLabel: "Filtrar por estado (opcional)",
|
|
300
|
+
statusAll: "Todos los estados",
|
|
301
|
+
statusRunning: "En ejecución",
|
|
302
|
+
statusWaiting: "En espera",
|
|
303
|
+
statusCompleted: "Completado",
|
|
304
|
+
statusUnknown: "Desconocido",
|
|
305
|
+
connectingCopy: "Conectando al estado local de la aplicación Codex.",
|
|
306
|
+
sessionListAria: "Lista de tareas principales de Codex",
|
|
307
|
+
privacyPrompt: "Esta vista no muestra solicitudes del usuario ni entradas de herramientas.",
|
|
308
|
+
privacyLocal: "Los datos se leen únicamente del monitor local de este dispositivo.",
|
|
309
|
+
timeUnknown: "Hora no disponible",
|
|
310
|
+
startedUnknown: "Hora de inicio no disponible",
|
|
311
|
+
noReceivedActivity: "No se recibió actividad",
|
|
312
|
+
parentTask: "TAREA PRINCIPAL",
|
|
313
|
+
projectUnknown: "Proyecto no disponible",
|
|
314
|
+
recentActivity: "Actividad reciente",
|
|
315
|
+
recent: "Reciente",
|
|
316
|
+
subagentsCount: "Subagentes · {count}",
|
|
317
|
+
subagentName: "Subagente {ordinal}",
|
|
318
|
+
agentProfile: "Rol/perfil · {profile}",
|
|
319
|
+
agentProfileNote: "Los hooks verificados no proporcionan descripciones de la tarea asignada. Se muestra el rol/perfil cuando está disponible.",
|
|
320
|
+
noSubagents: "No se observaron subagentes para esta tarea principal.",
|
|
321
|
+
noRecentActivity: "No hay actividad reciente que mostrar.",
|
|
322
|
+
technicalInfo: "Información técnica",
|
|
323
|
+
sessionId: "ID de sesión",
|
|
324
|
+
agentId: "ID del agente",
|
|
325
|
+
rawProfile: "Rol/perfil original",
|
|
326
|
+
rawEvent: "Evento original",
|
|
327
|
+
rawTool: "Herramienta original",
|
|
328
|
+
retry: "Reconectar",
|
|
329
|
+
resultsFiltered: "Mostrando {visible} de {total}",
|
|
330
|
+
resultsTotal: "{count} tareas principales",
|
|
331
|
+
searchEmptyTitle: "No hay resultados.",
|
|
332
|
+
searchEmptyCopy: "Prueba otra búsqueda o filtro de estado.",
|
|
333
|
+
emptyWithDiagnosticsTitle: "No hay tareas principales que mostrar.",
|
|
334
|
+
emptyWithDiagnosticsCopy: "El monitor local recibió {count} registros de actividad, pero no pudo aplicarlos a tareas visibles.",
|
|
335
|
+
emptyTitle: "No se recibió actividad en esta ventana de observación.",
|
|
336
|
+
emptyCopy: "El monitor local está conectado. Este resultado no implica por sí solo que Codex no tenga tareas o subagentes activos.",
|
|
337
|
+
emptyGuidanceTitle: "Si el trabajo no aparece",
|
|
338
|
+
automaticTracking: "No es necesario introducir ni registrar IDs de tareas. La actividad de hooks confiables se añade automáticamente.",
|
|
339
|
+
emptyStep1: "Tras instalar el plugin, reinicia por completo la aplicación oficial de Codex.",
|
|
340
|
+
emptyStep2: "En una tarea nueva, revisa y autoriza explícitamente el comando hook de Codex Agent View.",
|
|
341
|
+
emptyStep3: "Después, inicia una tarea nueva y ejecuta subagentes. La actividad se añadirá automáticamente.",
|
|
342
|
+
observationBoundary: "La observación comienza con el primer evento de un hook autorizado. La actividad anterior o perdida mientras la recopilación local estuvo detenida no se puede reproducir; al reiniciarla se abre una ventana nueva.",
|
|
343
|
+
diagnosticsCount: "Información de validación · {count}",
|
|
344
|
+
diagnosticOccurrences: "{count} apariciones",
|
|
345
|
+
disconnectedTitle: "Se perdió la conexión local; reintentando.",
|
|
346
|
+
authTitle: "No se puede autenticar esta vista en vivo.",
|
|
347
|
+
retryWithState: "Se reconecta automáticamente cada 2 segundos y mantiene visible el último estado válido.",
|
|
348
|
+
retryWithoutState: "Se reconecta automáticamente cada 2 segundos. Puedes dejar esta vista abierta en Codex.",
|
|
349
|
+
missingToken: "Esta pestaña no tiene un token de acceso. Pide a Codex Agent View que vuelva a abrir la vista en Codex.",
|
|
350
|
+
expiredToken: "La credencial de esta vista ya no es válida. Pide a Codex Agent View que vuelva a abrir la vista en Codex.",
|
|
351
|
+
requestFailed: "Falló la solicitud de estado ({status})",
|
|
352
|
+
unknownConnectionError: "Se produjo un error de conexión desconocido.",
|
|
353
|
+
offline: "Este dispositivo está sin conexión.",
|
|
354
|
+
invalidState: "El formato de la respuesta de estado no es válido.",
|
|
355
|
+
durationSeconds: "{count} s",
|
|
356
|
+
durationMinutes: "{count} min",
|
|
357
|
+
durationHours: "{count} h",
|
|
358
|
+
durationHoursMinutes: "{hours} h {minutes} min",
|
|
359
|
+
toolFallback: "Herramienta · {name}",
|
|
360
|
+
activityFallback: "Actividad · {name}",
|
|
361
|
+
nameUnknown: "Nombre desconocido",
|
|
362
|
+
activityUnknown: "Desconocida",
|
|
363
|
+
approvalRequest: "Se solicitó aprobación para {tool}",
|
|
364
|
+
agentStarted: "Subagente {ordinal} iniciado",
|
|
365
|
+
agentStopped: "Subagente {ordinal} completado",
|
|
366
|
+
activitySessionStarted: "Observación iniciada",
|
|
367
|
+
activitySessionEnded: "Observación finalizada",
|
|
368
|
+
activityTurnStarted: "Tarea principal iniciada",
|
|
369
|
+
activityTurnStopped: "Respuesta de la tarea completada",
|
|
370
|
+
activitySubagentStarted: "Subagente iniciado",
|
|
371
|
+
activitySubagentStopped: "Subagente completado",
|
|
372
|
+
activityToolStarted: "Actividad de herramienta",
|
|
373
|
+
activityToolCompleted: "Actividad de herramienta",
|
|
374
|
+
activityPermissionRequested: "Se solicitó aprobación",
|
|
375
|
+
toolApplyPatch: "Edición de archivo",
|
|
376
|
+
toolBash: "Actividad de terminal",
|
|
377
|
+
toolFollowup: "Seguimiento del subagente solicitado",
|
|
378
|
+
toolInterrupt: "Interrupción del subagente solicitada",
|
|
379
|
+
toolList: "Estado de subagentes consultado",
|
|
380
|
+
toolMessage: "Mensaje enviado al subagente",
|
|
381
|
+
toolSpawn: "Subagente iniciado",
|
|
382
|
+
toolWaitAgent: "Esperando al subagente",
|
|
383
|
+
toolExec: "Actividad de terminal",
|
|
384
|
+
toolUserInput: "Se solicitó entrada del usuario",
|
|
385
|
+
toolWait: "Esperando finalización",
|
|
386
|
+
toolStdin: "Entrada de terminal",
|
|
387
|
+
roleExplorer: "Investigación",
|
|
388
|
+
roleReviewer: "Revisión",
|
|
389
|
+
roleWorker: "Implementación",
|
|
390
|
+
roleDefault: "Agente general",
|
|
391
|
+
roleUnknown: "No informado",
|
|
392
|
+
}),
|
|
393
|
+
});
|
|
5
394
|
|
|
6
|
-
const
|
|
7
|
-
running: "
|
|
8
|
-
waiting: "
|
|
9
|
-
completed: "
|
|
10
|
-
unknown: "
|
|
395
|
+
const STATUS_KEYS = Object.freeze({
|
|
396
|
+
running: "statusRunning",
|
|
397
|
+
waiting: "statusWaiting",
|
|
398
|
+
completed: "statusCompleted",
|
|
399
|
+
unknown: "statusUnknown",
|
|
11
400
|
});
|
|
12
401
|
|
|
13
402
|
const STATUS_ORDER = Object.freeze({
|
|
@@ -17,45 +406,86 @@ const STATUS_ORDER = Object.freeze({
|
|
|
17
406
|
completed: 3,
|
|
18
407
|
});
|
|
19
408
|
|
|
20
|
-
const
|
|
21
|
-
session_started: "
|
|
22
|
-
session_ended: "
|
|
23
|
-
turn_started: "
|
|
24
|
-
turn_stopped: "
|
|
25
|
-
subagent_started: "
|
|
26
|
-
subagent_stopped: "
|
|
27
|
-
tool_started: "
|
|
28
|
-
tool_completed: "
|
|
29
|
-
permission_requested: "
|
|
409
|
+
const ACTIVITY_KEYS = Object.freeze({
|
|
410
|
+
session_started: "activitySessionStarted",
|
|
411
|
+
session_ended: "activitySessionEnded",
|
|
412
|
+
turn_started: "activityTurnStarted",
|
|
413
|
+
turn_stopped: "activityTurnStopped",
|
|
414
|
+
subagent_started: "activitySubagentStarted",
|
|
415
|
+
subagent_stopped: "activitySubagentStopped",
|
|
416
|
+
tool_started: "activityToolStarted",
|
|
417
|
+
tool_completed: "activityToolCompleted",
|
|
418
|
+
permission_requested: "activityPermissionRequested",
|
|
30
419
|
});
|
|
31
420
|
|
|
32
|
-
const
|
|
33
|
-
apply_patch: "
|
|
34
|
-
bash: "
|
|
35
|
-
collaborationfollowup_task: "
|
|
36
|
-
collaborationinterrupt_agent: "
|
|
37
|
-
collaborationlist_agents: "
|
|
38
|
-
collaborationsend_message: "
|
|
39
|
-
collaborationspawn_agent: "
|
|
40
|
-
collaborationwait_agent: "
|
|
41
|
-
exec_command: "
|
|
42
|
-
request_user_input: "
|
|
43
|
-
wait: "
|
|
44
|
-
write_stdin: "
|
|
421
|
+
const TOOL_KEYS = Object.freeze({
|
|
422
|
+
apply_patch: "toolApplyPatch",
|
|
423
|
+
bash: "toolBash",
|
|
424
|
+
collaborationfollowup_task: "toolFollowup",
|
|
425
|
+
collaborationinterrupt_agent: "toolInterrupt",
|
|
426
|
+
collaborationlist_agents: "toolList",
|
|
427
|
+
collaborationsend_message: "toolMessage",
|
|
428
|
+
collaborationspawn_agent: "toolSpawn",
|
|
429
|
+
collaborationwait_agent: "toolWaitAgent",
|
|
430
|
+
exec_command: "toolExec",
|
|
431
|
+
request_user_input: "toolUserInput",
|
|
432
|
+
wait: "toolWait",
|
|
433
|
+
write_stdin: "toolStdin",
|
|
45
434
|
});
|
|
46
435
|
|
|
47
|
-
const
|
|
48
|
-
explorer: "
|
|
49
|
-
reviewer: "
|
|
50
|
-
worker: "
|
|
436
|
+
const AGENT_ROLE_KEYS = Object.freeze({
|
|
437
|
+
explorer: "roleExplorer",
|
|
438
|
+
reviewer: "roleReviewer",
|
|
439
|
+
worker: "roleWorker",
|
|
440
|
+
default: "roleDefault",
|
|
441
|
+
unknown: "roleUnknown",
|
|
51
442
|
});
|
|
52
443
|
|
|
53
|
-
const HIDDEN_AGENT_ROLES = new Set(["", "default", "unknown"]);
|
|
54
444
|
const CONTROL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f]/g;
|
|
55
445
|
|
|
56
|
-
function
|
|
57
|
-
|
|
58
|
-
|
|
446
|
+
function readStoredLanguage() {
|
|
447
|
+
try {
|
|
448
|
+
const stored = window.localStorage.getItem(LANGUAGE_KEY);
|
|
449
|
+
return SUPPORTED_LANGUAGES.has(stored) ? stored : "en";
|
|
450
|
+
} catch {
|
|
451
|
+
return "en";
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
let currentLanguage = readStoredLanguage();
|
|
456
|
+
|
|
457
|
+
function t(key, replacements = {}) {
|
|
458
|
+
const template = MESSAGES[currentLanguage][key] ?? MESSAGES.en[key] ?? key;
|
|
459
|
+
return Object.entries(replacements).reduce(
|
|
460
|
+
(result, [name, value]) => result.replaceAll(`{${name}}`, String(value)),
|
|
461
|
+
template,
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function isExactLiveFragment(entries) {
|
|
466
|
+
const tokenEntries = entries.filter(([key]) => key === "token");
|
|
467
|
+
const excludeEntries = entries.filter(([key]) => key === "exclude");
|
|
468
|
+
return (
|
|
469
|
+
entries.length === tokenEntries.length + excludeEntries.length &&
|
|
470
|
+
tokenEntries.length === 1 &&
|
|
471
|
+
excludeEntries.length <= 1 &&
|
|
472
|
+
VIEWER_TOKEN_PATTERN.test(tokenEntries[0][1]) &&
|
|
473
|
+
(excludeEntries.length === 0 || CANONICAL_SESSION_ID_PATTERN.test(excludeEntries[0][1]))
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function consumeLiveContext() {
|
|
478
|
+
const hasFragment = window.location.hash.length > 1;
|
|
479
|
+
const entries = hasFragment
|
|
480
|
+
? [...new URLSearchParams(window.location.hash.slice(1)).entries()]
|
|
481
|
+
: [];
|
|
482
|
+
const validFragment = hasFragment && isExactLiveFragment(entries);
|
|
483
|
+
const fragmentToken = validFragment
|
|
484
|
+
? entries.find(([key]) => key === "token")[1]
|
|
485
|
+
: "";
|
|
486
|
+
const fragmentExclude = validFragment
|
|
487
|
+
? entries.find(([key]) => key === "exclude")?.[1] || ""
|
|
488
|
+
: "";
|
|
59
489
|
|
|
60
490
|
if (window.location.hash) {
|
|
61
491
|
window.history.replaceState(
|
|
@@ -66,25 +496,39 @@ function consumeAccessToken() {
|
|
|
66
496
|
}
|
|
67
497
|
|
|
68
498
|
let token = fragmentToken;
|
|
499
|
+
let excludedSessionId = fragmentExclude;
|
|
69
500
|
try {
|
|
70
|
-
if (
|
|
501
|
+
if (validFragment) {
|
|
71
502
|
window.sessionStorage.setItem(SESSION_TOKEN_KEY, fragmentToken);
|
|
503
|
+
if (fragmentExclude) {
|
|
504
|
+
window.sessionStorage.setItem(EXCLUDED_SESSION_KEY, fragmentExclude);
|
|
505
|
+
} else {
|
|
506
|
+
window.sessionStorage.removeItem(EXCLUDED_SESSION_KEY);
|
|
507
|
+
}
|
|
72
508
|
} else {
|
|
73
509
|
token = window.sessionStorage.getItem(SESSION_TOKEN_KEY)?.trim() || "";
|
|
510
|
+
excludedSessionId = window.sessionStorage.getItem(EXCLUDED_SESSION_KEY)?.trim() || "";
|
|
74
511
|
}
|
|
75
512
|
} catch {
|
|
76
513
|
// Storage can be unavailable in hardened browser contexts. The fragment
|
|
77
514
|
// token remains usable for this page load and is never copied elsewhere.
|
|
78
515
|
}
|
|
79
516
|
|
|
80
|
-
return
|
|
517
|
+
return {
|
|
518
|
+
accessToken: VIEWER_TOKEN_PATTERN.test(token) ? token : "",
|
|
519
|
+
excludedSessionId: CANONICAL_SESSION_ID_PATTERN.test(excludedSessionId)
|
|
520
|
+
? excludedSessionId
|
|
521
|
+
: "",
|
|
522
|
+
};
|
|
81
523
|
}
|
|
82
524
|
|
|
83
|
-
const accessToken =
|
|
525
|
+
const { accessToken, excludedSessionId } = consumeLiveContext();
|
|
84
526
|
|
|
85
527
|
const elements = Object.freeze({
|
|
86
528
|
connectionStatus: document.querySelector("#connection-status"),
|
|
87
529
|
connectionLabel: document.querySelector("#connection-label"),
|
|
530
|
+
language: document.querySelector("#language-select"),
|
|
531
|
+
metaDescription: document.querySelector("#meta-description"),
|
|
88
532
|
lastUpdated: document.querySelector("#last-updated"),
|
|
89
533
|
metricSessions: document.querySelector("#metric-sessions"),
|
|
90
534
|
metricRunning: document.querySelector("#metric-running"),
|
|
@@ -98,12 +542,43 @@ const elements = Object.freeze({
|
|
|
98
542
|
sessionList: document.querySelector("#session-list"),
|
|
99
543
|
});
|
|
100
544
|
|
|
545
|
+
function applyStaticTranslations() {
|
|
546
|
+
document.documentElement.lang = currentLanguage;
|
|
547
|
+
elements.language.value = currentLanguage;
|
|
548
|
+
elements.metaDescription.content = t("metaDescription");
|
|
549
|
+
for (const element of document.querySelectorAll("[data-i18n]")) {
|
|
550
|
+
element.textContent = t(element.dataset.i18n);
|
|
551
|
+
}
|
|
552
|
+
for (const element of document.querySelectorAll("[data-i18n-aria-label]")) {
|
|
553
|
+
element.setAttribute("aria-label", t(element.dataset.i18nAriaLabel));
|
|
554
|
+
}
|
|
555
|
+
for (const element of document.querySelectorAll("[data-i18n-placeholder]")) {
|
|
556
|
+
element.setAttribute("placeholder", t(element.dataset.i18nPlaceholder));
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function setLanguage(value) {
|
|
561
|
+
currentLanguage = SUPPORTED_LANGUAGES.has(value) ? value : "en";
|
|
562
|
+
try {
|
|
563
|
+
window.localStorage.setItem(LANGUAGE_KEY, currentLanguage);
|
|
564
|
+
} catch {
|
|
565
|
+
// The selected language still applies for this page load.
|
|
566
|
+
}
|
|
567
|
+
applyStaticTranslations();
|
|
568
|
+
setConnectionStatus(
|
|
569
|
+
elements.connectionStatus.dataset.status,
|
|
570
|
+
viewState.authenticationFailed ? t("authenticationRequired") : "",
|
|
571
|
+
);
|
|
572
|
+
render();
|
|
573
|
+
}
|
|
574
|
+
|
|
101
575
|
const viewState = {
|
|
102
576
|
updatedAtMs: null,
|
|
103
577
|
sessions: [],
|
|
104
578
|
diagnostics: [],
|
|
105
579
|
hasLoaded: false,
|
|
106
580
|
errorMessage: "",
|
|
581
|
+
errorKey: "",
|
|
107
582
|
canRetry: true,
|
|
108
583
|
authenticationFailed: false,
|
|
109
584
|
requestInFlight: false,
|
|
@@ -139,26 +614,27 @@ function normalizedLookupKey(value) {
|
|
|
139
614
|
|
|
140
615
|
function formatAgentRole(agentType) {
|
|
141
616
|
const key = normalizedLookupKey(agentType);
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
return AGENT_ROLE_LABELS[key] || sanitizedFallbackLabel(agentType, "");
|
|
617
|
+
return AGENT_ROLE_KEYS[key]
|
|
618
|
+
? t(AGENT_ROLE_KEYS[key])
|
|
619
|
+
: sanitizedFallbackLabel(agentType, t("roleUnknown"));
|
|
146
620
|
}
|
|
147
621
|
|
|
148
622
|
function formatToolLabel(toolName) {
|
|
149
623
|
const key = normalizedLookupKey(toolName);
|
|
150
|
-
return
|
|
624
|
+
return TOOL_KEYS[key]
|
|
625
|
+
? t(TOOL_KEYS[key])
|
|
626
|
+
: t("toolFallback", { name: sanitizedFallbackLabel(toolName, t("nameUnknown")) });
|
|
151
627
|
}
|
|
152
628
|
|
|
153
629
|
function formatActivityLabel(activity) {
|
|
154
630
|
if (activity.eventName === "subagent_started" && activity.agentOrdinal !== null) {
|
|
155
|
-
return
|
|
631
|
+
return t("agentStarted", { ordinal: activity.agentOrdinal });
|
|
156
632
|
}
|
|
157
633
|
if (activity.eventName === "subagent_stopped" && activity.agentOrdinal !== null) {
|
|
158
|
-
return
|
|
634
|
+
return t("agentStopped", { ordinal: activity.agentOrdinal });
|
|
159
635
|
}
|
|
160
636
|
if (activity.eventName === "permission_requested" && activity.toolName) {
|
|
161
|
-
return
|
|
637
|
+
return t("approvalRequest", { tool: formatToolLabel(activity.toolName) });
|
|
162
638
|
}
|
|
163
639
|
if (
|
|
164
640
|
(activity.eventName === "tool_started" || activity.eventName === "tool_completed") &&
|
|
@@ -166,8 +642,11 @@ function formatActivityLabel(activity) {
|
|
|
166
642
|
) {
|
|
167
643
|
return formatToolLabel(activity.toolName);
|
|
168
644
|
}
|
|
169
|
-
return
|
|
170
|
-
|
|
645
|
+
return ACTIVITY_KEYS[activity.eventName]
|
|
646
|
+
? t(ACTIVITY_KEYS[activity.eventName])
|
|
647
|
+
: t("activityFallback", {
|
|
648
|
+
name: sanitizedFallbackLabel(activity.eventName, t("activityUnknown")),
|
|
649
|
+
});
|
|
171
650
|
}
|
|
172
651
|
|
|
173
652
|
function safeTimestamp(value) {
|
|
@@ -337,7 +816,7 @@ function normalizeState(value) {
|
|
|
337
816
|
!Array.isArray(value.sessions) ||
|
|
338
817
|
!Array.isArray(value.diagnostics)
|
|
339
818
|
) {
|
|
340
|
-
throw new TypeError("
|
|
819
|
+
throw new TypeError(t("invalidState"));
|
|
341
820
|
}
|
|
342
821
|
|
|
343
822
|
return {
|
|
@@ -363,10 +842,10 @@ function compareSessions(left, right) {
|
|
|
363
842
|
|
|
364
843
|
function formatDateTime(timestampMs) {
|
|
365
844
|
if (timestampMs === null) {
|
|
366
|
-
return "
|
|
845
|
+
return t("timeUnknown");
|
|
367
846
|
}
|
|
368
847
|
|
|
369
|
-
return new Intl.DateTimeFormat(
|
|
848
|
+
return new Intl.DateTimeFormat(currentLanguage, {
|
|
370
849
|
dateStyle: "medium",
|
|
371
850
|
timeStyle: "medium",
|
|
372
851
|
}).format(new Date(timestampMs));
|
|
@@ -374,12 +853,12 @@ function formatDateTime(timestampMs) {
|
|
|
374
853
|
|
|
375
854
|
function formatRelativeTime(timestampMs) {
|
|
376
855
|
if (timestampMs === null) {
|
|
377
|
-
return "
|
|
856
|
+
return t("timeUnknown");
|
|
378
857
|
}
|
|
379
858
|
|
|
380
859
|
const differenceSeconds = Math.round((timestampMs - Date.now()) / 1_000);
|
|
381
860
|
const absoluteSeconds = Math.abs(differenceSeconds);
|
|
382
|
-
const formatter = new Intl.RelativeTimeFormat(
|
|
861
|
+
const formatter = new Intl.RelativeTimeFormat(currentLanguage, { numeric: "auto" });
|
|
383
862
|
|
|
384
863
|
if (absoluteSeconds < 60) {
|
|
385
864
|
return formatter.format(differenceSeconds, "second");
|
|
@@ -395,23 +874,25 @@ function formatRelativeTime(timestampMs) {
|
|
|
395
874
|
|
|
396
875
|
function formatDuration(startedAtMs, stoppedAtMs) {
|
|
397
876
|
if (startedAtMs === null) {
|
|
398
|
-
return "
|
|
877
|
+
return t("startedUnknown");
|
|
399
878
|
}
|
|
400
879
|
|
|
401
880
|
const endAtMs = stoppedAtMs ?? Date.now();
|
|
402
881
|
const seconds = Math.max(0, Math.floor((endAtMs - startedAtMs) / 1_000));
|
|
403
882
|
if (seconds < 60) {
|
|
404
|
-
return
|
|
883
|
+
return t("durationSeconds", { count: seconds });
|
|
405
884
|
}
|
|
406
885
|
|
|
407
886
|
const minutes = Math.floor(seconds / 60);
|
|
408
887
|
if (minutes < 60) {
|
|
409
|
-
return
|
|
888
|
+
return t("durationMinutes", { count: minutes });
|
|
410
889
|
}
|
|
411
890
|
|
|
412
891
|
const hours = Math.floor(minutes / 60);
|
|
413
892
|
const remainingMinutes = minutes % 60;
|
|
414
|
-
return remainingMinutes
|
|
893
|
+
return remainingMinutes
|
|
894
|
+
? t("durationHoursMinutes", { hours, minutes: remainingMinutes })
|
|
895
|
+
: t("durationHours", { count: hours });
|
|
415
896
|
}
|
|
416
897
|
|
|
417
898
|
function createStatusBadge(status) {
|
|
@@ -424,7 +905,7 @@ function createStatusBadge(status) {
|
|
|
424
905
|
dot.setAttribute("aria-hidden", "true");
|
|
425
906
|
|
|
426
907
|
const label = document.createElement("span");
|
|
427
|
-
label.textContent =
|
|
908
|
+
label.textContent = t(STATUS_KEYS[status]);
|
|
428
909
|
|
|
429
910
|
badge.append(dot, label);
|
|
430
911
|
return badge;
|
|
@@ -439,7 +920,7 @@ function createTime(timestampMs, prefix) {
|
|
|
439
920
|
}
|
|
440
921
|
|
|
441
922
|
if (timestampMs === null) {
|
|
442
|
-
wrapper.append("
|
|
923
|
+
wrapper.append(t("timeUnknown"));
|
|
443
924
|
return wrapper;
|
|
444
925
|
}
|
|
445
926
|
|
|
@@ -451,12 +932,13 @@ function createTime(timestampMs, prefix) {
|
|
|
451
932
|
return wrapper;
|
|
452
933
|
}
|
|
453
934
|
|
|
454
|
-
function
|
|
455
|
-
const
|
|
456
|
-
|
|
935
|
+
function createTechnicalInfo(rows) {
|
|
936
|
+
const info = document.createElement("div");
|
|
937
|
+
info.className = "technical-info";
|
|
457
938
|
|
|
458
|
-
const
|
|
459
|
-
|
|
939
|
+
const heading = document.createElement("p");
|
|
940
|
+
heading.className = "technical-info-title";
|
|
941
|
+
heading.textContent = t("technicalInfo");
|
|
460
942
|
const list = document.createElement("dl");
|
|
461
943
|
|
|
462
944
|
for (const [label, value] of rows) {
|
|
@@ -472,8 +954,8 @@ function createTechnicalDetails(rows) {
|
|
|
472
954
|
list.append(term, description);
|
|
473
955
|
}
|
|
474
956
|
|
|
475
|
-
|
|
476
|
-
return
|
|
957
|
+
info.append(heading, list);
|
|
958
|
+
return info;
|
|
477
959
|
}
|
|
478
960
|
|
|
479
961
|
function createAgentItem(agent) {
|
|
@@ -488,31 +970,27 @@ function createAgentItem(agent) {
|
|
|
488
970
|
identity.className = "agent-identity";
|
|
489
971
|
const name = document.createElement("strong");
|
|
490
972
|
name.className = "agent-name";
|
|
491
|
-
name.textContent =
|
|
973
|
+
name.textContent = t("subagentName", { ordinal: agent.ordinal });
|
|
492
974
|
identity.append(name);
|
|
493
975
|
|
|
494
976
|
const roleLabel = formatAgentRole(agent.agentType);
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
identity.append(role);
|
|
500
|
-
}
|
|
977
|
+
const role = document.createElement("span");
|
|
978
|
+
role.className = "agent-role";
|
|
979
|
+
role.textContent = t("agentProfile", { profile: roleLabel });
|
|
980
|
+
identity.append(role);
|
|
501
981
|
heading.append(identity, createStatusBadge(agent.status));
|
|
502
982
|
|
|
503
983
|
const metadata = document.createElement("div");
|
|
504
984
|
metadata.className = "agent-metadata";
|
|
505
985
|
metadata.append(
|
|
506
|
-
createTime(agent.lastActivityAtMs, "
|
|
986
|
+
createTime(agent.lastActivityAtMs, t("recent")),
|
|
507
987
|
document.createTextNode(` · ${formatDuration(agent.startedAtMs, agent.stoppedAtMs)}`),
|
|
508
988
|
);
|
|
509
989
|
|
|
510
|
-
const technicalRows = [["
|
|
511
|
-
|
|
512
|
-
technicalRows.push(["원본 역할", agent.agentType]);
|
|
513
|
-
}
|
|
990
|
+
const technicalRows = [[t("agentId"), agent.agentId]];
|
|
991
|
+
technicalRows.push([t("rawProfile"), agent.agentType]);
|
|
514
992
|
|
|
515
|
-
item.append(heading, metadata,
|
|
993
|
+
item.append(heading, metadata, createTechnicalInfo(technicalRows));
|
|
516
994
|
return item;
|
|
517
995
|
}
|
|
518
996
|
|
|
@@ -536,12 +1014,12 @@ function createActivityItem(activity) {
|
|
|
536
1014
|
metadata.className = "activity-metadata";
|
|
537
1015
|
metadata.append(createStatusBadge(activity.status), createTime(activity.occurredAtMs, ""));
|
|
538
1016
|
|
|
539
|
-
const technicalRows = [["
|
|
1017
|
+
const technicalRows = [[t("rawEvent"), activity.eventName]];
|
|
540
1018
|
if (activity.toolName) {
|
|
541
|
-
technicalRows.push(["
|
|
1019
|
+
technicalRows.push([t("rawTool"), activity.toolName]);
|
|
542
1020
|
}
|
|
543
1021
|
|
|
544
|
-
content.append(title, metadata,
|
|
1022
|
+
content.append(title, metadata, createTechnicalInfo(technicalRows));
|
|
545
1023
|
item.append(marker, content);
|
|
546
1024
|
return item;
|
|
547
1025
|
}
|
|
@@ -566,20 +1044,20 @@ function createSessionCard(session) {
|
|
|
566
1044
|
identity.className = "session-identity";
|
|
567
1045
|
const eyebrow = document.createElement("span");
|
|
568
1046
|
eyebrow.className = "session-kind";
|
|
569
|
-
eyebrow.textContent = "
|
|
1047
|
+
eyebrow.textContent = t("parentTask");
|
|
570
1048
|
const title = document.createElement("h3");
|
|
571
|
-
title.textContent = session.workspaceLabel || "
|
|
1049
|
+
title.textContent = session.workspaceLabel || t("projectUnknown");
|
|
572
1050
|
identity.append(
|
|
573
1051
|
eyebrow,
|
|
574
1052
|
title,
|
|
575
|
-
|
|
1053
|
+
createTechnicalInfo([[t("sessionId"), session.sessionId]]),
|
|
576
1054
|
);
|
|
577
1055
|
|
|
578
1056
|
const sessionState = document.createElement("div");
|
|
579
1057
|
sessionState.className = "session-state";
|
|
580
1058
|
sessionState.append(
|
|
581
1059
|
createStatusBadge(session.status),
|
|
582
|
-
createTime(session.lastActivityAtMs, "
|
|
1060
|
+
createTime(session.lastActivityAtMs, t("recentActivity")),
|
|
583
1061
|
);
|
|
584
1062
|
|
|
585
1063
|
cardHeader.append(identity, sessionState);
|
|
@@ -590,8 +1068,11 @@ function createSessionCard(session) {
|
|
|
590
1068
|
const agentPanel = document.createElement("section");
|
|
591
1069
|
agentPanel.className = "session-panel";
|
|
592
1070
|
const agentTitle = document.createElement("h4");
|
|
593
|
-
agentTitle.textContent =
|
|
594
|
-
|
|
1071
|
+
agentTitle.textContent = t("subagentsCount", { count: session.agents.length });
|
|
1072
|
+
const profileNote = document.createElement("p");
|
|
1073
|
+
profileNote.className = "agent-profile-note";
|
|
1074
|
+
profileNote.textContent = t("agentProfileNote");
|
|
1075
|
+
agentPanel.append(agentTitle, profileNote);
|
|
595
1076
|
|
|
596
1077
|
if (session.agents.length) {
|
|
597
1078
|
const list = document.createElement("ul");
|
|
@@ -601,13 +1082,13 @@ function createSessionCard(session) {
|
|
|
601
1082
|
});
|
|
602
1083
|
agentPanel.append(list);
|
|
603
1084
|
} else {
|
|
604
|
-
agentPanel.append(createEmptyPanel("
|
|
1085
|
+
agentPanel.append(createEmptyPanel(t("noSubagents")));
|
|
605
1086
|
}
|
|
606
1087
|
|
|
607
1088
|
const activityPanel = document.createElement("section");
|
|
608
1089
|
activityPanel.className = "session-panel";
|
|
609
1090
|
const activityTitle = document.createElement("h4");
|
|
610
|
-
activityTitle.textContent = "
|
|
1091
|
+
activityTitle.textContent = t("recentActivity");
|
|
611
1092
|
activityPanel.append(activityTitle);
|
|
612
1093
|
|
|
613
1094
|
if (session.recentActivities.length) {
|
|
@@ -618,7 +1099,7 @@ function createSessionCard(session) {
|
|
|
618
1099
|
});
|
|
619
1100
|
activityPanel.append(list);
|
|
620
1101
|
} else {
|
|
621
|
-
activityPanel.append(createEmptyPanel("
|
|
1102
|
+
activityPanel.append(createEmptyPanel(t("noRecentActivity")));
|
|
622
1103
|
}
|
|
623
1104
|
|
|
624
1105
|
panels.append(agentPanel, activityPanel);
|
|
@@ -659,29 +1140,34 @@ function sessionMatchesStatus(session, status) {
|
|
|
659
1140
|
);
|
|
660
1141
|
}
|
|
661
1142
|
|
|
1143
|
+
function observableSessions() {
|
|
1144
|
+
return viewState.sessions.filter((session) => session.sessionId !== excludedSessionId);
|
|
1145
|
+
}
|
|
1146
|
+
|
|
662
1147
|
function filteredSessions() {
|
|
663
1148
|
const query = elements.search.value.trim().toLocaleLowerCase();
|
|
664
1149
|
const status = elements.statusFilter.value;
|
|
665
1150
|
|
|
666
|
-
return
|
|
1151
|
+
return observableSessions()
|
|
667
1152
|
.filter((session) => sessionMatchesQuery(session, query))
|
|
668
1153
|
.filter((session) => sessionMatchesStatus(session, status))
|
|
669
1154
|
.sort(compareSessions);
|
|
670
1155
|
}
|
|
671
1156
|
|
|
672
1157
|
function renderMetrics() {
|
|
673
|
-
const
|
|
1158
|
+
const sessions = observableSessions();
|
|
1159
|
+
const agents = sessions.flatMap((session) => session.agents);
|
|
674
1160
|
const countStatus = (status) => agents.filter((agent) => agent.status === status).length;
|
|
675
1161
|
|
|
676
|
-
elements.metricSessions.textContent = String(
|
|
1162
|
+
elements.metricSessions.textContent = String(sessions.length);
|
|
677
1163
|
elements.metricRunning.textContent = String(countStatus("running"));
|
|
678
|
-
const waitingCount = countStatus("waiting") +
|
|
1164
|
+
const waitingCount = countStatus("waiting") + sessions.filter(
|
|
679
1165
|
(session) => session.status === "waiting",
|
|
680
1166
|
).length;
|
|
681
1167
|
elements.metricWaiting.textContent = String(waitingCount);
|
|
682
1168
|
elements.metricCompleted.textContent = String(countStatus("completed"));
|
|
683
1169
|
elements.lastUpdated.textContent = !viewState.updatedAtMs
|
|
684
|
-
? "
|
|
1170
|
+
? t("noReceivedActivity")
|
|
685
1171
|
: formatDateTime(viewState.updatedAtMs);
|
|
686
1172
|
}
|
|
687
1173
|
|
|
@@ -699,7 +1185,7 @@ function setStateMessage(kind, title, description, includeRetry = false) {
|
|
|
699
1185
|
const retry = document.createElement("button");
|
|
700
1186
|
retry.type = "button";
|
|
701
1187
|
retry.className = "retry-button";
|
|
702
|
-
retry.textContent = "
|
|
1188
|
+
retry.textContent = t("retry");
|
|
703
1189
|
retry.addEventListener("click", refreshState);
|
|
704
1190
|
elements.stateMessage.append(retry);
|
|
705
1191
|
}
|
|
@@ -713,29 +1199,25 @@ function setEmptyObservationMessage() {
|
|
|
713
1199
|
const copy = document.createElement("span");
|
|
714
1200
|
|
|
715
1201
|
if (viewState.diagnostics.length) {
|
|
716
|
-
heading.textContent = "
|
|
717
|
-
copy.textContent =
|
|
1202
|
+
heading.textContent = t("emptyWithDiagnosticsTitle");
|
|
1203
|
+
copy.textContent = t("emptyWithDiagnosticsCopy", { count: viewState.diagnostics.length });
|
|
718
1204
|
} else {
|
|
719
|
-
heading.textContent = "
|
|
720
|
-
copy.textContent = "
|
|
1205
|
+
heading.textContent = t("emptyTitle");
|
|
1206
|
+
copy.textContent = t("emptyCopy");
|
|
721
1207
|
}
|
|
722
1208
|
|
|
723
1209
|
const guidance = document.createElement("div");
|
|
724
1210
|
guidance.className = "empty-guidance";
|
|
725
1211
|
|
|
726
1212
|
const guidanceTitle = document.createElement("h3");
|
|
727
|
-
guidanceTitle.textContent = "
|
|
1213
|
+
guidanceTitle.textContent = t("emptyGuidanceTitle");
|
|
728
1214
|
|
|
729
1215
|
const automaticTracking = document.createElement("p");
|
|
730
1216
|
automaticTracking.className = "automatic-tracking";
|
|
731
|
-
automaticTracking.textContent = "
|
|
1217
|
+
automaticTracking.textContent = t("automaticTracking");
|
|
732
1218
|
|
|
733
1219
|
const steps = document.createElement("ol");
|
|
734
|
-
for (const step of [
|
|
735
|
-
"플러그인을 설치한 뒤 공식 Codex 앱을 완전히 재시작했는지 확인합니다.",
|
|
736
|
-
"새 작업에서 표시되는 Codex Agent View hook 명령을 검토하고 직접 신뢰합니다.",
|
|
737
|
-
"신뢰 설정 후 새 작업을 시작해 작업 에이전트를 실행합니다. 새 활동은 이 목록에 자동으로 추가됩니다.",
|
|
738
|
-
]) {
|
|
1220
|
+
for (const step of [t("emptyStep1"), t("emptyStep2"), t("emptyStep3")]) {
|
|
739
1221
|
const item = document.createElement("li");
|
|
740
1222
|
item.textContent = step;
|
|
741
1223
|
steps.append(item);
|
|
@@ -743,15 +1225,16 @@ function setEmptyObservationMessage() {
|
|
|
743
1225
|
|
|
744
1226
|
const boundary = document.createElement("p");
|
|
745
1227
|
boundary.className = "observation-boundary";
|
|
746
|
-
boundary.textContent = "
|
|
1228
|
+
boundary.textContent = t("observationBoundary");
|
|
747
1229
|
|
|
748
1230
|
guidance.append(guidanceTitle, automaticTracking, steps, boundary);
|
|
749
1231
|
|
|
750
1232
|
if (viewState.diagnostics.length) {
|
|
751
|
-
const diagnostics = document.createElement("
|
|
752
|
-
diagnostics.className = "diagnostic-
|
|
753
|
-
const
|
|
754
|
-
|
|
1233
|
+
const diagnostics = document.createElement("div");
|
|
1234
|
+
diagnostics.className = "diagnostic-info";
|
|
1235
|
+
const diagnosticsTitle = document.createElement("p");
|
|
1236
|
+
diagnosticsTitle.className = "diagnostic-info-title";
|
|
1237
|
+
diagnosticsTitle.textContent = t("diagnosticsCount", { count: viewState.diagnostics.length });
|
|
755
1238
|
const codes = document.createElement("ul");
|
|
756
1239
|
const diagnosticCounts = new Map();
|
|
757
1240
|
for (const { code } of viewState.diagnostics) {
|
|
@@ -761,10 +1244,10 @@ function setEmptyObservationMessage() {
|
|
|
761
1244
|
const item = document.createElement("li");
|
|
762
1245
|
const code = document.createElement("code");
|
|
763
1246
|
code.textContent = diagnosticCode;
|
|
764
|
-
item.append(code, ` · ${count}
|
|
1247
|
+
item.append(code, ` · ${t("diagnosticOccurrences", { count })}`);
|
|
765
1248
|
codes.append(item);
|
|
766
1249
|
}
|
|
767
|
-
diagnostics.append(
|
|
1250
|
+
diagnostics.append(diagnosticsTitle, codes);
|
|
768
1251
|
guidance.append(diagnostics);
|
|
769
1252
|
}
|
|
770
1253
|
|
|
@@ -772,10 +1255,11 @@ function setEmptyObservationMessage() {
|
|
|
772
1255
|
}
|
|
773
1256
|
|
|
774
1257
|
function renderSessions() {
|
|
1258
|
+
const sessions = observableSessions();
|
|
775
1259
|
const visibleSessions = filteredSessions();
|
|
776
1260
|
const hasFilters = elements.search.value.trim() || elements.statusFilter.value !== "all";
|
|
777
1261
|
|
|
778
|
-
elements.toolbar.hidden =
|
|
1262
|
+
elements.toolbar.hidden = sessions.length === 0;
|
|
779
1263
|
elements.stateMessage.hidden = false;
|
|
780
1264
|
|
|
781
1265
|
elements.sessionList.replaceChildren();
|
|
@@ -784,34 +1268,34 @@ function renderSessions() {
|
|
|
784
1268
|
});
|
|
785
1269
|
|
|
786
1270
|
elements.resultsSummary.textContent = hasFilters
|
|
787
|
-
?
|
|
788
|
-
:
|
|
1271
|
+
? t("resultsFiltered", { total: sessions.length, visible: visibleSessions.length })
|
|
1272
|
+
: t("resultsTotal", { count: sessions.length });
|
|
789
1273
|
|
|
790
1274
|
if (!viewState.hasLoaded) {
|
|
791
1275
|
elements.sessionList.hidden = true;
|
|
792
|
-
setStateMessage("loading", "
|
|
1276
|
+
setStateMessage("loading", t("loadingState"), t("connectingCopy"));
|
|
793
1277
|
return;
|
|
794
1278
|
}
|
|
795
1279
|
|
|
796
1280
|
if (viewState.errorMessage) {
|
|
797
1281
|
elements.sessionList.hidden = !visibleSessions.length;
|
|
798
1282
|
const description = viewState.canRetry
|
|
799
|
-
?
|
|
800
|
-
? "
|
|
801
|
-
: "
|
|
802
|
-
: viewState.errorMessage;
|
|
1283
|
+
? sessions.length
|
|
1284
|
+
? t("retryWithState")
|
|
1285
|
+
: t("retryWithoutState")
|
|
1286
|
+
: viewState.errorKey ? t(viewState.errorKey) : viewState.errorMessage;
|
|
803
1287
|
setStateMessage(
|
|
804
1288
|
"error",
|
|
805
1289
|
viewState.canRetry
|
|
806
|
-
? "
|
|
807
|
-
: "
|
|
1290
|
+
? t("disconnectedTitle")
|
|
1291
|
+
: t("authTitle"),
|
|
808
1292
|
description,
|
|
809
1293
|
viewState.canRetry,
|
|
810
1294
|
);
|
|
811
1295
|
return;
|
|
812
1296
|
}
|
|
813
1297
|
|
|
814
|
-
if (!
|
|
1298
|
+
if (!sessions.length) {
|
|
815
1299
|
elements.sessionList.hidden = true;
|
|
816
1300
|
setEmptyObservationMessage();
|
|
817
1301
|
return;
|
|
@@ -821,8 +1305,8 @@ function renderSessions() {
|
|
|
821
1305
|
elements.sessionList.hidden = true;
|
|
822
1306
|
setStateMessage(
|
|
823
1307
|
"empty",
|
|
824
|
-
"
|
|
825
|
-
"
|
|
1308
|
+
t("searchEmptyTitle"),
|
|
1309
|
+
t("searchEmptyCopy"),
|
|
826
1310
|
);
|
|
827
1311
|
return;
|
|
828
1312
|
}
|
|
@@ -840,9 +1324,9 @@ function render() {
|
|
|
840
1324
|
function setConnectionStatus(status, labelOverride = "") {
|
|
841
1325
|
elements.connectionStatus.dataset.status = status;
|
|
842
1326
|
const labels = {
|
|
843
|
-
connecting: "
|
|
844
|
-
connected: "
|
|
845
|
-
error: "
|
|
1327
|
+
connecting: t("connectionConnecting"),
|
|
1328
|
+
connected: t("connectionConnected"),
|
|
1329
|
+
error: t("connectionRetrying"),
|
|
846
1330
|
};
|
|
847
1331
|
elements.connectionLabel.textContent = labelOverride || labels[status];
|
|
848
1332
|
}
|
|
@@ -856,8 +1340,9 @@ async function refreshState() {
|
|
|
856
1340
|
viewState.hasLoaded = true;
|
|
857
1341
|
viewState.canRetry = false;
|
|
858
1342
|
viewState.authenticationFailed = true;
|
|
859
|
-
viewState.
|
|
860
|
-
|
|
1343
|
+
viewState.errorKey = "missingToken";
|
|
1344
|
+
viewState.errorMessage = t(viewState.errorKey);
|
|
1345
|
+
setConnectionStatus("error", t("authenticationRequired"));
|
|
861
1346
|
render();
|
|
862
1347
|
return;
|
|
863
1348
|
}
|
|
@@ -882,13 +1367,14 @@ async function refreshState() {
|
|
|
882
1367
|
viewState.hasLoaded = true;
|
|
883
1368
|
viewState.canRetry = false;
|
|
884
1369
|
viewState.authenticationFailed = true;
|
|
885
|
-
viewState.
|
|
886
|
-
|
|
1370
|
+
viewState.errorKey = "expiredToken";
|
|
1371
|
+
viewState.errorMessage = t(viewState.errorKey);
|
|
1372
|
+
setConnectionStatus("error", t("authenticationRequired"));
|
|
887
1373
|
return;
|
|
888
1374
|
}
|
|
889
1375
|
|
|
890
1376
|
if (!response.ok) {
|
|
891
|
-
throw new Error(
|
|
1377
|
+
throw new Error(t("requestFailed", { status: response.status }));
|
|
892
1378
|
}
|
|
893
1379
|
|
|
894
1380
|
const nextState = normalizeState(await response.json());
|
|
@@ -897,6 +1383,7 @@ async function refreshState() {
|
|
|
897
1383
|
viewState.diagnostics = nextState.diagnostics;
|
|
898
1384
|
viewState.hasLoaded = true;
|
|
899
1385
|
viewState.errorMessage = "";
|
|
1386
|
+
viewState.errorKey = "";
|
|
900
1387
|
viewState.canRetry = true;
|
|
901
1388
|
setConnectionStatus("connected");
|
|
902
1389
|
} catch (error) {
|
|
@@ -904,7 +1391,8 @@ async function refreshState() {
|
|
|
904
1391
|
viewState.canRetry = true;
|
|
905
1392
|
viewState.errorMessage = error instanceof Error
|
|
906
1393
|
? error.message
|
|
907
|
-
: "
|
|
1394
|
+
: t("unknownConnectionError");
|
|
1395
|
+
viewState.errorKey = "";
|
|
908
1396
|
setConnectionStatus("error");
|
|
909
1397
|
} finally {
|
|
910
1398
|
viewState.requestInFlight = false;
|
|
@@ -914,6 +1402,7 @@ async function refreshState() {
|
|
|
914
1402
|
|
|
915
1403
|
elements.search.addEventListener("input", renderSessions);
|
|
916
1404
|
elements.statusFilter.addEventListener("change", renderSessions);
|
|
1405
|
+
elements.language.addEventListener("change", (event) => setLanguage(event.target.value));
|
|
917
1406
|
elements.toolbar.addEventListener("submit", (event) => {
|
|
918
1407
|
event.preventDefault();
|
|
919
1408
|
});
|
|
@@ -923,7 +1412,8 @@ window.addEventListener("offline", () => {
|
|
|
923
1412
|
return;
|
|
924
1413
|
}
|
|
925
1414
|
viewState.canRetry = true;
|
|
926
|
-
viewState.errorMessage = "
|
|
1415
|
+
viewState.errorMessage = t("offline");
|
|
1416
|
+
viewState.errorKey = "offline";
|
|
927
1417
|
setConnectionStatus("error");
|
|
928
1418
|
render();
|
|
929
1419
|
});
|
|
@@ -933,6 +1423,7 @@ document.addEventListener("visibilitychange", () => {
|
|
|
933
1423
|
}
|
|
934
1424
|
});
|
|
935
1425
|
|
|
1426
|
+
applyStaticTranslations();
|
|
936
1427
|
render();
|
|
937
1428
|
refreshState();
|
|
938
1429
|
if (accessToken) {
|