codex-agent-view 0.4.1 → 0.4.3

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/public/app.js CHANGED
@@ -17,6 +17,42 @@ const STATUS_ORDER = Object.freeze({
17
17
  completed: 3,
18
18
  });
19
19
 
20
+ const ACTIVITY_LABELS = Object.freeze({
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: "사용자 승인 요청",
30
+ });
31
+
32
+ const TOOL_LABELS = Object.freeze({
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: "터미널 입력",
45
+ });
46
+
47
+ const AGENT_ROLE_LABELS = Object.freeze({
48
+ explorer: "조사 담당",
49
+ reviewer: "검토 담당",
50
+ worker: "작업 담당",
51
+ });
52
+
53
+ const HIDDEN_AGENT_ROLES = new Set(["", "default", "unknown"]);
54
+ const CONTROL_CHARACTERS = /[\u0000-\u001f\u007f-\u009f]/g;
55
+
20
56
  function consumeAccessToken() {
21
57
  const fragment = new URLSearchParams(window.location.hash.slice(1));
22
58
  const fragmentToken = fragment.get("token")?.trim() || "";
@@ -81,6 +117,59 @@ function safeString(value, fallback) {
81
117
  return typeof value === "string" && value.trim() ? value.trim() : fallback;
82
118
  }
83
119
 
120
+ function sanitizedFallbackLabel(value, fallback) {
121
+ const normalized = safeString(value, "")
122
+ .replace(CONTROL_CHARACTERS, " ")
123
+ .replace(/([a-z\d])([A-Z])/g, "$1 $2")
124
+ .replace(/[_./:-]+/g, " ")
125
+ .replace(/\s+/g, " ")
126
+ .trim()
127
+ .slice(0, 48);
128
+
129
+ if (!normalized) {
130
+ return fallback;
131
+ }
132
+
133
+ return normalized.replace(/\b[a-z]/g, (character) => character.toUpperCase());
134
+ }
135
+
136
+ function normalizedLookupKey(value) {
137
+ return safeString(value, "").replace(/[^a-zA-Z0-9_]/g, "").toLocaleLowerCase();
138
+ }
139
+
140
+ function formatAgentRole(agentType) {
141
+ const key = normalizedLookupKey(agentType);
142
+ if (HIDDEN_AGENT_ROLES.has(key)) {
143
+ return "";
144
+ }
145
+ return AGENT_ROLE_LABELS[key] || sanitizedFallbackLabel(agentType, "");
146
+ }
147
+
148
+ function formatToolLabel(toolName) {
149
+ const key = normalizedLookupKey(toolName);
150
+ return TOOL_LABELS[key] || `도구 · ${sanitizedFallbackLabel(toolName, "이름 미상")}`;
151
+ }
152
+
153
+ function formatActivityLabel(activity) {
154
+ if (activity.eventName === "subagent_started" && activity.agentOrdinal !== null) {
155
+ return `작업 에이전트 ${activity.agentOrdinal} 시작`;
156
+ }
157
+ if (activity.eventName === "subagent_stopped" && activity.agentOrdinal !== null) {
158
+ return `작업 에이전트 ${activity.agentOrdinal} 완료`;
159
+ }
160
+ if (activity.eventName === "permission_requested" && activity.toolName) {
161
+ return `${formatToolLabel(activity.toolName)} 승인 요청`;
162
+ }
163
+ if (
164
+ (activity.eventName === "tool_started" || activity.eventName === "tool_completed") &&
165
+ activity.toolName
166
+ ) {
167
+ return formatToolLabel(activity.toolName);
168
+ }
169
+ return ACTIVITY_LABELS[activity.eventName] ||
170
+ `활동 · ${sanitizedFallbackLabel(activity.eventName, "알 수 없음")}`;
171
+ }
172
+
84
173
  function safeTimestamp(value) {
85
174
  return Number.isFinite(value) && value >= 0 && value <= 8_640_000_000_000_000
86
175
  ? value
@@ -122,16 +211,72 @@ function normalizeAgent(value, index) {
122
211
  };
123
212
  }
124
213
 
214
+ function agentOrderTimestamp(agent) {
215
+ return agent.startedAtMs ?? agent.stoppedAtMs ?? agent.lastActivityAtMs ?? Number.MAX_SAFE_INTEGER;
216
+ }
217
+
218
+ function assignStableAgentOrdinals(agents) {
219
+ const ordinalByAgentId = new Map(
220
+ [...agents]
221
+ .sort((left, right) => (
222
+ agentOrderTimestamp(left) - agentOrderTimestamp(right) ||
223
+ left.agentId.localeCompare(right.agentId)
224
+ ))
225
+ .map((agent, index) => [agent.agentId, index + 1]),
226
+ );
227
+
228
+ return agents.map((agent) => ({
229
+ ...agent,
230
+ ordinal: ordinalByAgentId.get(agent.agentId),
231
+ }));
232
+ }
233
+
125
234
  function normalizeActivity(value) {
126
235
  const activity = isRecord(value) ? value : {};
127
236
  return {
128
237
  eventName: safeString(activity.type, "unknown_event"),
238
+ agentId: safeString(activity.agent_id, ""),
239
+ agentOrdinal: null,
129
240
  toolName: safeString(activity.tool_name, ""),
241
+ toolUseId: safeString(activity.tool_use_id, ""),
130
242
  status: normalizeCoreStatus(activity.status),
131
243
  occurredAtMs: safeTimestamp(activity.received_at_ms),
132
244
  };
133
245
  }
134
246
 
247
+ function isToolLifecycle(activity) {
248
+ return activity.eventName === "tool_started" || activity.eventName === "tool_completed";
249
+ }
250
+
251
+ function shouldReplaceToolActivity(candidate, current) {
252
+ const candidateTime = candidate.occurredAtMs ?? -1;
253
+ const currentTime = current.occurredAtMs ?? -1;
254
+ if (candidateTime !== currentTime) {
255
+ return candidateTime > currentTime;
256
+ }
257
+ return candidate.eventName === "tool_completed" && current.eventName !== "tool_completed";
258
+ }
259
+
260
+ function collapseToolActivities(activities) {
261
+ const latestByToolUseId = new Map();
262
+
263
+ for (const activity of activities) {
264
+ if (!activity.toolUseId || !isToolLifecycle(activity)) {
265
+ continue;
266
+ }
267
+ const current = latestByToolUseId.get(activity.toolUseId);
268
+ if (!current || shouldReplaceToolActivity(activity, current)) {
269
+ latestByToolUseId.set(activity.toolUseId, activity);
270
+ }
271
+ }
272
+
273
+ return activities.filter((activity) => (
274
+ !activity.toolUseId ||
275
+ !isToolLifecycle(activity) ||
276
+ latestByToolUseId.get(activity.toolUseId) === activity
277
+ ));
278
+ }
279
+
135
280
  function normalizeDiagnostic(value) {
136
281
  const diagnostic = isRecord(value) ? value : {};
137
282
  return {
@@ -160,10 +305,18 @@ function deriveSessionStatus(session, agents, recentActivities) {
160
305
  function normalizeSession(value, index) {
161
306
  const session = isRecord(value) ? value : {};
162
307
  const agents = Array.isArray(session.agents)
163
- ? session.agents.map(normalizeAgent)
308
+ ? assignStableAgentOrdinals(session.agents.map(normalizeAgent))
164
309
  : [];
310
+ const agentOrdinalById = new Map(
311
+ agents.map((agent) => [agent.agentId, agent.ordinal]),
312
+ );
165
313
  const recentActivities = Array.isArray(session.recent_activities)
166
- ? session.recent_activities.map(normalizeActivity)
314
+ ? collapseToolActivities(session.recent_activities.map(normalizeActivity)).map(
315
+ (activity) => ({
316
+ ...activity,
317
+ agentOrdinal: agentOrdinalById.get(activity.agentId) ?? null,
318
+ }),
319
+ )
167
320
  : [];
168
321
 
169
322
  return {
@@ -203,6 +356,11 @@ function compareAgents(left, right) {
203
356
  return statusDifference || compareByActivity(left, right);
204
357
  }
205
358
 
359
+ function compareSessions(left, right) {
360
+ const statusDifference = STATUS_ORDER[left.status] - STATUS_ORDER[right.status];
361
+ return statusDifference || compareByActivity(left, right);
362
+ }
363
+
206
364
  function formatDateTime(timestampMs) {
207
365
  if (timestampMs === null) {
208
366
  return "시간 정보 없음";
@@ -293,21 +451,53 @@ function createTime(timestampMs, prefix) {
293
451
  return wrapper;
294
452
  }
295
453
 
454
+ function createTechnicalDetails(rows) {
455
+ const details = document.createElement("details");
456
+ details.className = "technical-details";
457
+
458
+ const summary = document.createElement("summary");
459
+ summary.textContent = "기술 정보";
460
+ const list = document.createElement("dl");
461
+
462
+ for (const [label, value] of rows) {
463
+ if (!value) {
464
+ continue;
465
+ }
466
+ const term = document.createElement("dt");
467
+ term.textContent = label;
468
+ const description = document.createElement("dd");
469
+ const code = document.createElement("code");
470
+ code.textContent = value;
471
+ description.append(code);
472
+ list.append(term, description);
473
+ }
474
+
475
+ details.append(summary, list);
476
+ return details;
477
+ }
478
+
296
479
  function createAgentItem(agent) {
297
480
  const item = document.createElement("li");
298
481
  item.className = "agent-item";
482
+ item.dataset.status = agent.status;
299
483
 
300
484
  const heading = document.createElement("div");
301
485
  heading.className = "agent-heading";
302
486
 
303
487
  const identity = document.createElement("div");
304
488
  identity.className = "agent-identity";
305
- const type = document.createElement("span");
306
- type.className = "agent-type";
307
- type.textContent = agent.agentType;
308
- const id = document.createElement("code");
309
- id.textContent = agent.agentId;
310
- identity.append(type, id);
489
+ const name = document.createElement("strong");
490
+ name.className = "agent-name";
491
+ name.textContent = `작업 에이전트 ${agent.ordinal}`;
492
+ identity.append(name);
493
+
494
+ const roleLabel = formatAgentRole(agent.agentType);
495
+ if (roleLabel) {
496
+ const role = document.createElement("span");
497
+ role.className = "agent-role";
498
+ role.textContent = roleLabel;
499
+ identity.append(role);
500
+ }
311
501
  heading.append(identity, createStatusBadge(agent.status));
312
502
 
313
503
  const metadata = document.createElement("div");
@@ -317,7 +507,12 @@ function createAgentItem(agent) {
317
507
  document.createTextNode(` · ${formatDuration(agent.startedAtMs, agent.stoppedAtMs)}`),
318
508
  );
319
509
 
320
- item.append(heading, metadata);
510
+ const technicalRows = [["에이전트 ID", agent.agentId]];
511
+ if (roleLabel) {
512
+ technicalRows.push(["원본 역할", agent.agentType]);
513
+ }
514
+
515
+ item.append(heading, metadata, createTechnicalDetails(technicalRows));
321
516
  return item;
322
517
  }
323
518
 
@@ -334,20 +529,19 @@ function createActivityItem(activity) {
334
529
  const title = document.createElement("div");
335
530
  title.className = "activity-title";
336
531
  const eventName = document.createElement("strong");
337
- eventName.textContent = activity.eventName;
532
+ eventName.textContent = formatActivityLabel(activity);
338
533
  title.append(eventName);
339
534
 
340
- if (activity.toolName) {
341
- const tool = document.createElement("code");
342
- tool.textContent = activity.toolName;
343
- title.append(tool);
344
- }
345
-
346
535
  const metadata = document.createElement("div");
347
536
  metadata.className = "activity-metadata";
348
537
  metadata.append(createStatusBadge(activity.status), createTime(activity.occurredAtMs, ""));
349
538
 
350
- content.append(title, metadata);
539
+ const technicalRows = [["원본 이벤트", activity.eventName]];
540
+ if (activity.toolName) {
541
+ technicalRows.push(["원본 도구", activity.toolName]);
542
+ }
543
+
544
+ content.append(title, metadata, createTechnicalDetails(technicalRows));
351
545
  item.append(marker, content);
352
546
  return item;
353
547
  }
@@ -363,6 +557,7 @@ function createSessionCard(session) {
363
557
  const listItem = document.createElement("li");
364
558
  const article = document.createElement("article");
365
559
  article.className = "session-card";
560
+ article.dataset.status = session.status;
366
561
 
367
562
  const cardHeader = document.createElement("header");
368
563
  cardHeader.className = "session-header";
@@ -371,13 +566,14 @@ function createSessionCard(session) {
371
566
  identity.className = "session-identity";
372
567
  const eyebrow = document.createElement("span");
373
568
  eyebrow.className = "session-kind";
374
- eyebrow.append("PARENT TASK · ");
375
- const id = document.createElement("code");
376
- id.textContent = session.sessionId;
377
- eyebrow.append(id);
569
+ eyebrow.textContent = "부모 작업";
378
570
  const title = document.createElement("h3");
379
571
  title.textContent = session.workspaceLabel || "프로젝트 정보 없음";
380
- identity.append(eyebrow, title);
572
+ identity.append(
573
+ eyebrow,
574
+ title,
575
+ createTechnicalDetails([["세션 ID", session.sessionId]]),
576
+ );
381
577
 
382
578
  const sessionState = document.createElement("div");
383
579
  sessionState.className = "session-state";
@@ -394,7 +590,7 @@ function createSessionCard(session) {
394
590
  const agentPanel = document.createElement("section");
395
591
  agentPanel.className = "session-panel";
396
592
  const agentTitle = document.createElement("h4");
397
- agentTitle.textContent = `Subagents · ${session.agents.length}`;
593
+ agentTitle.textContent = `작업 에이전트 · ${session.agents.length}`;
398
594
  agentPanel.append(agentTitle);
399
595
 
400
596
  if (session.agents.length) {
@@ -405,7 +601,7 @@ function createSessionCard(session) {
405
601
  });
406
602
  agentPanel.append(list);
407
603
  } else {
408
- agentPanel.append(createEmptyPanel("이 task에서 관찰된 subagent가 없습니다."));
604
+ agentPanel.append(createEmptyPanel("이 부모 작업에서 관찰된 작업 에이전트가 없습니다."));
409
605
  }
410
606
 
411
607
  const activityPanel = document.createElement("section");
@@ -422,7 +618,7 @@ function createSessionCard(session) {
422
618
  });
423
619
  activityPanel.append(list);
424
620
  } else {
425
- activityPanel.append(createEmptyPanel("표시할 lifecycle event가 없습니다."));
621
+ activityPanel.append(createEmptyPanel("표시할 최근 활동이 없습니다."));
426
622
  }
427
623
 
428
624
  panels.append(agentPanel, activityPanel);
@@ -440,7 +636,11 @@ function sessionMatchesQuery(session, query) {
440
636
  session.sessionId,
441
637
  session.workspaceLabel,
442
638
  session.status,
443
- ...session.agents.flatMap((agent) => [agent.agentId, agent.agentType, agent.status]),
639
+ ...session.agents.flatMap((agent) => [
640
+ agent.agentId,
641
+ formatAgentRole(agent.agentType),
642
+ agent.status,
643
+ ]),
444
644
  ...session.recentActivities.flatMap((activity) => [
445
645
  activity.eventName,
446
646
  activity.toolName,
@@ -466,7 +666,7 @@ function filteredSessions() {
466
666
  return [...viewState.sessions]
467
667
  .filter((session) => sessionMatchesQuery(session, query))
468
668
  .filter((session) => sessionMatchesStatus(session, status))
469
- .sort(compareByActivity);
669
+ .sort(compareSessions);
470
670
  }
471
671
 
472
672
  function renderMetrics() {
@@ -481,7 +681,7 @@ function renderMetrics() {
481
681
  elements.metricWaiting.textContent = String(waitingCount);
482
682
  elements.metricCompleted.textContent = String(countStatus("completed"));
483
683
  elements.lastUpdated.textContent = !viewState.updatedAtMs
484
- ? "수신된 hook 없음"
684
+ ? "수신된 활동 없음"
485
685
  : formatDateTime(viewState.updatedAtMs);
486
686
  }
487
687
 
@@ -513,11 +713,11 @@ function setEmptyObservationMessage() {
513
713
  const copy = document.createElement("span");
514
714
 
515
715
  if (viewState.diagnostics.length) {
516
- heading.textContent = "표시 가능한 task가 없습니다.";
517
- copy.textContent = `Monitor가 hook 입력 ${viewState.diagnostics.length}건을 받았지만 유효한 session으로 적용하지 않았습니다.`;
716
+ heading.textContent = "표시 가능한 부모 작업이 없습니다.";
717
+ copy.textContent = `로컬 모니터가 활동 정보 ${viewState.diagnostics.length}건을 받았지만 표시 가능한 부모 작업으로 적용하지 못했습니다.`;
518
718
  } else {
519
- heading.textContent = "이 관찰 창에서 수신된 hook event가 0건입니다.";
520
- copy.textContent = "로컬 monitor 연결은 정상입니다. 이 결과만으로 Codex에 실행 중인 task나 agent가 없다고 판단할 수 없습니다.";
719
+ heading.textContent = "이 관찰 화면에서 수신된 작업 활동이 없습니다.";
720
+ copy.textContent = "로컬 모니터 연결은 정상입니다. 이 결과만으로 Codex에 실행 중인 부모 작업이나 작업 에이전트가 없다고 판단할 수 없습니다.";
521
721
  }
522
722
 
523
723
  const guidance = document.createElement("div");
@@ -528,13 +728,13 @@ function setEmptyObservationMessage() {
528
728
 
529
729
  const automaticTracking = document.createElement("p");
530
730
  automaticTracking.className = "automatic-tracking";
531
- automaticTracking.textContent = "task ID를 입력하거나 task별로 등록할 필요가 없습니다. Trusted hook event가 자동으로 이 목록에 추가됩니다.";
731
+ automaticTracking.textContent = "작업 ID를 입력하거나 작업별로 등록할 필요가 없습니다. 신뢰한 hook 작업 활동이 이 목록에 자동으로 추가됩니다.";
532
732
 
533
733
  const steps = document.createElement("ol");
534
734
  for (const step of [
535
- "Plugin을 설치한 뒤 공식 Codex 앱을 완전히 재시작했는지 확인합니다.",
536
- "새 task에서 표시되는 Codex Agent View hook command를 검토하고 직접 trust합니다.",
537
- "Trust 이후task를 시작해 subagent 작업을 실행합니다. Hook event가 이 목록에 자동으로 추가됩니다.",
735
+ "플러그인을 설치한 뒤 공식 Codex 앱을 완전히 재시작했는지 확인합니다.",
736
+ "새 작업에서 표시되는 Codex Agent View hook 명령을 검토하고 직접 신뢰합니다.",
737
+ "신뢰 설정 작업을 시작해 작업 에이전트를 실행합니다. 활동은 이 목록에 자동으로 추가됩니다.",
538
738
  ]) {
539
739
  const item = document.createElement("li");
540
740
  item.textContent = step;
@@ -543,7 +743,7 @@ function setEmptyObservationMessage() {
543
743
 
544
744
  const boundary = document.createElement("p");
545
745
  boundary.className = "observation-boundary";
546
- boundary.textContent = "관찰 window는trusted hook에서 시작합니다. 그 전에 이미 지나간 event와 로컬 상태 수집이 중단된 동안의 event는 재생되지 않으며, 수집이 다시 시작되면 새 관찰 window가 열립니다.";
746
+ boundary.textContent = "관찰 화면은번째로 신뢰한 hook 받은 시점부터 시작합니다. 그 전에 이미 지나간 활동과 로컬 상태 수집이 중단된 동안의 활동은 재생되지 않으며, 수집이 다시 시작되면 새 관찰 화면이 열립니다.";
547
747
 
548
748
  guidance.append(guidanceTitle, automaticTracking, steps, boundary);
549
749
 
@@ -551,7 +751,7 @@ function setEmptyObservationMessage() {
551
751
  const diagnostics = document.createElement("details");
552
752
  diagnostics.className = "diagnostic-details";
553
753
  const summary = document.createElement("summary");
554
- summary.textContent = `검증 diagnostic ${viewState.diagnostics.length}건`;
754
+ summary.textContent = `검증 정보 ${viewState.diagnostics.length}건`;
555
755
  const codes = document.createElement("ul");
556
756
  const diagnosticCounts = new Map();
557
757
  for (const { code } of viewState.diagnostics) {
@@ -585,11 +785,11 @@ function renderSessions() {
585
785
 
586
786
  elements.resultsSummary.textContent = hasFilters
587
787
  ? `전체 ${viewState.sessions.length}개 중 ${visibleSessions.length}개 표시`
588
- : `${viewState.sessions.length}개 부모 task`;
788
+ : `${viewState.sessions.length}개 부모 작업`;
589
789
 
590
790
  if (!viewState.hasLoaded) {
591
791
  elements.sessionList.hidden = true;
592
- setStateMessage("loading", "상태를 불러오는 중입니다.", "로컬 monitor에 연결하고 있습니다.");
792
+ setStateMessage("loading", "상태를 불러오는 중입니다.", "로컬 모니터에 연결하고 있습니다.");
593
793
  return;
594
794
  }
595
795
 
@@ -604,7 +804,7 @@ function renderSessions() {
604
804
  "error",
605
805
  viewState.canRetry
606
806
  ? "로컬 상태 연결이 끊겨 다시 시도 중입니다."
607
- : "이 live view를 인증할 수 없습니다.",
807
+ : "이 실시간 화면을 인증할 수 없습니다.",
608
808
  description,
609
809
  viewState.canRetry,
610
810
  );
@@ -641,7 +841,7 @@ function setConnectionStatus(status, labelOverride = "") {
641
841
  elements.connectionStatus.dataset.status = status;
642
842
  const labels = {
643
843
  connecting: "로컬 상태 연결 중",
644
- connected: "로컬 monitor 연결됨",
844
+ connected: "로컬 모니터 연결됨",
645
845
  error: "연결 끊김 · 재시도 중",
646
846
  };
647
847
  elements.connectionLabel.textContent = labelOverride || labels[status];
@@ -656,8 +856,8 @@ async function refreshState() {
656
856
  viewState.hasLoaded = true;
657
857
  viewState.canRetry = false;
658
858
  viewState.authenticationFailed = true;
659
- viewState.errorMessage = "이 탭에는 접근 token이 없습니다. Codex 앱에서 Codex Agent View live view 열기를 다시 요청하세요.";
660
- setConnectionStatus("error", "live view 인증 필요");
859
+ viewState.errorMessage = "이 탭에는 접근 토큰이 없습니다. Codex 앱에서 Codex Agent View 실시간 화면 열기를 다시 요청하세요.";
860
+ setConnectionStatus("error", "실시간 화면 인증 필요");
661
861
  render();
662
862
  return;
663
863
  }
@@ -682,8 +882,8 @@ async function refreshState() {
682
882
  viewState.hasLoaded = true;
683
883
  viewState.canRetry = false;
684
884
  viewState.authenticationFailed = true;
685
- viewState.errorMessage = "이 live view의 인증이 더 이상 유효하지 않습니다. Codex 앱에서 Codex Agent View live view 열기를 다시 요청하세요.";
686
- setConnectionStatus("error", "live view 인증 필요");
885
+ viewState.errorMessage = "이 실시간 화면의 인증이 더 이상 유효하지 않습니다. Codex 앱에서 Codex Agent View 실시간 화면 열기를 다시 요청하세요.";
886
+ setConnectionStatus("error", "실시간 화면 인증 필요");
687
887
  return;
688
888
  }
689
889
 
package/public/index.html CHANGED
@@ -6,7 +6,7 @@
6
6
  <meta name="color-scheme" content="light dark">
7
7
  <meta
8
8
  name="description"
9
- content="공식 Codex 앱의 부모 task와 subagent 상태를 로컬에서 읽기 전용으로 확인합니다."
9
+ content="공식 Codex 앱의 부모 작업과 작업 에이전트 상태를 로컬에서 읽기 전용으로 확인합니다."
10
10
  >
11
11
  <title>Codex Agent View</title>
12
12
  <link rel="stylesheet" href="/assets/styles.css">
@@ -21,7 +21,7 @@
21
21
  <span class="brand-mark" aria-hidden="true">CA</span>
22
22
  <span>
23
23
  <strong>Codex Agent View</strong>
24
- <small>Local companion monitor</small>
24
+ <small>로컬 읽기 전용 모니터</small>
25
25
  </span>
26
26
  </a>
27
27
 
@@ -41,10 +41,10 @@
41
41
  <main id="main-content" tabindex="-1">
42
42
  <section class="hero" aria-labelledby="page-title">
43
43
  <div>
44
- <p class="eyebrow">LOCAL · READ ONLY</p>
44
+ <p class="eyebrow">로컬 · 읽기 전용</p>
45
45
  <h1 id="page-title">작업 흐름을 한눈에</h1>
46
46
  <p class="hero-copy">
47
- 공식 Codex 앱은 그대로 두고, 부모 task와 subagent의 현재 상태만
47
+ 공식 Codex 앱은 그대로 두고, 부모 작업과 작업 에이전트의 현재 상태만
48
48
  로컬에서 확인합니다.
49
49
  </p>
50
50
  </div>
@@ -63,32 +63,32 @@
63
63
 
64
64
  <section class="metrics" aria-label="작업 상태 요약">
65
65
  <article class="metric-card">
66
- <p>부모 task</p>
66
+ <p>부모 작업</p>
67
67
  <strong id="metric-sessions">0</strong>
68
68
  <span>현재 관찰 중</span>
69
69
  </article>
70
70
  <article class="metric-card metric-running">
71
- <p>실행 중 agent</p>
71
+ <p>실행 중 에이전트</p>
72
72
  <strong id="metric-running">0</strong>
73
73
  <span>작업 수행 중</span>
74
74
  </article>
75
75
  <article class="metric-card metric-waiting">
76
76
  <p>대기 상태</p>
77
77
  <strong id="metric-waiting">0</strong>
78
- <span>사용자 또는 다음 작업 대기</span>
78
+ <span>사용자 응답 또는 다음 작업 대기</span>
79
79
  </article>
80
80
  <article class="metric-card metric-completed">
81
- <p>완료 agent</p>
81
+ <p>완료 에이전트</p>
82
82
  <strong id="metric-completed">0</strong>
83
- <span>현재 snapshot 기준</span>
83
+ <span>현재 화면 기준</span>
84
84
  </article>
85
85
  </section>
86
86
 
87
87
  <section class="workspace" aria-labelledby="sessions-heading">
88
88
  <div class="section-heading">
89
89
  <div>
90
- <p class="eyebrow">LIVE SESSIONS</p>
91
- <h2 id="sessions-heading">부모 task와 subagent</h2>
90
+ <p class="eyebrow">실시간 작업</p>
91
+ <h2 id="sessions-heading">부모 작업과 작업 에이전트</h2>
92
92
  </div>
93
93
  <p id="results-summary" class="results-summary" role="status" aria-live="polite">
94
94
  상태를 불러오는 중입니다.
@@ -98,7 +98,7 @@
98
98
  <form
99
99
  class="toolbar"
100
100
  role="search"
101
- aria-label="자동 수신된 task와 subagent 목록 필터"
101
+ aria-label="자동 수신된 부모 작업과 작업 에이전트 목록 필터"
102
102
  hidden
103
103
  >
104
104
  <div class="field field-search">
@@ -107,7 +107,7 @@
107
107
  id="session-search"
108
108
  name="query"
109
109
  type="search"
110
- placeholder="자동 수신된 task·agent 목록에서 찾기"
110
+ placeholder="부모 작업·작업 에이전트 목록에서 찾기"
111
111
  autocomplete="off"
112
112
  spellcheck="false"
113
113
  >
@@ -129,13 +129,13 @@
129
129
  <span>Codex 앱의 로컬 상태에 연결하고 있습니다.</span>
130
130
  </div>
131
131
 
132
- <ul id="session-list" class="session-list" aria-label="Codex 부모 task 목록" hidden></ul>
132
+ <ul id="session-list" class="session-list" aria-label="Codex 부모 작업 목록" hidden></ul>
133
133
  </section>
134
134
  </main>
135
135
 
136
136
  <footer>
137
- <p>이 화면은 prompt와 tool input을 표시하지 않습니다.</p>
138
- <p>데이터는 이 기기의 로컬 monitor에서만 읽습니다.</p>
137
+ <p>이 화면은 사용자 요청 내용과 도구 입력 내용을 표시하지 않습니다.</p>
138
+ <p>데이터는 이 기기의 로컬 모니터에서만 읽습니다.</p>
139
139
  </footer>
140
140
  </div>
141
141
  </body>