loadtoagent 1.0.0

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 (82) hide show
  1. package/README.ko.md +175 -0
  2. package/README.md +175 -0
  3. package/README.zh-CN.md +175 -0
  4. package/bin/loadtoagent.js +171 -0
  5. package/docs/ARCHITECTURE.md +30 -0
  6. package/docs/PROVIDER-CONTRACTS.md +58 -0
  7. package/docs/assets/loadtoagent-dashboard.png +0 -0
  8. package/docs/assets/loadtoagent-demo.gif +0 -0
  9. package/main.js +493 -0
  10. package/package.json +133 -0
  11. package/preload.js +75 -0
  12. package/renderer/app-agent-actions.js +285 -0
  13. package/renderer/app-bootstrap.js +95 -0
  14. package/renderer/app-dashboard.js +361 -0
  15. package/renderer/app-drawer-content.js +203 -0
  16. package/renderer/app-drawer-data.js +41 -0
  17. package/renderer/app-drawer.js +183 -0
  18. package/renderer/app-events-dialogs.js +169 -0
  19. package/renderer/app-events-filters.js +74 -0
  20. package/renderer/app-events-navigation.js +96 -0
  21. package/renderer/app-events-sessions.js +195 -0
  22. package/renderer/app-events.js +16 -0
  23. package/renderer/app-graph-layout.js +71 -0
  24. package/renderer/app-graph-model.js +107 -0
  25. package/renderer/app-graph-orchestration.js +76 -0
  26. package/renderer/app-graph-view.js +544 -0
  27. package/renderer/app-run-modal.js +221 -0
  28. package/renderer/app-session-render.js +243 -0
  29. package/renderer/app-tmux-render.js +247 -0
  30. package/renderer/app.js +608 -0
  31. package/renderer/fonts/geist-ext.woff2 +0 -0
  32. package/renderer/fonts/geist.woff2 +0 -0
  33. package/renderer/i18n-messages.js +355 -0
  34. package/renderer/i18n.js +122 -0
  35. package/renderer/index.html +386 -0
  36. package/renderer/shared.js +26 -0
  37. package/renderer/styles-agent-map.css +401 -0
  38. package/renderer/styles-cards.css +600 -0
  39. package/renderer/styles-collaboration.css +534 -0
  40. package/renderer/styles-components.css +1293 -0
  41. package/renderer/styles-onboarding.css +344 -0
  42. package/renderer/styles-overlays.css +284 -0
  43. package/renderer/styles-product.css +686 -0
  44. package/renderer/styles-responsive-product.css +689 -0
  45. package/renderer/styles-responsive-runtime.css +718 -0
  46. package/renderer/styles-responsive-shell.css +533 -0
  47. package/renderer/styles-responsive-workflows.css +778 -0
  48. package/renderer/styles-run-composer.css +695 -0
  49. package/renderer/styles-settings.css +606 -0
  50. package/renderer/styles-terminal.css +1241 -0
  51. package/renderer/styles-tmux.css +1162 -0
  52. package/renderer/styles-workflow-map.css +513 -0
  53. package/renderer/styles-workflows.css +1217 -0
  54. package/renderer/styles.css +486 -0
  55. package/renderer/terminal-agent.js +152 -0
  56. package/renderer/terminal-events.js +226 -0
  57. package/renderer/terminal-workbench.js +464 -0
  58. package/renderer/terminal.js +497 -0
  59. package/src/agentMonitor/claudeParser.js +197 -0
  60. package/src/agentMonitor/codexCollaboration.js +95 -0
  61. package/src/agentMonitor/codexParser.js +447 -0
  62. package/src/agentMonitor/genericParser.js +244 -0
  63. package/src/agentMonitor/hierarchy.js +248 -0
  64. package/src/agentMonitor/sessionFiles.js +86 -0
  65. package/src/agentMonitor.js +591 -0
  66. package/src/agentRunner.js +465 -0
  67. package/src/bridgeServer.js +246 -0
  68. package/src/contracts.js +71 -0
  69. package/src/diagnostics.js +23 -0
  70. package/src/ipc/registerAgentIpc.js +12 -0
  71. package/src/ipc/registerAppIpc.js +20 -0
  72. package/src/ipc/registerTerminalIpc.js +50 -0
  73. package/src/ipc/registerTmuxIpc.js +30 -0
  74. package/src/ipc/registerWorkspaceIpc.js +14 -0
  75. package/src/monitorWorker.js +273 -0
  76. package/src/processMonitor.js +394 -0
  77. package/src/providerRegistry.js +120 -0
  78. package/src/terminalManager.js +438 -0
  79. package/src/tmuxController.js +183 -0
  80. package/src/tmuxMonitor.js +432 -0
  81. package/src/updateManager.js +339 -0
  82. package/src/workspaceStore.js +77 -0
@@ -0,0 +1,221 @@
1
+ "use strict";
2
+
3
+ window.LoadToAgentAppFactories = window.LoadToAgentAppFactories || {};
4
+
5
+ window.LoadToAgentAppFactories.createRunModal = function createRunModal(context = {}) {
6
+ const {
7
+ $,
8
+ esc,
9
+ uiLocale,
10
+ state,
11
+ motionPreference,
12
+ motionState,
13
+ markGuideStep,
14
+ rememberDialogTrigger,
15
+ restoreDialogTrigger,
16
+ providerInfo,
17
+ providerStyle,
18
+ } = context;
19
+
20
+ function providerPickerHtml() {
21
+ return state.providers
22
+ .map((provider) => {
23
+ const installed = !!state.availability[provider.id];
24
+ const selected = state.runProvider === provider.id;
25
+ return `<button type="button" class="run-provider-option ${selected ? "selected" : ""}"
26
+ data-run-provider="${esc(provider.id)}"
27
+ style="${providerStyle(provider.id)}"
28
+ aria-pressed="${selected ? "true" : "false"}"
29
+ ${installed ? "" : "disabled"}>
30
+ <span class="provider-mini-mark">${esc(provider.mark)}</span>
31
+ <span class="run-provider-copy">
32
+ <b>${esc(provider.label)}</b>
33
+ <small>${esc(installed ? `${provider.company} · CLI 발견됨` : window.LoadToAgentI18n.t("ui.setup_required"))}</small>
34
+ </span>
35
+ <span class="run-provider-check" aria-hidden="true">✓</span>
36
+ </button>`;
37
+ })
38
+ .join("");
39
+ }
40
+
41
+ function runProviderHelpHtml() {
42
+ const available = state.providers.filter((provider) => state.availability[provider.id]);
43
+ if (available.length) return "";
44
+ const docs = state.providers
45
+ .map(
46
+ (provider) => `<button type="button" data-provider-docs="${esc(provider.id)}">
47
+ <span class="provider-mini-mark" style="${providerStyle(provider.id)}">${esc(provider.mark)}</span>
48
+ <span>
49
+ <b>${esc(window.LoadToAgentI18n.t("provider.install_guide", { provider: provider.label }))}</b>
50
+ <small>공식 문서에서 CLI 설치와 로그인을 확인하세요.</small>
51
+ </span>
52
+ <i aria-hidden="true">↗</i>
53
+ </button>`,
54
+ )
55
+ .join("");
56
+ return `<div class="run-provider-help-copy">
57
+ <b>먼저 AI CLI 한 개를 준비해 주세요</b>
58
+ <p>1. 아래 공식 설치 안내 열기 → 2. 터미널에서 로그인 → 3. 설치 확인을 누르면 됩니다.</p>
59
+ </div>
60
+ <div class="run-provider-docs">${docs}</div>
61
+ <button type="button" class="provider-recheck" data-provider-recheck>↻ 설치 상태 다시 확인</button>`;
62
+ }
63
+
64
+ function runWorkspaceSuggestionsHtml() {
65
+ const selected = String(($("#runCwd") && $("#runCwd").value) || "");
66
+ return state.workspaces
67
+ .slice(0, 4)
68
+ .map((workspace) => {
69
+ const path = workspace.path || workspace.name || "";
70
+ const active = path === selected;
71
+ return `<button type="button" data-run-workspace="${esc(path)}" class="${active ? "selected" : ""}" title="${esc(path)}">
72
+ <span aria-hidden="true">⌘</span>
73
+ ${esc(workspace.name || path.split(/[\\/]/).filter(Boolean).pop() || window.LoadToAgentI18n.t("ui.workspaces"))}
74
+ </button>`;
75
+ })
76
+ .join("");
77
+ }
78
+
79
+ function syncRunComposer() {
80
+ const prompt = $("#runPrompt");
81
+ const count = $("#runPromptCount");
82
+ if (prompt && count) count.textContent = `${prompt.value.length.toLocaleString(uiLocale())} / 8,000`;
83
+ const submitLabel = $("#runSubmitLabel");
84
+ const submit = $('#runForm button[type="submit"]');
85
+ const hasProvider = Boolean(state.availability[state.runProvider]);
86
+ const providerHelp = $("#runProviderHelp");
87
+ if (providerHelp) {
88
+ providerHelp.innerHTML = runProviderHelpHtml();
89
+ providerHelp.classList.toggle(
90
+ "hidden",
91
+ state.providers.some((provider) => state.availability[provider.id]),
92
+ );
93
+ }
94
+ if (submit) submit.disabled = submit.dataset.submitting === "true" || !hasProvider;
95
+ if (submitLabel && submit.dataset.submitting !== "true")
96
+ submitLabel.textContent = hasProvider
97
+ ? window.LoadToAgentI18n.t("provider.assign", { provider: providerInfo(state.runProvider).label })
98
+ : "AI 설치가 필요합니다";
99
+ const writeIntent = /(고치|수정|추가|구현|변경|삭제|작성|리팩터|fix|implement|update|edit|refactor)/i.test((prompt && prompt.value) || "");
100
+ const permissionHint = $("#runPermissionHint");
101
+ const permissionNeeded = writeIntent && !$("#allowWrites").checked;
102
+ permissionHint.classList.toggle("hidden", !permissionNeeded);
103
+ if (permissionNeeded) $("#allowWrites").setAttribute("aria-describedby", "runPermissionHint");
104
+ else $("#allowWrites").removeAttribute("aria-describedby");
105
+ const suggestions = $("#runWorkspaceSuggestions");
106
+ if (suggestions) suggestions.innerHTML = runWorkspaceSuggestionsHtml();
107
+ }
108
+
109
+ function setRunSubmitting(submitting) {
110
+ const submit = $('#runForm button[type="submit"]');
111
+ if (!submit) return;
112
+ submit.dataset.submitting = submitting ? "true" : "false";
113
+ submit.disabled = submitting || !state.availability[state.runProvider];
114
+ submit.setAttribute("aria-busy", submitting ? "true" : "false");
115
+ const label = $("#runSubmitLabel");
116
+ if (label) label.textContent = submitting
117
+ ? "AI 작업을 준비하는 중…"
118
+ : window.LoadToAgentI18n.t("provider.assign", { provider: providerInfo(state.runProvider).label });
119
+ }
120
+
121
+ function openRunModal() {
122
+ rememberDialogTrigger();
123
+ const installed = state.providers.find((provider) => state.availability[provider.id]);
124
+ if (!state.availability[state.runProvider] && installed) state.runProvider = installed.id;
125
+ $("#runProviderPicker").innerHTML = providerPickerHtml();
126
+ if (!$("#runCwd").value) $("#runCwd").value = state.workspace !== "all" ? state.workspace : (state.workspaces[0] && state.workspaces[0].path) || "";
127
+ $("#runCwd").placeholder = state.platform.id === "win32" ? "D:\\project" : "/Users/me/project";
128
+ const advanced = $("#runForm .run-advanced");
129
+ if (advanced) advanced.open = Boolean($("#runModel").value.trim());
130
+ $("#runError").classList.add("hidden");
131
+ syncRunComposer();
132
+ clearTimeout(motionState.modalTimer);
133
+ $("#runModal").classList.remove("hidden", "closing");
134
+ setTimeout(() => $("#runPrompt").focus(), 0);
135
+ }
136
+
137
+ function closeRunModal() {
138
+ const modal = $("#runModal");
139
+ if (modal.classList.contains("hidden") || modal.classList.contains("closing")) return;
140
+ modal.classList.add("closing");
141
+ clearTimeout(motionState.modalTimer);
142
+ motionState.modalTimer = setTimeout(
143
+ () => {
144
+ modal.classList.add("hidden");
145
+ modal.classList.remove("closing");
146
+ restoreDialogTrigger();
147
+ },
148
+ motionPreference.matches ? 0 : 220,
149
+ );
150
+ }
151
+
152
+ function toast(message) {
153
+ const el = $("#toast");
154
+ el.textContent = message;
155
+ el.classList.remove("hidden", "leaving");
156
+ clearTimeout(motionState.toastTimer);
157
+ motionState.toastTimer = setTimeout(() => {
158
+ el.classList.add("leaving");
159
+ motionState.toastTimer = setTimeout(
160
+ () => {
161
+ el.classList.add("hidden");
162
+ el.classList.remove("leaving");
163
+ },
164
+ motionPreference.matches ? 0 : 220,
165
+ );
166
+ }, 3200);
167
+ }
168
+
169
+ async function performUiAction(action, failureMessage) {
170
+ try {
171
+ return await action();
172
+ } catch (error) {
173
+ toast((error && error.message) || failureMessage);
174
+ return null;
175
+ }
176
+ }
177
+
178
+ async function handleRun(event) {
179
+ event.preventDefault();
180
+ if (!state.availability[state.runProvider]) {
181
+ $("#runError").textContent = window.LoadToAgentI18n.t("ui.no_ai_cli_is_ready_follow_the_official_setup_guide");
182
+ $("#runError").classList.remove("hidden");
183
+ return;
184
+ }
185
+ setRunSubmitting(true);
186
+ $("#runError").classList.add("hidden");
187
+ try {
188
+ const result = await window.loadtoagent.runAgent({
189
+ provider: state.runProvider,
190
+ cwd: $("#runCwd").value.trim(),
191
+ model: $("#runModel").value.trim(),
192
+ prompt: $("#runPrompt").value.trim(),
193
+ allowWrites: $("#allowWrites").checked,
194
+ });
195
+ if (!result.ok) throw new Error(result.error || window.LoadToAgentI18n.t("ui.could_not_start_the_task"));
196
+ markGuideStep("create");
197
+ closeRunModal();
198
+ $("#runPrompt").value = "";
199
+ syncRunComposer();
200
+ toast(window.LoadToAgentI18n.t("provider.started", { provider: providerInfo(state.runProvider).label }));
201
+ } catch (error) {
202
+ $("#runError").textContent = error.message;
203
+ $("#runError").classList.remove("hidden");
204
+ } finally {
205
+ setRunSubmitting(false);
206
+ }
207
+ }
208
+
209
+ return {
210
+ providerPickerHtml,
211
+ runProviderHelpHtml,
212
+ runWorkspaceSuggestionsHtml,
213
+ syncRunComposer,
214
+ setRunSubmitting,
215
+ openRunModal,
216
+ closeRunModal,
217
+ toast,
218
+ performUiAction,
219
+ handleRun,
220
+ };
221
+ };
@@ -0,0 +1,243 @@
1
+ "use strict";
2
+
3
+ window.LoadToAgentAppFactories = window.LoadToAgentAppFactories || {};
4
+
5
+ window.LoadToAgentAppFactories.createSessionRenderer = function createSessionRenderer(context = {}) {
6
+ const {
7
+ $,
8
+ esc,
9
+ state,
10
+ STATUS,
11
+ VIEW_TITLES,
12
+ captureMotionLayout,
13
+ playMotionLayout,
14
+ animateVisibleSections,
15
+ renderGuide,
16
+ syncViewChrome,
17
+ readablePreview,
18
+ compact,
19
+ fullNumber,
20
+ timeAgo,
21
+ providerInfo,
22
+ providerStyle,
23
+ statusClass,
24
+ currentActivity,
25
+ isLiveSession,
26
+ latestWorkCopy,
27
+ statusIcon,
28
+ renderProviderRail,
29
+ isProjectlessSession,
30
+ sessionWorkspaceLabel,
31
+ renderWorkspaces,
32
+ renderGlobalStats,
33
+ renderUpdateSettings,
34
+ renderProviderOverview,
35
+ renderProviderFilter,
36
+ filteredSessions,
37
+ graphFilteredSessions,
38
+ executionModeBadge,
39
+ renderAgentMap,
40
+ renderTmuxMap,
41
+ } = context;
42
+
43
+ function recentConversation(session) {
44
+ const messages = (session.messages || []).filter((message) => message && message.text && message.role !== "system");
45
+ const user = [...messages].reverse().find((message) => message.role === "user");
46
+ const assistant = [...messages].reverse().find((message) => message.role === "assistant");
47
+ const tool = [...messages].reverse().find((message) => message.role === "tool");
48
+ const rows = [];
49
+ if (user) rows.push({ label: "나", text: readablePreview(user.text, 140).text, tone: "user" });
50
+ if (assistant) rows.push({ label: providerInfo(session.provider).label, text: readablePreview(assistant.text, 140).text, tone: "assistant" });
51
+ else if (tool) rows.push({ label: tool.title || "도구", text: readablePreview(tool.text, 140).text, tone: "tool" });
52
+ if (!rows.length) rows.push({ label: "상태", text: session.statusDetail || "대화 이벤트를 기다리는 중입니다.", tone: "system" });
53
+ return rows.slice(-2);
54
+ }
55
+
56
+ function sessionCard(session, opts = {}) {
57
+ const provider = providerInfo(session.provider);
58
+ const usage = session.usage || {};
59
+ const context = session.context || {};
60
+ const activity = currentActivity(session);
61
+ const running = session.status === "running" || session.status === "starting";
62
+ const children = session.childIds || [];
63
+ const model = session.model || "사용 모델 정보 없음";
64
+ const contextPercent = Math.max(0, Math.min(100, Number(context.percent || 0)));
65
+ const remaining = context.window ? Math.max(0, Number(context.window) - Number(context.used || 0)) : 0;
66
+ const gaugeTone = contextPercent >= 90 ? "critical" : contextPercent >= 75 ? "warning" : "safe";
67
+ const conversation = recentConversation(session);
68
+ const runtime = session.runtimePresence || [];
69
+ const titlePreview = readablePreview(session.title, 96);
70
+ const activityCopy = latestWorkCopy(session) || session.statusDetail || "새 이벤트 대기";
71
+ const activityPreview = readablePreview(activityCopy, 116);
72
+ const accessibleId = `session-${String(session.id || "").replace(/[^a-zA-Z0-9_-]/g, "-")}`;
73
+ return `<article class="session-card ${opts.live ? "live-card" : ""} ${statusClass(session.status)} ${session.parentId ? "subagent" : ""}"
74
+ data-session-id="${esc(session.id)}"
75
+ data-motion-key="session:${esc(session.id)}"
76
+ data-motion-value="${esc(session.updatedAt || "")}:${usage.total || 0}:${esc(session.status || "")}"
77
+ style="${providerStyle(session.provider)}"
78
+ role="button" tabindex="0"
79
+ aria-labelledby="${accessibleId}-title" aria-describedby="${accessibleId}-summary">
80
+ <div class="card-head">
81
+ <span class="provider-mark">${esc(provider.mark)}</span>
82
+ <div class="card-head-main"><div class="card-provider-line"><b>${esc(provider.label)}</b><span>${esc(provider.company)}</span></div></div>
83
+ ${executionModeBadge(session, true)}
84
+ <span class="status-pill ${statusClass(session.status)}">${esc(STATUS[session.status] || session.status)}</span>
85
+ </div>
86
+ <h3 id="${accessibleId}-title" class="card-title" title="${esc(titlePreview.full)}">${esc(titlePreview.text)}</h3>
87
+ <div class="card-subtitle">
88
+ <span>${esc(model)}</span>
89
+ <i>
90
+ </i>
91
+ <span title="${esc(isProjectlessSession(session) ? window.LoadToAgentI18n.t("ui.session_not_linked_to_a_specific_project") : session.cwd)}">
92
+ ${esc(sessionWorkspaceLabel(session))}
93
+ </span>${
94
+ session.agentName
95
+ ? `<i>
96
+ </i>
97
+ <span>${esc(session.agentName)}</span>`
98
+ : ""
99
+ }</div>
100
+ <div id="${accessibleId}-summary" class="now-strip ${running ? "is-live" : ""}">
101
+ <span class="now-strip-icon">${statusIcon(activity.type)}</span>
102
+ <div><b>${running ? "지금: " : ""}${esc(activity.title)}</b><span title="${esc(activityPreview.full)}">${esc(activityPreview.text)}</span></div>
103
+ ${running ? '<span class="activity-wave"><i></i><i></i><i></i><i></i><i></i></span>' : ""}
104
+ </div>
105
+ ${
106
+ runtime.length
107
+ ? `<div class="runtime-strip">
108
+ <span class="runtime-pulse">
109
+ </span>
110
+ <b>실제로 실행 중인 프로그램 ${runtime.length}개</b>
111
+ <span>${esc(runtime.map((item) => item.label || `프로그램 ${item.pid}`).join(" · "))}</span>
112
+ </div>`
113
+ : ""
114
+ }
115
+ <div class="conversation-preview">
116
+ ${conversation.map((row) => `<div class="preview-line ${row.tone}"><b>${esc(row.label)}</b><span>${esc(row.text)}</span></div>`).join("")}
117
+ </div>
118
+ <div class="context-meter ${gaugeTone}">
119
+ <div class="context-meter-head">
120
+ <div>
121
+ <span>AI의 기억 공간 사용량</span>
122
+ <strong>${context.window ? `${fullNumber(context.used)} / ${fullNumber(context.window)}` : `${fullNumber(context.used)} / --`}</strong>
123
+ </div>
124
+ <b>${context.window ? `${contextPercent.toFixed(1)}%` : "--"}</b>
125
+ </div>
126
+ <div class="context-meter-track"><span style="width:${contextPercent}%"></span><i style="left:75%"></i><i style="left:90%"></i></div>
127
+ <div class="context-meter-foot">
128
+ <span>${context.window ? `아직 ${compact(remaining)} 토큰만큼 기억 가능` : "기억 공간 크기 정보 없음"}</span>
129
+ <span>지금까지 ${compact(usage.total)} 토큰 사용</span>
130
+ </div>
131
+ </div>
132
+ <div class="token-row">
133
+ <div><span>받은 글</span><b>${compact(usage.input)}</b></div>
134
+ <div><span>AI가 쓴 글</span><b>${compact(usage.output)}</b></div>
135
+ <div><span>다시 쓴 기억</span><b>${compact(usage.cachedInput)}</b></div>
136
+ <div class="total"><span>전체 사용</span><b>${compact(usage.total)}</b></div>
137
+ </div>
138
+ ${
139
+ children.length
140
+ ? `<div class="child-row">
141
+ <b>⑂</b>
142
+ <span>서브에이전트 ${children.length}개 누적 생성</span>
143
+ <span class="child-dots">${children
144
+ .slice(0, 4)
145
+ .map(() => "<i></i>")
146
+ .join("")}</span>
147
+ </div>`
148
+ : ""
149
+ }
150
+ <footer class="card-footer">
151
+ <span class="source-tag">${esc(session.sourceLabel || "내 PC의 작업 기록")}</span>
152
+ <span>${esc(timeAgo(session.updatedAt))}</span>
153
+ </footer>
154
+ </article>`;
155
+ }
156
+
157
+ function renderSessions(motionKind = "refresh", deferMotion = false) {
158
+ const previousLayout = deferMotion ? null : captureMotionLayout();
159
+ syncViewChrome();
160
+ renderGuide();
161
+ const tmuxView = state.view === "tmux";
162
+ const terminalView = state.view === "terminal";
163
+ const settingsView = state.view === "settings";
164
+ $("#terminalSection").classList.toggle("hidden", !terminalView);
165
+ $("#tmuxSection").classList.toggle("hidden", !tmuxView);
166
+ $("#settingsSection").classList.toggle("hidden", !settingsView);
167
+ $("#globalStats").classList.toggle("hidden", tmuxView || terminalView || settingsView);
168
+ $("#providerOverview").classList.toggle("hidden", tmuxView || terminalView || settingsView);
169
+ $("#sessionSection").classList.toggle("hidden", tmuxView || terminalView || settingsView);
170
+ const guideVisible = state.view === "all" && state.guideExpanded && !state.graphFocusId;
171
+ $("#beginnerGuide").classList.toggle("hidden", !guideVisible);
172
+ $("#guideBtn").setAttribute("aria-expanded", guideVisible ? "true" : "false");
173
+ renderUpdateSettings();
174
+ if (settingsView) {
175
+ $("#liveSection").classList.add("hidden");
176
+ if (window.LoadToAgentTerminal) window.LoadToAgentTerminal.deactivate();
177
+ if (!deferMotion) playMotionLayout(previousLayout, motionKind);
178
+ if (motionKind === "view") animateVisibleSections();
179
+ return;
180
+ }
181
+ if (terminalView) {
182
+ $("#liveSection").classList.add("hidden");
183
+ if (window.LoadToAgentTerminal) window.LoadToAgentTerminal.activate(state.snapshot, state.workspaces, "general");
184
+ if (!deferMotion) playMotionLayout(previousLayout, motionKind);
185
+ if (motionKind === "view") animateVisibleSections();
186
+ return;
187
+ }
188
+ if (tmuxView) {
189
+ $("#liveSection").classList.add("hidden");
190
+ renderTmuxMap();
191
+ if (window.LoadToAgentTerminal) window.LoadToAgentTerminal.activate(state.snapshot, state.workspaces, "tmux");
192
+ if (!deferMotion) playMotionLayout(previousLayout, motionKind);
193
+ if (motionKind === "view") animateVisibleSections();
194
+ return;
195
+ }
196
+ if (window.LoadToAgentTerminal) window.LoadToAgentTerminal.deactivate();
197
+ const sessions = filteredSessions();
198
+ const showMap = ["all", "active"].includes(state.view);
199
+ const graphLiveCount = showMap ? renderAgentMap(graphFilteredSessions(), motionKind) : 0;
200
+ const regular = state.view === "all" ? sessions.filter((session) => !isLiveSession(session)) : state.view === "active" ? [] : sessions;
201
+ const visible = regular.slice(0, state.visibleLimit);
202
+ $("#liveSection").classList.toggle("hidden", graphLiveCount === 0);
203
+ $("#viewTitle").textContent = VIEW_TITLES[state.view] || window.LoadToAgentI18n.t("ui.recent_conversations_and_tasks");
204
+ $("#sessionGrid").innerHTML = visible.map((session) => sessionCard(session)).join("");
205
+ $("#sessionGrid").classList.toggle("hidden", visible.length === 0);
206
+ $("#loadMoreBtn").classList.toggle("hidden", regular.length <= state.visibleLimit);
207
+ $("#loadMoreBtn").textContent = window.LoadToAgentI18n.t("common.remaining", { count: regular.length - state.visibleLimit });
208
+ $("#emptyState").classList.toggle("hidden", graphLiveCount + regular.length !== 0);
209
+ if (graphLiveCount + regular.length === 0) {
210
+ const emptyCopy = state.search
211
+ ? [window.LoadToAgentI18n.t("ui.no_search_results"), window.LoadToAgentI18n.t("ui.clear_the_search_or_change_the_ai_and_workspace_filters")]
212
+ : state.view === "active"
213
+ ? [window.LoadToAgentI18n.t("ui.no_tasks_are_currently_active"), window.LoadToAgentI18n.t("ui.new_tasks_will_show_their_progress_here_immediately")]
214
+ : state.view === "waiting"
215
+ ? [window.LoadToAgentI18n.t("ui.all_caught_up"), window.LoadToAgentI18n.t("ui.no_tasks_are_waiting_for_your_response_or_choice")]
216
+ : [window.LoadToAgentI18n.t("ui.no_tasks_to_show_yet"), window.LoadToAgentI18n.t("ui.check_ai_readiness_then_start_your_first_task")];
217
+ $("#emptyState h3").textContent = emptyCopy[0];
218
+ $("#emptyState p").textContent = emptyCopy[1];
219
+ }
220
+ if (!deferMotion) playMotionLayout(previousLayout, motionKind);
221
+ if (motionKind === "view") animateVisibleSections();
222
+ }
223
+
224
+ function render(motionKind = "refresh") {
225
+ const previousLayout = captureMotionLayout();
226
+ renderProviderRail();
227
+ renderWorkspaces();
228
+ renderGlobalStats();
229
+ renderProviderOverview();
230
+ renderProviderFilter();
231
+ renderSessions(motionKind, true);
232
+ if (state.selectedId && $("#detailDrawer").classList.contains("open")) context.renderDrawer();
233
+ playMotionLayout(previousLayout, motionKind);
234
+ if (motionKind === "view") animateVisibleSections();
235
+ }
236
+
237
+ return {
238
+ recentConversation,
239
+ sessionCard,
240
+ renderSessions,
241
+ render,
242
+ };
243
+ };