loadtoagent 1.1.0 → 1.3.2

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 +14 -5
  2. package/README.md +7 -0
  3. package/README.zh-CN.md +7 -0
  4. package/docs/PROVIDER-CONTRACTS.md +25 -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 +157 -38
  9. package/package.json +13 -4
  10. package/preload.js +13 -2
  11. package/renderer/app-agent-actions.js +125 -53
  12. package/renderer/app-bootstrap.js +54 -19
  13. package/renderer/app-dashboard.js +196 -55
  14. package/renderer/app-drawer-content.js +48 -44
  15. package/renderer/app-drawer-data.js +26 -13
  16. package/renderer/app-drawer.js +72 -54
  17. package/renderer/app-events-dialogs.js +146 -37
  18. package/renderer/app-events-filters.js +172 -13
  19. package/renderer/app-events-navigation.js +77 -30
  20. package/renderer/app-events-sessions.js +156 -9
  21. package/renderer/app-events.js +2 -1
  22. package/renderer/app-graph-model.js +20 -38
  23. package/renderer/app-graph-orchestration.js +15 -13
  24. package/renderer/app-graph-view.js +228 -113
  25. package/renderer/app-management.js +257 -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 +104 -34
  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 +52 -44
  32. package/renderer/app.js +153 -75
  33. package/renderer/i18n-messages.js +867 -37
  34. package/renderer/i18n.js +104 -0
  35. package/renderer/index.html +154 -67
  36. package/renderer/shared.js +17 -0
  37. package/renderer/styles-agent-map.css +16 -10
  38. package/renderer/styles-cards.css +44 -18
  39. package/renderer/styles-collaboration.css +23 -23
  40. package/renderer/styles-components.css +122 -45
  41. package/renderer/styles-management.css +364 -0
  42. package/renderer/styles-onboarding.css +8 -8
  43. package/renderer/styles-overlays.css +13 -7
  44. package/renderer/styles-product.css +48 -40
  45. package/renderer/styles-quality.css +312 -0
  46. package/renderer/styles-readability.css +1040 -0
  47. package/renderer/styles-responsive-product.css +84 -12
  48. package/renderer/styles-responsive-runtime.css +25 -17
  49. package/renderer/styles-responsive-shell.css +44 -48
  50. package/renderer/styles-responsive-workflows.css +39 -5
  51. package/renderer/styles-run-composer.css +28 -23
  52. package/renderer/styles-runtime-overview.css +285 -0
  53. package/renderer/styles-settings.css +176 -16
  54. package/renderer/styles-terminal.css +374 -37
  55. package/renderer/styles-tmux.css +49 -49
  56. package/renderer/styles-workflow-map.css +362 -15
  57. package/renderer/styles-workflows.css +116 -49
  58. package/renderer/styles.css +34 -19
  59. package/renderer/terminal-agent.js +17 -13
  60. package/renderer/terminal-events.js +253 -37
  61. package/renderer/terminal-workbench.js +208 -89
  62. package/renderer/terminal.js +350 -35
  63. package/src/agentMonitor/claudeParser.js +96 -7
  64. package/src/agentMonitor/codexParser.js +59 -4
  65. package/src/agentMonitor/executionActivity.js +282 -0
  66. package/src/agentMonitor/genericParser.js +56 -13
  67. package/src/agentMonitor/hierarchy.js +17 -0
  68. package/src/agentMonitor/responseIntent.js +51 -0
  69. package/src/agentMonitor.js +21 -3
  70. package/src/agentRunner.js +66 -1
  71. package/src/attentionNotifier.js +14 -10
  72. package/src/automationMonitor.js +258 -0
  73. package/src/contracts.js +10 -1
  74. package/src/ipc/registerAgentIpc.js +9 -3
  75. package/src/ipc/registerAppIpc.js +4 -1
  76. package/src/ipc/registerTerminalIpc.js +12 -14
  77. package/src/macUpdateHelper.js +210 -0
  78. package/src/monitorWorker.js +65 -6
  79. package/src/platformPath.js +80 -0
  80. package/src/processMonitor.js +80 -22
  81. package/src/providerVisibilityStore.js +45 -0
  82. package/src/sessionIntelligence.js +261 -0
  83. package/src/terminalHost.js +453 -0
  84. package/src/terminalHostDaemon.js +61 -0
  85. package/src/terminalManager.js +218 -6
  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("ui.waiting_for_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 || pane.dead) 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,64 +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" && !tmuxLinkedIds.has(root.id));
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 fallbackTmuxRoots = tmuxRoots.filter((root) => !tmuxLinkedIds.has(root.id));
276
- const fallbackTmuxLanes = lanesFor(fallbackTmuxRoots);
277
- const tmuxDisplayCount = tmuxEntries.length + fallbackTmuxRoots.length;
315
+ const tmuxLanes = lanesFor(tmuxRoots);
278
316
  const standardHtml = standardLanes.length
279
317
  ? `<div class="agent-flow-overview">${standardLanes.map((item) => providerFlowLane(item.providerId, item.roots, model)).join("")}</div>`
280
- : '<div class="runtime-segment-empty"><b>일반 실행 AI가 없습니다</b><span>현재 감지된 작업은 모두 TMUX에서 실행 중입니다.</span></div>';
281
- const tmuxHtml =
282
- tmuxEntries.length || fallbackTmuxLanes.length
283
- ? `${tmuxEntries.length ? `<div class="live-tmux-grid">${tmuxEntries.map(liveTmuxPaneCard).join("")}</div>` : ""}
284
- ${
285
- fallbackTmuxLanes.length
286
- ? `<div class="agent-flow-overview live-tmux-fallback">
287
- ${fallbackTmuxLanes.map((item) => providerFlowLane(item.providerId, item.roots, model)).join("")}
288
- </div>`
289
- : ""
290
- }`
291
- : '<div class="runtime-segment-empty tmux"><b>TMUX에서 실행 중인 AI가 없습니다</b><span>TMUX AI 프로세스가 감지되면 일반 실행과 분리해 여기에 표시합니다.</span></div>';
292
- return `<div class="agent-runtime-split" data-runtime-split="true">
293
- <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">
294
321
  <header>
295
322
  <span class="runtime-segment-icon">▦</span>
296
- <span><small>TMUX 전용</small><b>TMUX 세션</b><em>Linux 작업 묶음·창·분할 칸을 유지해서 실행 중인 AI</em></span>
297
- <strong>${tmuxDisplayCount}개</strong>
298
- <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>
299
326
  </header>
300
- ${tmuxHtml}
301
- </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}
302
332
  <section class="runtime-segment standard-runtime" data-runtime-segment="standard">
303
333
  <header>
304
334
  <span class="runtime-segment-icon">›_</span>
305
- <span><small>TMUX 미사용</small><b>일반 실행 세션</b><em>Codex 앱·외부 터미널에서 실행 중인 메인 AI</em></span>
306
- <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>
307
337
  </header>
308
338
  ${standardHtml}
309
339
  </section>
@@ -341,24 +371,24 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
341
371
  .join("");
342
372
  const metrics = workflowMetrics(session, children);
343
373
  const capacity = metrics.simultaneousCapacity > 0 ? metrics.simultaneousCapacity : "--";
344
- const retained = metrics.retainedCount == null ? "현재 목록 유지 수는 관측되지 않음" : `현재 런타임 목록에는 ${metrics.retainedCount}개 유지`;
345
- 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");
346
376
  return `<div class="agent-workflow-summary" data-collaboration-summary="true">
347
377
  <div class="workflow-metric-grid">
348
378
  <span data-collaboration-metric="created">
349
- <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>
350
380
  </span>
351
381
  <span data-collaboration-metric="capacity">
352
- <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>
353
383
  </span>
354
384
  <span data-collaboration-metric="running">
355
- <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>
356
386
  </span>
357
387
  <span data-collaboration-metric="completed">
358
- <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>
359
389
  </span>
360
390
  </div>
361
- <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>
362
392
  <div class="workflow-summary-providers">${providers}</div>
363
393
  </div>`;
364
394
  }
@@ -379,10 +409,10 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
379
409
  return `<div class="completed-subagent-disclosure ${expanded ? "expanded" : ""}" data-completed-subagent-section>
380
410
  <button type="button" data-subagent-completed-toggle="${esc(ownerId)}" aria-expanded="${expanded ? "true" : "false"}">
381
411
  <span class="completed-disclosure-icon">✓</span>
382
- <span><b>완료된 서브에이전트 ${completed.length}개</b>
383
- <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>
384
414
  </span>
385
- <i>${expanded ? "접기 ↑" : "펼쳐 보기 ↓"}</i>
415
+ <i>${esc(expanded ? t("graph.collapse") : t("graph.expand"))}</i>
386
416
  </button>
387
417
  </div>`;
388
418
  }
@@ -400,12 +430,12 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
400
430
 
401
431
  function communicationEndpoint(value, owner, model) {
402
432
  const path = String(value || "");
403
- if (!path) return "대상 미상";
404
- if (path === "Codex 런타임") return path;
405
- 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");
406
436
  const taskName = agentPathTaskName(path);
407
437
  const session = model.nodes.find((node) => node.agentPath === path || node.taskName === taskName);
408
- if (session) return `${session.agentName || "서브 AI"}${taskName ? ` · ${taskName}` : ""}`;
438
+ if (session) return `${session.agentName || t("graph.sub_ai")}${taskName ? ` · ${taskName}` : ""}`;
409
439
  return taskName || path;
410
440
  }
411
441
 
@@ -418,11 +448,11 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
418
448
  return `<section class="agent-communication-panel empty" data-collaboration-communications="0">
419
449
  <header>
420
450
  <span>
421
- <b>메인 AI ↔ 서브에이전트 소통</b>
422
- <small>배정·추가 지시·결과 반환 기록</small>
451
+ <b>${esc(t("graph.communication_title"))}</b>
452
+ <small>${esc(t("graph.communication_short_description"))}</small>
423
453
  </span>
424
454
  </header>
425
- <p>이 세션 로그에서는 에이전트 간 통신 이벤트가 확인되지 않았습니다.</p>
455
+ <p>${esc(t("graph.no_communication_events"))}</p>
426
456
  </section>`;
427
457
  }
428
458
  const rows = events
@@ -430,32 +460,34 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
430
460
  const text =
431
461
  event.text ||
432
462
  (event.protected
433
- ? `${event.taskName || " 작업"}을 서브에이전트에게 배정했습니다.`
463
+ ? t("graph.assigned_to_subagent", { task: event.taskName || t("graph.this_task") })
434
464
  : event.kind === "started"
435
- ? "런타임에서 실행 시작을 확인했습니다."
436
- : "내용 없이 상태만 기록되었습니다.");
437
- 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")}` : "";
438
468
  return `<article class="agent-communication-event ${esc(event.kind)}" data-communication-kind="${esc(event.kind)}">
439
469
  <span class="communication-route">
440
470
  <b>${esc(communicationEndpoint(event.from, owner, model))}</b><i>→</i>
441
471
  <b>${esc(communicationEndpoint(event.to, owner, model))}</b>
442
472
  </span>
443
473
  <span class="communication-copy">
444
- <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>
445
475
  <strong>${esc(text)}</strong>
446
476
  </span>
447
477
  <time>${esc(timeOnly(event.timestamp))}</time>
448
478
  </article>`;
449
479
  })
450
480
  .join("");
451
- 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 });
452
484
  return `<section class="agent-communication-panel"
453
485
  data-collaboration-communications="${events.length}"
454
486
  data-collaboration-communications-total="${relevant.length}">
455
487
  <header>
456
488
  <span>
457
- <b>메인 AI ↔ 서브에이전트 소통</b>
458
- <small>누가 일을 맡겼고, 언제 시작했으며, 어떤 결과를 돌려줬는지 시간순으로 표시</small>
489
+ <b>${esc(t("graph.communication_title"))}</b>
490
+ <small>${esc(t("graph.communication_description"))}</small>
459
491
  </span>
460
492
  <em>${countLabel}</em>
461
493
  </header>
@@ -463,20 +495,99 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
463
495
  </section>`;
464
496
  }
465
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
+
466
576
  function focusedGraph(focus, model, motionKind = "refresh") {
467
577
  const parent = focus.parentId ? model.byId.get(focus.parentId) : null;
468
578
  const children = graphChildren(focus, model);
579
+ const executionCount = (focus.executions || []).length;
469
580
  const { ongoing, completed } = splitSubagents(children);
470
581
  const completedExpanded = state.expandedCompletedSubagents.has(focus.id);
471
582
  const shownChildren = completedExpanded ? [...ongoing, ...completed] : ongoing;
472
583
  const metrics = workflowMetrics(focus, children);
473
584
  const upstream = parent
474
- ? 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"))
475
586
  : `<div class="agent-workflow-origin">
476
587
  <span class="workflow-origin-icon">◎</span>
477
588
  <span>
478
- <b>사용자 요청</b>
479
- <small>이 작업이 시작된 곳</small>
589
+ <b>${esc(t("graph.user_request"))}</b>
590
+ <small>${esc(t("graph.task_origin"))}</small>
480
591
  </span>
481
592
  <span class="agent-workflow-port output" data-workflow-port="upstream-output" aria-hidden="true">
482
593
  </span>
@@ -484,8 +595,10 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
484
595
  const ongoingRows = ongoing.length
485
596
  ? ongoing.map((child) => workflowCompactNode(child, model, "downstream", agentRoleLabel(child.agentRole))).join("")
486
597
  : children.length
487
- ? '<div class="agent-workflow-empty current-clear"><b>현재 작업 중인 서브에이전트가 없습니다</b><span>완료된 기록은 아래에서 필요할 때만 펼쳐볼 수 있습니다.</span></div>'
488
- : '<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>`;
489
602
  const completedRows = completedExpanded
490
603
  ? `<div class="completed-subagent-list" data-completed-subagent-list>
491
604
  ${completed.map((child) => workflowCompactNode(child, model, "downstream", agentRoleLabel(child.agentRole))).join("")}
@@ -493,13 +606,13 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
493
606
  : "";
494
607
  const downstream = `${ongoingRows}${completedSubagentDisclosure(focus.id, completed, completedExpanded)}${completedRows}`;
495
608
  const connectMotion = ["focus", "focus-back", "view"].includes(motionKind) ? "motion-connect" : "";
496
- const childGroupPort = shownChildren.length
609
+ const childGroupPort = shownChildren.length || executionCount
497
610
  ? '<span class="agent-workflow-port input group-input" data-workflow-port="children-group-input" aria-hidden="true"></span>'
498
611
  : "";
499
612
  return `<div class="agent-workflow-canvas ${connectMotion}" data-workflow-focus="${esc(focus.id)}">
500
- <svg class="agent-workflow-edges" role="img" aria-label="일을 맡긴 AI에서 선택한 AI를 거쳐 도움 AI로 이어지는 연결">
501
- <title>AI 작업 연결</title>
502
- <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>
503
616
  <defs><marker id="workflowArrow" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto" markerUnits="strokeWidth">
504
617
  <path d="M0,0 L8,4 L0,8 Z" fill="context-stroke"></path>
505
618
  </marker></defs>
@@ -507,28 +620,30 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
507
620
  </svg>
508
621
  <div class="agent-workflow-grid">
509
622
  <section class="agent-workflow-column upstream-column">
510
- <header><b>${parent ? "이 일을 맡긴 AI" : "작업 시작점"}</b>
511
- <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>
512
625
  </header>
513
626
  <div class="agent-workflow-stack">${upstream}</div>
514
627
  </section>
515
628
  <section class="agent-workflow-column selected-column">
516
- <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>
517
630
  <div class="agent-workflow-selected-stack">
518
631
  <div class="agent-workflow-selected">
519
632
  <span class="agent-workflow-port input" data-workflow-port="focus-input" aria-hidden="true"></span>
520
633
  ${graphNode(focus, { focus: true })}
521
634
  </div>
522
635
  ${context.agentCommandComposer(focus)}
523
- ${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>' : ""}
524
637
  </div>
525
638
  </section>
526
639
  <section class="agent-workflow-column downstream-column"
527
640
  data-workflow-child-count="${children.length}" data-workflow-visible-child-count="${shownChildren.length}">
528
641
  ${childGroupPort}
529
- <header><b>서브에이전트 세션</b>
530
- <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>
531
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>` : ""}
532
647
  ${workflowChildrenSummary(focus, children)}
533
648
  <div class="agent-workflow-stack downstream-stack ${shownChildren.length > 3 ? "density-many" : ""}">${downstream}</div>
534
649
  </section>
@@ -540,6 +655,6 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
540
655
  return {
541
656
  graphNode, compactGraphNode, providerFlowLane, workflowCompactNode, liveTmuxEntries, liveTmuxPaneCard, runtimeSeparatedOverview,
542
657
  workflowMetrics, workflowChildrenSummary, splitSubagents, completedSubagentDisclosure, agentPathTaskName, communicationEndpoint,
543
- workflowCommunicationPanel, focusedGraph,
658
+ workflowCommunicationPanel, executionActivityLabel, executionActivityStatus, executionActivityCard, executionActivityPanel, focusedGraph,
544
659
  };
545
660
  };