loadtoagent 1.0.0 → 1.3.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.
Files changed (86) hide show
  1. package/README.ko.md +7 -0
  2. package/README.md +7 -0
  3. package/README.zh-CN.md +7 -0
  4. package/docs/PROVIDER-CONTRACTS.md +23 -1
  5. package/docs/UI-AUDIT-100.md +118 -0
  6. package/docs/UI-AUDIT-101-200.md +120 -0
  7. package/docs/UI-AUDIT-201-300.md +120 -0
  8. package/main.js +183 -37
  9. package/package.json +15 -4
  10. package/preload.js +11 -0
  11. package/renderer/app-agent-actions.js +125 -53
  12. package/renderer/app-bootstrap.js +54 -9
  13. package/renderer/app-dashboard.js +197 -58
  14. package/renderer/app-drawer-content.js +103 -86
  15. package/renderer/app-drawer-data.js +26 -13
  16. package/renderer/app-drawer.js +79 -55
  17. package/renderer/app-events-dialogs.js +146 -37
  18. package/renderer/app-events-filters.js +172 -9
  19. package/renderer/app-events-navigation.js +78 -31
  20. package/renderer/app-events-sessions.js +171 -4
  21. package/renderer/app-events.js +2 -1
  22. package/renderer/app-graph-model.js +32 -4
  23. package/renderer/app-graph-orchestration.js +16 -13
  24. package/renderer/app-graph-view.js +228 -112
  25. package/renderer/app-management.js +211 -0
  26. package/renderer/app-provider-visibility.js +124 -0
  27. package/renderer/app-quality.js +568 -0
  28. package/renderer/app-run-modal.js +103 -33
  29. package/renderer/app-runtime-overview.js +312 -0
  30. package/renderer/app-session-render.js +94 -37
  31. package/renderer/app-tmux-render.js +119 -38
  32. package/renderer/app.js +155 -76
  33. package/renderer/i18n-messages.js +833 -15
  34. package/renderer/i18n.js +104 -0
  35. package/renderer/index.html +162 -75
  36. package/renderer/shared.js +17 -0
  37. package/renderer/styles-agent-map.css +50 -1
  38. package/renderer/styles-cards.css +26 -0
  39. package/renderer/styles-collaboration.css +18 -88
  40. package/renderer/styles-components.css +126 -14
  41. package/renderer/styles-management.css +355 -0
  42. package/renderer/styles-overlays.css +13 -2
  43. package/renderer/styles-product.css +24 -16
  44. package/renderer/styles-quality.css +312 -0
  45. package/renderer/styles-readability.css +862 -0
  46. package/renderer/styles-responsive-product.css +84 -12
  47. package/renderer/styles-responsive-runtime.css +31 -14
  48. package/renderer/styles-responsive-shell.css +44 -48
  49. package/renderer/styles-responsive-workflows.css +37 -17
  50. package/renderer/styles-run-composer.css +5 -0
  51. package/renderer/styles-runtime-overview.css +285 -0
  52. package/renderer/styles-settings.css +160 -0
  53. package/renderer/styles-terminal.css +339 -2
  54. package/renderer/styles-tmux.css +154 -0
  55. package/renderer/styles-workflow-map.css +362 -15
  56. package/renderer/styles-workflows.css +69 -2
  57. package/renderer/styles.css +27 -12
  58. package/renderer/terminal-agent.js +17 -13
  59. package/renderer/terminal-events.js +233 -40
  60. package/renderer/terminal-workbench.js +176 -78
  61. package/renderer/terminal.js +350 -37
  62. package/src/agentMonitor/claudeParser.js +96 -7
  63. package/src/agentMonitor/codexParser.js +59 -4
  64. package/src/agentMonitor/executionActivity.js +282 -0
  65. package/src/agentMonitor/genericParser.js +56 -13
  66. package/src/agentMonitor/hierarchy.js +17 -0
  67. package/src/agentMonitor/responseIntent.js +51 -0
  68. package/src/agentMonitor.js +21 -3
  69. package/src/agentRunner.js +66 -1
  70. package/src/attentionNotifier.js +71 -0
  71. package/src/automationMonitor.js +258 -0
  72. package/src/contracts.js +10 -1
  73. package/src/ipc/registerAgentIpc.js +9 -3
  74. package/src/ipc/registerAppIpc.js +4 -1
  75. package/src/ipc/registerTerminalIpc.js +12 -6
  76. package/src/macUpdateHelper.js +210 -0
  77. package/src/monitorWorker.js +65 -6
  78. package/src/platformPath.js +80 -0
  79. package/src/processMonitor.js +81 -23
  80. package/src/providerVisibilityStore.js +45 -0
  81. package/src/sessionIntelligence.js +247 -0
  82. package/src/terminalHost.js +381 -0
  83. package/src/terminalHostDaemon.js +60 -0
  84. package/src/terminalManager.js +158 -3
  85. package/src/tmuxMonitor.js +2 -2
  86. package/src/updateInstaller.js +175 -0
@@ -14,6 +14,8 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
14
14
  timeOnly,
15
15
  providerInfo,
16
16
  providerStyle,
17
+ isProviderVisible = () => true,
18
+ visibleProviders = () => state.providers,
17
19
  agentRoleLabel,
18
20
  statusClass,
19
21
  currentActivity,
@@ -27,7 +29,13 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
27
29
  agentExecutionMode,
28
30
  executionModeBadge,
29
31
  graphDescendantCount,
32
+ sessionWorkspaceLabel,
30
33
  } = context;
34
+ const t = (key, params) => window.LoadToAgentI18n.t(key, params);
35
+ const statusLabel = (status) => ({
36
+ starting: t("ui.preparing"), running: t("ui.working"), waiting: t("app.nav.needs_review"), idle: t("ui.idle"),
37
+ completed: t("ui.completed"), failed: t("ui.problem"), cancelled: t("ui.stopped"),
38
+ })[status] || STATUS[status] || status;
31
39
 
32
40
  function graphNode(session, options = {}) {
33
41
  const provider = providerInfo(session.provider);
@@ -50,31 +58,34 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
50
58
  const currentWork = latestWorkCopy(session);
51
59
  const currentPreview = readablePreview(currentWork, options.focus ? 132 : 108);
52
60
  const role = session.parentId
53
- ? `도움 AI${session.agentName ? ` · ${session.agentName}` : ""}${session.agentRole ? ` / ${agentRoleLabel(session.agentRole)}` : ""}`
54
- : "일을 맡은 AI";
61
+ ? t("graph.helper_ai_identity", {
62
+ name: session.agentName ? ` · ${session.agentName}` : "",
63
+ role: session.agentRole ? ` / ${agentRoleLabel(session.agentRole)}` : "",
64
+ })
65
+ : t("graph.assigned_ai");
55
66
  return `<article class="agent-node ${running ? "running" : ""} ${session.parentId ? "child-agent" : "root-agent"} ${options.focus ? "is-focus" : ""}"
56
67
  data-motion-key="agent:${esc(session.id)}"
57
68
  data-motion-value="${esc(session.updatedAt || "")}:${usage.total || 0}:${esc(session.status || "")}"
58
69
  style="${providerStyle(session.provider)}">
59
- <button class="agent-node-main" type="button" data-graph-focus="${esc(session.id)}" aria-label="${esc(role)} 관계 중심으로 보기">
70
+ <button class="agent-node-main" type="button" data-graph-focus="${esc(session.id)}" aria-label="${esc(t("graph.focus_relationships", { role }))}">
60
71
  <span class="agent-node-top">
61
72
  <span class="provider-mark">${esc(provider.mark)}</span>
62
- <span class="agent-identity"><b>${esc(role)}</b><small>${esc(provider.label)} · ${esc(session.model || "모델 정보 없음")}</small></span>
73
+ <span class="agent-identity"><b>${esc(role)}</b><small>${esc(provider.label)} · ${esc(session.model || t("graph.model_unknown"))}</small></span>
63
74
  ${executionModeBadge(session, true)}
64
- <span class="status-pill ${statusClass(session.status)}">${esc(STATUS[session.status] || session.status)}</span>
75
+ <span class="status-pill ${statusClass(session.status)}">${esc(statusLabel(session.status))}</span>
65
76
  </span>
66
77
  <span class="agent-task-label">
67
- ${session.parentId ? `담당 작업${delegation.assignmentSource === "parent-narration" ? " · 메인 AI 설명 기반" : ""}` : "지금 목표"}
78
+ ${session.parentId ? t("graph.assigned_task", { source: delegation.assignmentSource === "parent-narration" ? t("graph.main_ai_explanation_suffix") : "" }) : t("graph.current_goal")}
68
79
  </span>
69
80
  <strong class="agent-task" title="${esc(goalPreview.full)}">${esc(goalPreview.text)}</strong>
70
- ${goalPreview.truncated ? '<span class="agent-goal-note">요약 표시 · 전체 내용은 대화 상세에서 확인</span>' : ""}
81
+ ${goalPreview.truncated ? `<span class="agent-goal-note">${esc(t("graph.summary_shown"))}</span>` : ""}
71
82
  <span class="agent-current">
72
- <span><i>${statusIcon(activity.type)}</i><b>지금 하는 일</b></span>
83
+ <span><i>${statusIcon(activity.type)}</i><b>${esc(t("graph.current_work"))}</b></span>
73
84
  <strong title="${esc(currentPreview.full)}">${esc(currentPreview.text)}</strong>
74
85
  </span>
75
86
  <span class="agent-node-metrics">
76
87
  <span>
77
- <small>기억 공간 사용</small>
88
+ <small>${esc(t("graph.memory_usage"))}</small>
78
89
  <b>${context.window ? `${percent.toFixed(1)}%` : "--"}</b>
79
90
  </span>
80
91
  <span>
@@ -82,15 +93,17 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
82
93
  <b>${compact(usage.total)}</b>
83
94
  </span>
84
95
  <span>
85
- <small>마지막 활동</small>
96
+ <small>${esc(t("graph.last_activity"))}</small>
86
97
  <b>${esc(timeAgo(session.updatedAt))}</b>
87
98
  </span>
88
99
  </span>
89
100
  <span class="agent-node-gauge"><i style="width:${percent}%"></i></span>
90
101
  </button>
91
102
  <footer class="agent-node-footer">
92
- <span>${cumulativeChildren ? `서브에이전트 ${cumulativeChildren}개 누적 생성` : session.parentId ? "도움을 맡은 AI" : "이 작업의 중심 AI"}</span>
93
- <button type="button" data-open-session="${esc(session.id)}">대화 내용 보기 <b>↗</b>
103
+ <span>${session.parentId
104
+ ? (cumulativeChildren ? t("graph.subagents_created", { count: cumulativeChildren }) : t("graph.helper_ai"))
105
+ : t("project.origin_named", { name: sessionWorkspaceLabel(session) })}</span>
106
+ <button type="button" data-open-session="${esc(session.id)}">${esc(t("graph.view_conversation"))} <b>↗</b>
94
107
  </button>
95
108
  </footer>
96
109
  </article>`;
@@ -100,7 +113,9 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
100
113
  const provider = providerInfo(session.provider);
101
114
  const usage = session.usage || {};
102
115
  const directChildren = graphChildren(session, model).length;
103
- const identity = session.parentId ? `도움 AI ${session.agentName || agentRoleLabel(session.agentRole)}` : session.workspace || "중심 작업";
116
+ const identity = session.parentId
117
+ ? t("graph.helper_ai_named", { name: session.agentName || agentRoleLabel(session.agentRole) })
118
+ : t("project.origin_named", { name: sessionWorkspaceLabel(session) });
104
119
  const delegation = session.delegation || {};
105
120
  const taskName = delegation.taskName || session.taskName || "";
106
121
  const assignedWork = delegation.assignmentObserved && delegation.assignment ? delegation.assignment : taskName || session.title;
@@ -108,16 +123,16 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
108
123
  const outcome = delegation.result || session.result || "";
109
124
  const outcomeText = outcome || latestWorkCopy(session);
110
125
  const assignedWorkPreview = readablePreview(assignedWork, session.parentId ? 110 : 104);
111
- const taskLabel = session.parentId ? `${label || agentRoleLabel(session.agentRole)}${taskName ? ` · 담당 ${taskName}` : ""}` : label;
126
+ const taskLabel = session.parentId ? `${label || agentRoleLabel(session.agentRole)}${taskName ? t("graph.assigned_name_suffix", { name: taskName }) : ""}` : label;
112
127
  const assignmentSourceNote =
113
128
  session.parentId && delegation.assignmentSource === "parent-narration"
114
- ? '<span class="agent-flow-assignment-source">메인 AI가 작업 시작 직전에 설명한 내용</span>'
129
+ ? `<span class="agent-flow-assignment-source">${esc(t("graph.main_ai_prestart_explanation"))}</span>`
115
130
  : "";
116
131
  const sharedGoalCopy =
117
- session.parentId && sharedGoal && sharedGoal !== assignedWork ? `<span class="agent-flow-shared">공유 목표 · ${esc(sharedGoal)}</span>` : "";
132
+ session.parentId && sharedGoal && sharedGoal !== assignedWork ? `<span class="agent-flow-shared">${esc(t("graph.shared_goal"))} · ${esc(sharedGoal)}</span>` : "";
118
133
  const outcomeCopy = session.parentId
119
134
  ? `<span class="agent-flow-outcome ${session.status === "completed" ? "done" : ""}">
120
- <b>${session.status === "completed" ? "완료 결과" : "현재 작업"}</b>
135
+ <b>${esc(session.status === "completed" ? t("graph.completed_result") : t("graph.current_work"))}</b>
121
136
  <span class="agent-flow-outcome-copy" title="${esc(outcomeText)}">${esc(outcomeText)}</span>
122
137
  </span>`
123
138
  : "";
@@ -125,13 +140,13 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
125
140
  const primaryTask = taskName || assignedWork || session.title;
126
141
  const assignmentCopy =
127
142
  assignedWork && assignedWork !== primaryTask
128
- ? `<span class="agent-flow-assignment"><small>담당 내용</small><strong title="${esc(assignedWork)}">${esc(assignedWork)}</strong></span>`
143
+ ? `<span class="agent-flow-assignment"><small>${esc(t("graph.assignment_details"))}</small><strong title="${esc(assignedWork)}">${esc(assignedWork)}</strong></span>`
129
144
  : "";
130
145
  const workState = subagentWorkState(session);
131
146
  const interaction = directChildren
132
- ? `data-graph-focus="${esc(session.id)}" aria-label="${esc(primaryTask)}의 하위 서브에이전트 흐름 보기"`
133
- : `data-open-subagent-chat="${esc(session.id)}" aria-label="${esc(primaryTask)}와 메인 AI의 대화 보기"`;
134
- const action = directChildren ? `하위 서브에이전트 ${directChildren}개 보기 →` : "메인 AI와의 대화 보기 →";
147
+ ? `data-graph-focus="${esc(session.id)}" aria-label="${esc(t("graph.view_child_flow", { task: primaryTask }))}"`
148
+ : `data-open-subagent-chat="${esc(session.id)}" aria-label="${esc(t("graph.view_main_ai_conversation_for_task", { task: primaryTask }))}"`;
149
+ const action = directChildren ? t("graph.view_child_subagents", { count: directChildren }) : t("graph.view_main_ai_conversation");
135
150
  return `<button type="button" class="agent-flow-row child-session work-${workState} ${statusClass(session.status)}"
136
151
  ${interaction}
137
152
  data-motion-key="agent:${esc(session.id)}"
@@ -140,13 +155,13 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
140
155
  <span class="agent-flow-state" aria-hidden="true"></span>
141
156
  <span class="agent-flow-copy">
142
157
  <span class="agent-flow-kicker">
143
- <small>${esc(label || agentRoleLabel(session.agentRole))} 세션</small>
158
+ <small>${esc(t("graph.named_session", { name: label || agentRoleLabel(session.agentRole) }))}</small>
144
159
  <time>${esc(timeAgo(session.updatedAt))}</time>
145
160
  </span>
146
161
  <b class="agent-flow-session-title" title="${esc(primaryTask)}">${esc(primaryTask)}</b>
147
162
  <span class="agent-flow-agent">
148
163
  <i>${esc(provider.mark)}</i>
149
- <strong>${esc(session.agentName || "이름 미확인")}</strong>
164
+ <strong>${esc(session.agentName || t("graph.name_unknown"))}</strong>
150
165
  <small>${esc(provider.label)}${session.model ? ` · ${esc(session.model)}` : ""}</small>
151
166
  </span>
152
167
  ${assignmentCopy}${assignmentSourceNote}${outcomeCopy}<span class="agent-flow-child-action">${esc(action)}</span>
@@ -154,7 +169,7 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
154
169
  <span class="agent-flow-provider">
155
170
  ${executionModeBadge(session, true)}
156
171
  <small class="status-pill work-${workState}">${esc(subagentWorkLabel(session))}</small>
157
- ${session.status === "completed" ? "<em>최근 작업 완료</em>" : ""}
172
+ ${session.status === "completed" ? `<em>${esc(t("graph.recent_work_completed"))}</em>` : ""}
158
173
  </span>
159
174
  </button>`;
160
175
  }
@@ -167,10 +182,10 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
167
182
  <span class="agent-flow-copy">
168
183
  ${taskLabel ? `<small>${esc(taskLabel)}</small>` : ""}
169
184
  <b title="${esc(assignedWorkPreview.full)}">${esc(assignedWorkPreview.text)}</b>
170
- <em>${esc(identity)} · ${directChildren ? `도움 AI ${directChildren} · ` : ""}${esc(timeAgo(session.updatedAt))}</em>
185
+ <em>${esc(identity)} · ${directChildren ? `${t("graph.helper_ai_count", { count: directChildren })} · ` : ""}${esc(timeAgo(session.updatedAt))}</em>
171
186
  ${assignmentSourceNote}${sharedGoalCopy}${outcomeCopy}
172
187
  </span>
173
- <span class="agent-flow-provider"><i>${esc(provider.mark)}</i><small>${esc(STATUS[session.status] || session.status)}</small></span>
188
+ <span class="agent-flow-provider"><i>${esc(provider.mark)}</i><small>${esc(statusLabel(session.status))}</small></span>
174
189
  </button>`;
175
190
  }
176
191
 
@@ -180,19 +195,30 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
180
195
  const expanded = state.graphExpandedProviders.has(providerId);
181
196
  const shown = expanded ? ordered : ordered.slice(0, 6);
182
197
  const hidden = Math.max(0, ordered.length - shown.length);
183
- const agents = ordered.reduce((total, root) => total + 1 + graphDescendantCount(root, model), 0);
198
+ const helperIds = new Set();
199
+ const queue = ordered.flatMap((root) => root.childIds || []);
200
+ while (queue.length) {
201
+ const id = queue.shift();
202
+ if (!id || helperIds.has(id)) continue;
203
+ const helper = model.byId.get(id);
204
+ if (!helper) continue;
205
+ helperIds.add(id);
206
+ queue.push(...(helper.childIds || []));
207
+ }
208
+ const helpers = [...helperIds].map((id) => model.byId.get(id)).filter(Boolean);
209
+ const activeHelpers = helpers.filter(isLiveSession).length;
184
210
  return `<section class="agent-flow-lane" style="${providerStyle(providerId)}">
185
211
  <header class="agent-flow-lane-head">
186
212
  <span class="provider-mark">${esc(provider.mark)}</span>
187
- <span><b>${esc(provider.label)}</b><small>${ordered.length}개 · 참여 AI ${agents}명</small></span>
188
- <em>${ordered.filter(isLiveSession).length}개 진행 중</em>
213
+ <span><b>${esc(provider.label)}</b><small>${esc(t("graph.major_tasks_and_agents", { tasks: ordered.length, active: activeHelpers, records: helpers.length }))}</small></span>
214
+ <em>${esc(t("graph.running_count", { count: ordered.filter(isLiveSession).length }))}</em>
189
215
  </header>
190
216
  <div class="agent-flow-list">${shown.map((root) => compactGraphNode(root, model)).join("")}</div>
191
217
  ${
192
218
  hidden
193
- ? `<button type="button" class="agent-flow-more" data-graph-provider-more="${esc(providerId)}">나머지 ${hidden}개 일도 보기</button>`
219
+ ? `<button type="button" class="agent-flow-more" data-graph-provider-more="${esc(providerId)}">${esc(t("graph.show_remaining_tasks", { count: hidden }))}</button>`
194
220
  : expanded && ordered.length > 6
195
- ? `<button type="button" class="agent-flow-more" data-graph-provider-less="${esc(providerId)}">간단히 보기</button>`
221
+ ? `<button type="button" class="agent-flow-more" data-graph-provider-less="${esc(providerId)}">${esc(t("graph.show_compact"))}</button>`
196
222
  : ""
197
223
  }
198
224
  </section>`;
@@ -209,7 +235,7 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
209
235
  for (const tmuxSession of distro.sessions || []) {
210
236
  for (const window of tmuxSession.windows || []) {
211
237
  for (const pane of window.panes || []) {
212
- if (!pane.agent) continue;
238
+ if (!pane.agent || pane.dead || !isProviderVisible(pane.agent.provider)) continue;
213
239
  entries.push({ distro, tmuxSession, window, pane, agent: pane.agent });
214
240
  }
215
241
  }
@@ -227,15 +253,15 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
227
253
  const { distro, tmuxSession, window, pane, agent } = entry;
228
254
  const provider = providerInfo(agent.provider);
229
255
  const linked = agent.linkedSessionId ? (state.snapshot?.sessions || []).find((session) => session.id === agent.linkedSessionId) || null : null;
230
- const title = linked ? linked.title : pane.title || `${provider.label} TMUX 작업`;
231
- const stateLabel = pane.dead ? "종료됨" : pane.active ? "현재 선택된 칸" : "백그라운드 실행";
256
+ const title = linked ? linked.title : pane.title || t("graph.tmux_task", { provider: provider.label });
257
+ const stateLabel = pane.dead ? t("graph.ended") : pane.active ? t("graph.selected_pane") : t("graph.background_running");
232
258
  return `<article class="live-tmux-card ${pane.active ? "active" : ""} ${pane.dead ? "dead" : ""}"
233
259
  style="${providerStyle(agent.provider)}"
234
260
  data-motion-key="live-tmux:${esc(pane.id)}"
235
261
  data-motion-value="${esc(agent.updatedAt || "")}:${pane.pid || 0}">
236
- <button type="button" class="live-tmux-pane" data-tmux-type="pane" data-tmux-id="${esc(pane.id)}" aria-label="${esc(tmuxSession.name)} TMUX 칸 열기">
262
+ <button type="button" class="live-tmux-pane" data-tmux-type="pane" data-tmux-id="${esc(pane.id)}" aria-label="${esc(t("graph.open_tmux_pane", { session: tmuxSession.name }))}">
237
263
  <span class="live-tmux-card-head">
238
- <span class="live-tmux-symbol">▦</span><span><small>TMUX 세션</small><b>${esc(tmuxSession.name)}</b></span>
264
+ <span class="live-tmux-symbol">▦</span><span><small>${esc(t("graph.tmux_session"))}</small><b>${esc(tmuxSession.name)}</b></span>
239
265
  <em>${esc(stateLabel)}</em>
240
266
  </span>
241
267
  <strong class="live-tmux-title">${esc(title)}</strong>
@@ -246,63 +272,68 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
246
272
  <span class="live-tmux-location">
247
273
  <b>${esc(distro.name)}</b>
248
274
  <i>›</i>
249
- <span>${esc(window.name || `창 ${window.index + 1}`)}</span>
275
+ <span>${esc(window.name || t("graph.window_number", { number: window.index + 1 }))}</span>
250
276
  <i>›</i>
251
- <span>칸 ${pane.index + 1} · ${esc(pane.nativeId || pane.id)}</span>
277
+ <span>${esc(t("graph.pane_number", { number: pane.index + 1 }))} · ${esc(pane.nativeId || pane.id)}</span>
252
278
  </span>
253
- <span class="live-tmux-cwd" title="${esc(pane.cwd || "")}">${esc(pane.cwd || "작업 폴더 미확인")}</span>
279
+ <span class="live-tmux-cwd" title="${esc(pane.cwd || "")}">${esc(pane.cwd || t("graph.workspace_unknown"))}</span>
254
280
  </button>
255
281
  <footer>
256
- <span>${linked ? "대화 기록과 연결됨" : "TMUX 프로세스에서 직접 감지"}</span>
282
+ <span>${esc(linked ? t("graph.linked_to_conversation") : t("graph.detected_from_tmux"))}</span>
257
283
  <span>
258
- ${linked ? `<button type="button" data-graph-focus="${esc(linked.id)}">AI 흐름 보기</button>` : ""}
259
- <button type="button" class="live-tmux-pane" data-tmux-type="pane" data-tmux-id="${esc(pane.id)}">TMUX에서 열기 →</button>
284
+ ${linked ? `<button type="button" data-graph-focus="${esc(linked.id)}">${esc(t("graph.view_ai_flow"))}</button>` : ""}
285
+ <button type="button" class="live-tmux-pane" data-tmux-type="pane" data-tmux-id="${esc(pane.id)}">${esc(t("graph.open_in_tmux"))}</button>
260
286
  </span>
261
287
  </footer>
262
288
  </article>`;
263
289
  }
264
290
 
265
291
  function runtimeSeparatedOverview(roots, model) {
266
- const tmux = (state.snapshot && state.snapshot.tmux) || { distros: [], summary: {} };
267
- const tmuxEntries = liveTmuxEntries(tmux);
268
- const tmuxLinkedIds = new Set(tmuxEntries.map((entry) => entry.agent.linkedSessionId).filter(Boolean));
269
- const tmuxRoots = roots.filter((root) => agentExecutionMode(root).kind === "tmux");
270
- const standardRoots = roots.filter((root) => agentExecutionMode(root).kind !== "tmux");
271
- const providerOrder = [...new Set([...state.providers.map((item) => item.id), ...roots.map((item) => item.provider)])];
292
+ const tmuxEntries = liveTmuxEntries(state.snapshot && state.snapshot.tmux);
293
+ const tmuxLinkedIds = new Set(tmuxEntries.map((entry) => String(entry.agent && entry.agent.linkedSessionId || "")).filter(Boolean));
294
+ const workflowUsesTmux = root => {
295
+ const queue = [...(root.childIds || [])];
296
+ const visited = new Set();
297
+ while (queue.length) {
298
+ const id = queue.shift();
299
+ if (!id || visited.has(id)) continue;
300
+ visited.add(id);
301
+ const child = model.byId.get(id);
302
+ if (!child) continue;
303
+ if (agentExecutionMode(child).kind === "tmux" || tmuxLinkedIds.has(child.id)) return true;
304
+ queue.push(...(child.childIds || []));
305
+ }
306
+ return false;
307
+ };
308
+ const tmuxRoots = roots.filter((root) => agentExecutionMode(root).kind === "tmux" || tmuxLinkedIds.has(root.id) || workflowUsesTmux(root));
309
+ const tmuxRootIds = new Set(tmuxRoots.map((root) => root.id));
310
+ const standardRoots = roots.filter((root) => !tmuxRootIds.has(root.id));
311
+ const providerOrder = [...new Set([...visibleProviders().map((item) => item.id), ...roots.map((item) => item.provider).filter(isProviderVisible)])];
272
312
  const lanesFor = (items) =>
273
313
  providerOrder.map((providerId) => ({ providerId, roots: items.filter((root) => root.provider === providerId) })).filter((item) => item.roots.length);
274
314
  const standardLanes = lanesFor(standardRoots);
275
- const fallbackTmuxLanes = lanesFor(tmuxRoots.filter((root) => !tmuxLinkedIds.has(root.id)));
276
- const summary = tmux.summary || {};
315
+ const tmuxLanes = lanesFor(tmuxRoots);
277
316
  const standardHtml = standardLanes.length
278
317
  ? `<div class="agent-flow-overview">${standardLanes.map((item) => providerFlowLane(item.providerId, item.roots, model)).join("")}</div>`
279
- : '<div class="runtime-segment-empty"><b>일반 실행 AI가 없습니다</b><span>현재 감지된 작업은 모두 TMUX에서 실행 중입니다.</span></div>';
280
- const tmuxHtml =
281
- tmuxEntries.length || fallbackTmuxLanes.length
282
- ? `${tmuxEntries.length ? `<div class="live-tmux-grid">${tmuxEntries.map(liveTmuxPaneCard).join("")}</div>` : ""}
283
- ${
284
- fallbackTmuxLanes.length
285
- ? `<div class="agent-flow-overview live-tmux-fallback">
286
- ${fallbackTmuxLanes.map((item) => providerFlowLane(item.providerId, item.roots, model)).join("")}
287
- </div>`
288
- : ""
289
- }`
290
- : '<div class="runtime-segment-empty tmux"><b>TMUX에서 실행 중인 AI가 없습니다</b><span>TMUX AI 프로세스가 감지되면 일반 실행과 분리해 여기에 표시합니다.</span></div>';
291
- return `<div class="agent-runtime-split" data-runtime-split="true">
292
- <section class="runtime-segment tmux-runtime" data-runtime-segment="tmux">
318
+ : `<div class="runtime-segment-empty"><b>${esc(t("graph.no_standard_ai"))}</b><span>${esc(t("graph.all_detected_in_tmux"))}</span></div>`;
319
+ const tmuxSection = tmuxLanes.length
320
+ ? `<section class="runtime-segment tmux-runtime" data-runtime-segment="tmux">
293
321
  <header>
294
322
  <span class="runtime-segment-icon">▦</span>
295
- <span><small>TMUX 전용</small><b>TMUX 세션</b><em>Linux 작업 묶음·창·분할 칸을 유지해서 실행 중인 AI</em></span>
296
- <strong>${tmuxEntries.length || summary.aiPanes || 0}개</strong>
297
- <button type="button" class="live-tmux-overview-open">TMUX 전체 화면 →</button>
323
+ <span><small>${esc(t("graph.tmux_only"))}</small><b>${esc(t("graph.tmux_sessions"))}</b><em>${esc(t("graph.tmux_runtime_description"))}</em></span>
324
+ <strong>${esc(t("common.count", { count: tmuxRoots.length }))}</strong>
325
+ <button type="button" class="live-tmux-overview-open">${esc(t("graph.open_tmux_overview"))}</button>
298
326
  </header>
299
- ${tmuxHtml}
300
- </section>
327
+ <div class="agent-flow-overview">${tmuxLanes.map((item) => providerFlowLane(item.providerId, item.roots, model)).join("")}</div>
328
+ </section>`
329
+ : "";
330
+ return `<div class="agent-runtime-split" data-runtime-split="true">
331
+ ${tmuxSection}
301
332
  <section class="runtime-segment standard-runtime" data-runtime-segment="standard">
302
333
  <header>
303
334
  <span class="runtime-segment-icon">›_</span>
304
- <span><small>TMUX 미사용</small><b>일반 실행 세션</b><em>Codex 앱·외부 터미널에서 실행 중인 메인 AI</em></span>
305
- <strong>${standardRoots.length}개</strong>
335
+ <span><small>${esc(t("graph.without_tmux"))}</small><b>${esc(t("graph.standard_sessions"))}</b><em>${esc(t("graph.standard_runtime_description"))}</em></span>
336
+ <strong>${esc(t("common.count", { count: standardRoots.length }))}</strong>
306
337
  </header>
307
338
  ${standardHtml}
308
339
  </section>
@@ -340,24 +371,24 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
340
371
  .join("");
341
372
  const metrics = workflowMetrics(session, children);
342
373
  const capacity = metrics.simultaneousCapacity > 0 ? metrics.simultaneousCapacity : "--";
343
- const retained = metrics.retainedCount == null ? "현재 목록 유지 수는 관측되지 않음" : `현재 런타임 목록에는 ${metrics.retainedCount}개 유지`;
344
- const source = metrics.capacitySource === "runtime-instruction" ? "세션 런타임 한도" : "동시 한도 출처 미확인";
374
+ const retained = metrics.retainedCount == null ? t("graph.retained_count_unobserved") : t("graph.retained_count", { count: metrics.retainedCount });
375
+ const source = metrics.capacitySource === "runtime-instruction" ? t("graph.session_runtime_limit") : t("graph.capacity_source_unknown");
345
376
  return `<div class="agent-workflow-summary" data-collaboration-summary="true">
346
377
  <div class="workflow-metric-grid">
347
378
  <span data-collaboration-metric="created">
348
- <small>이 작업에서 누적 생성</small><b>${esc(metrics.cumulativeCreated)}</b><em>${window.LoadToAgentI18n.t("ui.items")}</em>
379
+ <small>${esc(t("graph.created_in_task"))}</small><b>${esc(metrics.cumulativeCreated)}</b><em>${window.LoadToAgentI18n.t("ui.items")}</em>
349
380
  </span>
350
381
  <span data-collaboration-metric="capacity">
351
- <small>동시에 유지 가능</small><b>${esc(capacity)}</b><em>${capacity === "--" ? "" : window.LoadToAgentI18n.t("ui.items")}</em>
382
+ <small>${esc(t("graph.simultaneous_capacity"))}</small><b>${esc(capacity)}</b><em>${capacity === "--" ? "" : window.LoadToAgentI18n.t("ui.items")}</em>
352
383
  </span>
353
384
  <span data-collaboration-metric="running">
354
- <small>현재 실행 중</small><b>${esc(metrics.currentlyRunning)}</b><em>${window.LoadToAgentI18n.t("ui.items")}</em>
385
+ <small>${esc(t("graph.currently_running"))}</small><b>${esc(metrics.currentlyRunning)}</b><em>${window.LoadToAgentI18n.t("ui.items")}</em>
355
386
  </span>
356
387
  <span data-collaboration-metric="completed">
357
- <small>작업 완료 기록</small><b>${esc(metrics.completedRecords)}</b><em>${window.LoadToAgentI18n.t("ui.items")}</em>
388
+ <small>${esc(t("graph.completed_records"))}</small><b>${esc(metrics.completedRecords)}</b><em>${window.LoadToAgentI18n.t("ui.items")}</em>
358
389
  </span>
359
390
  </div>
360
- <div class="workflow-summary-evidence"><span>${esc(retained)} · 완료 기록은 삭제하지 않고 기본으로 접어 보관</span><small>${esc(source)} · spawn/완료 이벤트 기준</small></div>
391
+ <div class="workflow-summary-evidence"><span>${esc(retained)} · ${esc(t("graph.completed_records_collapsed"))}</span><small>${esc(source)} · ${esc(t("graph.event_basis"))}</small></div>
361
392
  <div class="workflow-summary-providers">${providers}</div>
362
393
  </div>`;
363
394
  }
@@ -378,10 +409,10 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
378
409
  return `<div class="completed-subagent-disclosure ${expanded ? "expanded" : ""}" data-completed-subagent-section>
379
410
  <button type="button" data-subagent-completed-toggle="${esc(ownerId)}" aria-expanded="${expanded ? "true" : "false"}">
380
411
  <span class="completed-disclosure-icon">✓</span>
381
- <span><b>완료된 서브에이전트 ${completed.length}개</b>
382
- <small>${expanded ? "완료 기록을 펼쳐 보는 중" : "작업 중인 AI에 집중할 수 있도록 기본으로 접어둡니다"}</small>
412
+ <span><b>${esc(t("graph.completed_subagents", { count: completed.length }))}</b>
413
+ <small>${esc(expanded ? t("graph.completed_expanded") : t("graph.completed_collapsed_hint"))}</small>
383
414
  </span>
384
- <i>${expanded ? "접기 ↑" : "펼쳐 보기 ↓"}</i>
415
+ <i>${esc(expanded ? t("graph.collapse") : t("graph.expand"))}</i>
385
416
  </button>
386
417
  </div>`;
387
418
  }
@@ -399,12 +430,12 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
399
430
 
400
431
  function communicationEndpoint(value, owner, model) {
401
432
  const path = String(value || "");
402
- if (!path) return "대상 미상";
403
- if (path === "Codex 런타임") return path;
404
- if (path === "/root" || path === owner.agentPath || (!owner.agentPath && path === owner.id)) return "메인 AI";
433
+ if (!path) return t("graph.target_unknown");
434
+ if (path === "Codex 런타임") return t("graph.codex_runtime");
435
+ if (path === "/root" || path === owner.agentPath || (!owner.agentPath && path === owner.id)) return t("graph.main_ai");
405
436
  const taskName = agentPathTaskName(path);
406
437
  const session = model.nodes.find((node) => node.agentPath === path || node.taskName === taskName);
407
- if (session) return `${session.agentName || "서브 AI"}${taskName ? ` · ${taskName}` : ""}`;
438
+ if (session) return `${session.agentName || t("graph.sub_ai")}${taskName ? ` · ${taskName}` : ""}`;
408
439
  return taskName || path;
409
440
  }
410
441
 
@@ -417,11 +448,11 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
417
448
  return `<section class="agent-communication-panel empty" data-collaboration-communications="0">
418
449
  <header>
419
450
  <span>
420
- <b>메인 AI ↔ 서브에이전트 소통</b>
421
- <small>배정·추가 지시·결과 반환 기록</small>
451
+ <b>${esc(t("graph.communication_title"))}</b>
452
+ <small>${esc(t("graph.communication_short_description"))}</small>
422
453
  </span>
423
454
  </header>
424
- <p>이 세션 로그에서는 에이전트 간 통신 이벤트가 확인되지 않았습니다.</p>
455
+ <p>${esc(t("graph.no_communication_events"))}</p>
425
456
  </section>`;
426
457
  }
427
458
  const rows = events
@@ -429,32 +460,34 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
429
460
  const text =
430
461
  event.text ||
431
462
  (event.protected
432
- ? `${event.taskName || " 작업"}을 서브에이전트에게 배정했습니다.`
463
+ ? t("graph.assigned_to_subagent", { task: event.taskName || t("graph.this_task") })
433
464
  : event.kind === "started"
434
- ? "런타임에서 실행 시작을 확인했습니다."
435
- : "내용 없이 상태만 기록되었습니다.");
436
- const sourceLabel = event.assignmentSource === "parent-narration" ? " · 작업 시작 직전 메인 AI 설명" : "";
465
+ ? t("graph.runtime_start_confirmed")
466
+ : t("graph.status_only_recorded"));
467
+ const sourceLabel = event.assignmentSource === "parent-narration" ? ` · ${t("graph.main_ai_prestart_short")}` : "";
437
468
  return `<article class="agent-communication-event ${esc(event.kind)}" data-communication-kind="${esc(event.kind)}">
438
469
  <span class="communication-route">
439
470
  <b>${esc(communicationEndpoint(event.from, owner, model))}</b><i>→</i>
440
471
  <b>${esc(communicationEndpoint(event.to, owner, model))}</b>
441
472
  </span>
442
473
  <span class="communication-copy">
443
- <small>${esc(event.label)}${event.taskName ? ` · ${esc(event.taskName)}` : ""}${sourceLabel}</small>
474
+ <small>${esc(window.LoadToAgentI18n.observedText(event.label))}${event.taskName ? ` · ${esc(event.taskName)}` : ""}${sourceLabel}</small>
444
475
  <strong>${esc(text)}</strong>
445
476
  </span>
446
477
  <time>${esc(timeOnly(event.timestamp))}</time>
447
478
  </article>`;
448
479
  })
449
480
  .join("");
450
- const countLabel = relevant.length > events.length ? `최근 ${events.length} / 전체 ${relevant.length}건` : `${events.length}건`;
481
+ const countLabel = relevant.length > events.length
482
+ ? t("graph.recent_of_total_events", { recent: events.length, total: relevant.length })
483
+ : t("common.events", { count: events.length });
451
484
  return `<section class="agent-communication-panel"
452
485
  data-collaboration-communications="${events.length}"
453
486
  data-collaboration-communications-total="${relevant.length}">
454
487
  <header>
455
488
  <span>
456
- <b>메인 AI ↔ 서브에이전트 소통</b>
457
- <small>누가 일을 맡겼고, 언제 시작했으며, 어떤 결과를 돌려줬는지 시간순으로 표시</small>
489
+ <b>${esc(t("graph.communication_title"))}</b>
490
+ <small>${esc(t("graph.communication_description"))}</small>
458
491
  </span>
459
492
  <em>${countLabel}</em>
460
493
  </header>
@@ -462,20 +495,99 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
462
495
  </section>`;
463
496
  }
464
497
 
498
+ function executionActivityLabel(activity) {
499
+ if (activity.kind === "background") return t("graph.background_task");
500
+ return activity.mode === "background" ? t("graph.shell_background") : t("graph.shell_foreground");
501
+ }
502
+
503
+ function executionActivityStatus(activity) {
504
+ return ({
505
+ running: t("graph.execution_running"),
506
+ completed: t("graph.execution_completed"),
507
+ failed: t("graph.execution_failed"),
508
+ cancelled: t("ui.stopped"),
509
+ })[activity.status] || activity.status;
510
+ }
511
+
512
+ function executionActivityCard(activity, ownerId = "") {
513
+ const label = executionActivityLabel(activity);
514
+ const command = readablePreview(activity.command || activity.label || label, 150);
515
+ const handle = activity.backgroundId
516
+ ? `${activity.backgroundIdType || t("graph.execution_handle")} · ${activity.backgroundId}`
517
+ : "";
518
+ const runtime = activity.runtime || activity.tool || t("graph.runtime_unknown");
519
+ return `<details class="execution-activity-card ${esc(activity.kind || "background")} ${esc(activity.mode || "foreground")} ${esc(activity.status || "completed")}"
520
+ data-disclosure-key="${esc(`graph:execution:${ownerId}:${activity.id}`)}"
521
+ data-execution-activity="${esc(activity.id)}"
522
+ data-execution-kind="${esc(activity.kind || "")}" data-execution-mode="${esc(activity.mode || "")}" data-execution-status="${esc(activity.status || "")}">
523
+ <summary>
524
+ <span class="execution-activity-icon" aria-hidden="true">${activity.kind === "shell" ? "›_" : "◌"}</span>
525
+ <span class="execution-activity-copy">
526
+ <span class="execution-activity-kicker"><b>${esc(label)}</b><small>${esc(runtime)}</small></span>
527
+ <strong title="${esc(command.full)}">${esc(activity.label || command.text)}</strong>
528
+ ${activity.command ? `<code title="${esc(activity.command)}">${esc(command.text)}</code>` : ""}
529
+ <span class="execution-activity-meta">
530
+ ${activity.cwd ? `<small title="${esc(activity.cwd)}">${esc(t("graph.execution_workdir"))} · ${esc(activity.cwd)}</small>` : ""}
531
+ ${handle ? `<small>${esc(handle)}</small>` : ""}
532
+ </span>
533
+ </span>
534
+ <span class="execution-activity-state"><span><i aria-hidden="true"></i><b>${esc(executionActivityStatus(activity))}</b></span><small>${esc(activity.statusDetail || timeAgo(activity.updatedAt || activity.startedAt))}</small><span class="execution-activity-disclosure"><b class="open-label">${esc(t("graph.execution_details"))}</b><b class="close-label">${esc(t("graph.execution_details_close"))}</b><i aria-hidden="true">⌄</i></span></span>
535
+ </summary>
536
+ <div class="execution-activity-detail">
537
+ <div class="execution-detail-command"><header><span>${esc(t("graph.execution_command"))}</span><button type="button" data-copy-text="${esc(activity.command || activity.label || command.full)}">${esc(t("graph.copy_command"))}</button></header><code>${esc(activity.command || activity.label || command.full)}</code></div>
538
+ <dl>
539
+ <div><dt>${esc(t("graph.execution_status_label"))}</dt><dd>${esc(executionActivityStatus(activity))}${activity.statusDetail ? ` · ${esc(activity.statusDetail)}` : ""}</dd></div>
540
+ <div><dt>${esc(t("graph.execution_runtime"))}</dt><dd>${esc(runtime)}</dd></div>
541
+ ${activity.cwd ? `<div><dt>${esc(t("graph.execution_workdir"))}</dt><dd title="${esc(activity.cwd)}">${esc(activity.cwd)}</dd></div>` : ""}
542
+ ${handle ? `<div><dt>${esc(t("graph.execution_handle"))}</dt><dd>${esc(handle)}</dd></div>` : ""}
543
+ ${activity.startedAt ? `<div><dt>${esc(t("graph.execution_started"))}</dt><dd title="${esc(activity.startedAt)}">${esc(timeAgo(activity.startedAt))}</dd></div>` : ""}
544
+ ${activity.updatedAt ? `<div><dt>${esc(t("graph.execution_updated"))}</dt><dd title="${esc(activity.updatedAt)}">${esc(timeAgo(activity.updatedAt))}</dd></div>` : ""}
545
+ </dl>
546
+ <div class="execution-detail-output"><header><span>${esc(t("graph.execution_output"))}</span>${activity.output ? `<button type="button" data-copy-text="${esc(activity.output)}">${esc(t("graph.copy_output"))}</button>` : ""}</header>${activity.output ? `<pre>${esc(activity.output)}</pre>` : `<p>${esc(t("graph.execution_output_unavailable"))}</p>`}</div>
547
+ </div>
548
+ </details>`;
549
+ }
550
+
551
+ function executionActivityPanel(session) {
552
+ const activities = [...(session.executions || [])].sort((a, b) => {
553
+ const activeA = a.status === "running" ? 1 : 0;
554
+ const activeB = b.status === "running" ? 1 : 0;
555
+ return activeB - activeA || Date.parse(b.updatedAt || b.startedAt || 0) - Date.parse(a.updatedAt || a.startedAt || 0);
556
+ });
557
+ if (!activities.length) return "";
558
+ const running = activities.filter((activity) => activity.status === "running");
559
+ const completed = activities.filter((activity) => activity.status !== "running");
560
+ const expanded = state.expandedExecutionSessions.has(session.id);
561
+ const shown = expanded ? activities : [...running, ...completed.slice(0, Math.max(0, 6 - running.length))];
562
+ return `<section class="execution-activity-panel" data-execution-activities="${activities.length}" data-running-executions="${running.length}">
563
+ <header>
564
+ <span><b>${esc(t("graph.execution_activity"))}</b><small>${esc(t("graph.execution_activity_description"))}</small></span>
565
+ <em>${esc(t("graph.execution_activity_summary", { running: running.length, total: activities.length }))}</em>
566
+ </header>
567
+ <div class="execution-activity-list">${shown.map(activity => executionActivityCard(activity, session.id)).join("")}</div>
568
+ ${shown.length < activities.length
569
+ ? `<button type="button" class="execution-activity-retained" data-execution-history-toggle="${esc(session.id)}" aria-expanded="false">${esc(t("graph.execution_show_older", { count: activities.length - shown.length }))}</button>`
570
+ : expanded && activities.length > 6
571
+ ? `<button type="button" class="execution-activity-retained" data-execution-history-toggle="${esc(session.id)}" aria-expanded="true">${esc(t("graph.execution_collapse_older"))}</button>`
572
+ : ""}
573
+ </section>`;
574
+ }
575
+
465
576
  function focusedGraph(focus, model, motionKind = "refresh") {
466
577
  const parent = focus.parentId ? model.byId.get(focus.parentId) : null;
467
578
  const children = graphChildren(focus, model);
579
+ const executionCount = (focus.executions || []).length;
468
580
  const { ongoing, completed } = splitSubagents(children);
469
581
  const completedExpanded = state.expandedCompletedSubagents.has(focus.id);
470
582
  const shownChildren = completedExpanded ? [...ongoing, ...completed] : ongoing;
471
583
  const metrics = workflowMetrics(focus, children);
472
584
  const upstream = parent
473
- ? workflowCompactNode(parent, model, "upstream", parent.parentId ? "이전 AI로 돌아가기" : "메인 AI로 돌아가기")
585
+ ? workflowCompactNode(parent, model, "upstream", parent.parentId ? t("graph.back_to_previous_ai") : t("graph.back_to_main_ai"))
474
586
  : `<div class="agent-workflow-origin">
475
587
  <span class="workflow-origin-icon">◎</span>
476
588
  <span>
477
- <b>사용자 요청</b>
478
- <small>이 작업이 시작된 곳</small>
589
+ <b>${esc(t("graph.user_request"))}</b>
590
+ <small>${esc(t("graph.task_origin"))}</small>
479
591
  </span>
480
592
  <span class="agent-workflow-port output" data-workflow-port="upstream-output" aria-hidden="true">
481
593
  </span>
@@ -483,8 +595,10 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
483
595
  const ongoingRows = ongoing.length
484
596
  ? ongoing.map((child) => workflowCompactNode(child, model, "downstream", agentRoleLabel(child.agentRole))).join("")
485
597
  : children.length
486
- ? '<div class="agent-workflow-empty current-clear"><b>현재 작업 중인 서브에이전트가 없습니다</b><span>완료된 기록은 아래에서 필요할 때만 펼쳐볼 수 있습니다.</span></div>'
487
- : '<div class="agent-workflow-empty">아직 다른 AI에게 나눠 맡긴 일이 없습니다.</div>';
598
+ ? `<div class="agent-workflow-empty current-clear"><b>${esc(t("graph.no_active_subagents"))}</b><span>${esc(t("graph.completed_available_below"))}</span></div>`
599
+ : executionCount
600
+ ? ""
601
+ : `<div class="agent-workflow-empty">${esc(t("graph.no_delegated_tasks"))}</div>`;
488
602
  const completedRows = completedExpanded
489
603
  ? `<div class="completed-subagent-list" data-completed-subagent-list>
490
604
  ${completed.map((child) => workflowCompactNode(child, model, "downstream", agentRoleLabel(child.agentRole))).join("")}
@@ -492,13 +606,13 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
492
606
  : "";
493
607
  const downstream = `${ongoingRows}${completedSubagentDisclosure(focus.id, completed, completedExpanded)}${completedRows}`;
494
608
  const connectMotion = ["focus", "focus-back", "view"].includes(motionKind) ? "motion-connect" : "";
495
- const childGroupPort = shownChildren.length
609
+ const childGroupPort = shownChildren.length || executionCount
496
610
  ? '<span class="agent-workflow-port input group-input" data-workflow-port="children-group-input" aria-hidden="true"></span>'
497
611
  : "";
498
612
  return `<div class="agent-workflow-canvas ${connectMotion}" data-workflow-focus="${esc(focus.id)}">
499
- <svg class="agent-workflow-edges" role="img" aria-label="일을 맡긴 AI에서 선택한 AI를 거쳐 도움 AI로 이어지는 연결">
500
- <title>AI 작업 연결</title>
501
- <desc>왼쪽은 일을 맡긴 곳, 가운데는 선택한 AI, 오른쪽은 나눠 맡긴 AI입니다.</desc>
613
+ <svg class="agent-workflow-edges" role="img" aria-label="${esc(t("graph.workflow_aria"))}">
614
+ <title>${esc(t("graph.workflow_title"))}</title>
615
+ <desc>${esc(t("graph.workflow_description"))}</desc>
502
616
  <defs><marker id="workflowArrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto" markerUnits="strokeWidth">
503
617
  <path d="M0,0 L8,4 L0,8 Z" fill="context-stroke"></path>
504
618
  </marker></defs>
@@ -506,28 +620,30 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
506
620
  </svg>
507
621
  <div class="agent-workflow-grid">
508
622
  <section class="agent-workflow-column upstream-column">
509
- <header><b>${parent ? "이 일을 맡긴 AI" : "작업 시작점"}</b>
510
- <span>${parent ? "왼쪽을 눌러 이전으로 돌아가요" : "사용자가 처음 맡긴 일"}</span>
623
+ <header><b>${esc(parent ? t("graph.assigning_ai") : t("graph.starting_point"))}</b>
624
+ <span>${esc(parent ? t("graph.click_left_to_go_back") : t("graph.initial_user_task"))}</span>
511
625
  </header>
512
626
  <div class="agent-workflow-stack">${upstream}</div>
513
627
  </section>
514
628
  <section class="agent-workflow-column selected-column">
515
- <header><b>지금 선택한 AI</b><span>${focus.parentId ? "도움을 나눠 맡은 AI" : "전체 일을 맡은 메인 AI"}</span></header>
629
+ <header><b>${esc(t("graph.selected_ai"))}</b><span>${esc(focus.parentId ? t("graph.delegated_helper_ai") : t("graph.main_ai_in_charge"))}</span></header>
516
630
  <div class="agent-workflow-selected-stack">
517
631
  <div class="agent-workflow-selected">
518
632
  <span class="agent-workflow-port input" data-workflow-port="focus-input" aria-hidden="true"></span>
519
633
  ${graphNode(focus, { focus: true })}
520
634
  </div>
521
635
  ${context.agentCommandComposer(focus)}
522
- ${shownChildren.length ? '<span class="agent-workflow-port output" data-workflow-port="focus-output" aria-hidden="true"></span>' : ""}
636
+ ${shownChildren.length || executionCount ? '<span class="agent-workflow-port output" data-workflow-port="focus-output" aria-hidden="true"></span>' : ""}
523
637
  </div>
524
638
  </section>
525
639
  <section class="agent-workflow-column downstream-column"
526
640
  data-workflow-child-count="${children.length}" data-workflow-visible-child-count="${shownChildren.length}">
527
641
  ${childGroupPort}
528
- <header><b>서브에이전트 세션</b>
529
- <span>진행·대기 ${ongoing.length}개 바로 표시 · 완료 ${completed.length}개 기본 숨김</span>
642
+ <header><b>${esc(t("graph.observed_execution_units"))}</b>
643
+ <span>${esc(t("graph.execution_unit_summary", { subagents: children.length, executions: executionCount }))}</span>
530
644
  </header>
645
+ ${executionActivityPanel(focus)}
646
+ ${children.length ? `<div class="subagent-execution-heading"><b>${esc(t("graph.subagent_sessions"))}</b><span>${esc(t("graph.subagent_visibility_summary", { ongoing: ongoing.length, completed: completed.length }))}</span></div>` : ""}
531
647
  ${workflowChildrenSummary(focus, children)}
532
648
  <div class="agent-workflow-stack downstream-stack ${shownChildren.length > 3 ? "density-many" : ""}">${downstream}</div>
533
649
  </section>
@@ -539,6 +655,6 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
539
655
  return {
540
656
  graphNode, compactGraphNode, providerFlowLane, workflowCompactNode, liveTmuxEntries, liveTmuxPaneCard, runtimeSeparatedOverview,
541
657
  workflowMetrics, workflowChildrenSummary, splitSubagents, completedSubagentDisclosure, agentPathTaskName, communicationEndpoint,
542
- workflowCommunicationPanel, focusedGraph,
658
+ workflowCommunicationPanel, executionActivityLabel, executionActivityStatus, executionActivityCard, executionActivityPanel, focusedGraph,
543
659
  };
544
660
  };