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.
- package/README.ko.md +7 -0
- package/README.md +7 -0
- package/README.zh-CN.md +7 -0
- package/docs/PROVIDER-CONTRACTS.md +23 -1
- package/docs/UI-AUDIT-100.md +118 -0
- package/docs/UI-AUDIT-101-200.md +120 -0
- package/docs/UI-AUDIT-201-300.md +120 -0
- package/main.js +183 -37
- package/package.json +15 -4
- package/preload.js +11 -0
- package/renderer/app-agent-actions.js +125 -53
- package/renderer/app-bootstrap.js +54 -9
- package/renderer/app-dashboard.js +197 -58
- package/renderer/app-drawer-content.js +103 -86
- package/renderer/app-drawer-data.js +26 -13
- package/renderer/app-drawer.js +79 -55
- package/renderer/app-events-dialogs.js +146 -37
- package/renderer/app-events-filters.js +172 -9
- package/renderer/app-events-navigation.js +78 -31
- package/renderer/app-events-sessions.js +171 -4
- package/renderer/app-events.js +2 -1
- package/renderer/app-graph-model.js +32 -4
- package/renderer/app-graph-orchestration.js +16 -13
- package/renderer/app-graph-view.js +228 -112
- package/renderer/app-management.js +211 -0
- package/renderer/app-provider-visibility.js +124 -0
- package/renderer/app-quality.js +568 -0
- package/renderer/app-run-modal.js +103 -33
- package/renderer/app-runtime-overview.js +312 -0
- package/renderer/app-session-render.js +94 -37
- package/renderer/app-tmux-render.js +119 -38
- package/renderer/app.js +155 -76
- package/renderer/i18n-messages.js +833 -15
- package/renderer/i18n.js +104 -0
- package/renderer/index.html +162 -75
- package/renderer/shared.js +17 -0
- package/renderer/styles-agent-map.css +50 -1
- package/renderer/styles-cards.css +26 -0
- package/renderer/styles-collaboration.css +18 -88
- package/renderer/styles-components.css +126 -14
- package/renderer/styles-management.css +355 -0
- package/renderer/styles-overlays.css +13 -2
- package/renderer/styles-product.css +24 -16
- package/renderer/styles-quality.css +312 -0
- package/renderer/styles-readability.css +862 -0
- package/renderer/styles-responsive-product.css +84 -12
- package/renderer/styles-responsive-runtime.css +31 -14
- package/renderer/styles-responsive-shell.css +44 -48
- package/renderer/styles-responsive-workflows.css +37 -17
- package/renderer/styles-run-composer.css +5 -0
- package/renderer/styles-runtime-overview.css +285 -0
- package/renderer/styles-settings.css +160 -0
- package/renderer/styles-terminal.css +339 -2
- package/renderer/styles-tmux.css +154 -0
- package/renderer/styles-workflow-map.css +362 -15
- package/renderer/styles-workflows.css +69 -2
- package/renderer/styles.css +27 -12
- package/renderer/terminal-agent.js +17 -13
- package/renderer/terminal-events.js +233 -40
- package/renderer/terminal-workbench.js +176 -78
- package/renderer/terminal.js +350 -37
- package/src/agentMonitor/claudeParser.js +96 -7
- package/src/agentMonitor/codexParser.js +59 -4
- package/src/agentMonitor/executionActivity.js +282 -0
- package/src/agentMonitor/genericParser.js +56 -13
- package/src/agentMonitor/hierarchy.js +17 -0
- package/src/agentMonitor/responseIntent.js +51 -0
- package/src/agentMonitor.js +21 -3
- package/src/agentRunner.js +66 -1
- package/src/attentionNotifier.js +71 -0
- package/src/automationMonitor.js +258 -0
- package/src/contracts.js +10 -1
- package/src/ipc/registerAgentIpc.js +9 -3
- package/src/ipc/registerAppIpc.js +4 -1
- package/src/ipc/registerTerminalIpc.js +12 -6
- package/src/macUpdateHelper.js +210 -0
- package/src/monitorWorker.js +65 -6
- package/src/platformPath.js +80 -0
- package/src/processMonitor.js +81 -23
- package/src/providerVisibilityStore.js +45 -0
- package/src/sessionIntelligence.js +247 -0
- package/src/terminalHost.js +381 -0
- package/src/terminalHostDaemon.js +60 -0
- package/src/terminalManager.js +158 -3
- package/src/tmuxMonitor.js +2 -2
- package/src/updateInstaller.js +175 -0
|
@@ -4,33 +4,40 @@ window.LoadToAgentAppFactories = window.LoadToAgentAppFactories || {};
|
|
|
4
4
|
|
|
5
5
|
window.LoadToAgentAppFactories.createDrawerContent = function createDrawerContent(context = {}) {
|
|
6
6
|
const { esc, uiLocale, state, messageContentHtml, compact, fullNumber, timeOnly, providerInfo, statusIcon, agentPathTaskName, snapshotSession } = context;
|
|
7
|
+
const t = (key, params) => window.LoadToAgentI18n.t(key, params);
|
|
7
8
|
|
|
8
|
-
function chatHtml(session) {
|
|
9
|
+
function chatHtml(session, options = {}) {
|
|
9
10
|
const messages = session.messages || [];
|
|
10
|
-
if (!messages.length) return
|
|
11
|
+
if (!messages.length) return `<div class="empty-state"><h3>${esc(t("drawer.no_conversation"))}</h3></div>`;
|
|
12
|
+
const userLabel = options.userLabel || t("drawer.user");
|
|
13
|
+
const assistantLabel = options.assistantLabel || providerInfo(session.provider).label;
|
|
14
|
+
const conversationLabel = options.conversationLabel || t("drawer.conversation");
|
|
11
15
|
const conversation = messages.filter((message) => message.role === "user" || message.role === "assistant");
|
|
12
16
|
const activities = messages.filter((message) => message.role !== "user" && message.role !== "assistant");
|
|
13
17
|
const omitted = Number(session.omittedMessages || 0);
|
|
14
18
|
const notice =
|
|
15
19
|
omitted || session.truncated
|
|
16
|
-
? `<div class="chat-truncated"
|
|
20
|
+
? `<div class="chat-truncated">${esc(t("drawer.recent_history"))}${omitted ? ` · ${esc(t("drawer.messages_omitted", { count: omitted.toLocaleString(uiLocale()) }))}` : ""}</div>`
|
|
17
21
|
: "";
|
|
18
22
|
const statusLabels = {
|
|
19
|
-
started: "
|
|
20
|
-
done:
|
|
23
|
+
started: t("ui.working"), running: t("ui.working"),
|
|
24
|
+
done: t("ui.completed"), completed: t("ui.completed"), failed: t("drawer.failed"),
|
|
21
25
|
};
|
|
22
26
|
const statusLabel = (value) => statusLabels[value] || value || "";
|
|
23
27
|
const rows = conversation
|
|
24
28
|
.map((message) => {
|
|
25
29
|
const role = message.role === "assistant" ? "assistant" : message.role === "tool" ? "tool" : message.role === "system" ? "system" : "user";
|
|
30
|
+
const renderedMessage = role === "tool" || role === "system"
|
|
31
|
+
? { ...message, text: window.LoadToAgentI18n.observedText(message.text) }
|
|
32
|
+
: message;
|
|
26
33
|
const label =
|
|
27
34
|
role === "assistant"
|
|
28
|
-
?
|
|
35
|
+
? assistantLabel
|
|
29
36
|
: role === "tool"
|
|
30
|
-
? message.title || "
|
|
37
|
+
? window.LoadToAgentI18n.observedText(message.title || t("session.tool"))
|
|
31
38
|
: message.role === "system"
|
|
32
|
-
? "
|
|
33
|
-
:
|
|
39
|
+
? t("drawer.system")
|
|
40
|
+
: userLabel;
|
|
34
41
|
const avatar = role === "assistant" ? providerInfo(session.provider).mark : role === "tool" ? "⌘" : role === "system" ? "i" : "ME";
|
|
35
42
|
const fullTime = new Date(message.timestamp).toLocaleString(uiLocale());
|
|
36
43
|
return `<div class="chat-row ${role}" data-message-id="${esc(message.id || "")}">
|
|
@@ -40,44 +47,44 @@ window.LoadToAgentAppFactories.createDrawerContent = function createDrawerConten
|
|
|
40
47
|
<b>${esc(label)}</b>
|
|
41
48
|
<span title="${esc(fullTime)}">${esc(timeOnly(message.timestamp))}</span>
|
|
42
49
|
${message.status ? `<span>${esc(statusLabel(message.status))}</span>` : ""}
|
|
43
|
-
</div>${messageContentHtml(
|
|
50
|
+
</div>${messageContentHtml(renderedMessage, session.id)}</div>
|
|
44
51
|
</div>`;
|
|
45
52
|
})
|
|
46
53
|
.join("");
|
|
47
54
|
const activityHtml = activities.length
|
|
48
|
-
? `<details class="chat-activities">
|
|
49
|
-
<summary
|
|
55
|
+
? `<details class="chat-activities" data-disclosure-key="${esc(`drawer:${session.id}:activities`)}">
|
|
56
|
+
<summary>${esc(t("drawer.activities_view", { count: activities.length }))}</summary>
|
|
50
57
|
<div>${activities
|
|
51
58
|
.map(
|
|
52
59
|
(message) => `<article>
|
|
53
60
|
<header>
|
|
54
|
-
<b>${esc(message.title || (message.role === "tool" ? "
|
|
61
|
+
<b>${esc(window.LoadToAgentI18n.observedText(message.title || (message.role === "tool" ? t("drawer.tool_execution") : t("drawer.system"))))}</b>
|
|
55
62
|
<span>${esc(statusLabel(message.status))} · ${esc(timeOnly(message.timestamp))}</span>
|
|
56
|
-
</header>${messageContentHtml(message)}</article>`,
|
|
63
|
+
</header>${messageContentHtml({ ...message, text: window.LoadToAgentI18n.observedText(message.text) }, session.id)}</article>`,
|
|
57
64
|
)
|
|
58
65
|
.join("")}</div>
|
|
59
66
|
</details>`
|
|
60
67
|
: "";
|
|
61
|
-
const emptyConversation = conversation.length ? "" :
|
|
68
|
+
const emptyConversation = conversation.length ? "" : `<div class="empty-state compact"><h3>${esc(t("drawer.no_user_ai_conversation"))}</h3></div>`;
|
|
62
69
|
return `${notice}<div class="chat-history-head">
|
|
63
|
-
<span
|
|
64
|
-
<button type="button" data-scroll-latest
|
|
70
|
+
<span>${esc(t("drawer.conversation_summary", { label: conversationLabel, count: conversation.length, activities: activities.length ? ` · ${t("drawer.activities", { count: activities.length })}` : "" }))}</span>
|
|
71
|
+
<button type="button" data-scroll-latest>${esc(t("drawer.latest_conversation"))} ↓</button>
|
|
65
72
|
</div>
|
|
66
|
-
<div class="chat-list">${rows}${emptyConversation}${activityHtml}<div class="chat-latest-anchor" aria-label="
|
|
73
|
+
<div class="chat-list">${rows}${emptyConversation}${activityHtml}<div class="chat-latest-anchor" aria-label="${esc(t("drawer.latest_conversation"))}">
|
|
67
74
|
</div>
|
|
68
75
|
</div>`;
|
|
69
76
|
}
|
|
70
77
|
|
|
71
78
|
function lifecycleHtml(session) {
|
|
72
79
|
const events = session.lifecycle || [];
|
|
73
|
-
if (!events.length) return
|
|
80
|
+
if (!events.length) return `<div class="empty-state"><h3>${esc(t("drawer.no_lifecycle"))}</h3></div>`;
|
|
74
81
|
return `<div class="lifecycle-list">${events
|
|
75
82
|
.map(
|
|
76
83
|
(event) => `<div class="lifecycle-event ${esc(event.status)}">
|
|
77
84
|
<span class="life-node">${statusIcon(event.type)}</span>
|
|
78
85
|
<div class="life-copy">
|
|
79
|
-
<b>${esc(event.label)}</b>
|
|
80
|
-
<span>${esc(event.detail || event.type)}</span>
|
|
86
|
+
<b>${esc(window.LoadToAgentI18n.observedText(event.label))}</b>
|
|
87
|
+
<span>${esc(window.LoadToAgentI18n.observedText(event.detail || event.type))}</span>
|
|
81
88
|
</div>
|
|
82
89
|
<time>${esc(timeOnly(event.timestamp))}</time>
|
|
83
90
|
</div>`,
|
|
@@ -91,14 +98,14 @@ window.LoadToAgentAppFactories.createDrawerContent = function createDrawerConten
|
|
|
91
98
|
const context = session.context || {};
|
|
92
99
|
const sourceLabel =
|
|
93
100
|
context.source === "session"
|
|
94
|
-
? "
|
|
101
|
+
? t("drawer.context_source_session")
|
|
95
102
|
: context.source === "model-catalog"
|
|
96
|
-
? "
|
|
97
|
-
: "
|
|
103
|
+
? t("drawer.context_source_catalog")
|
|
104
|
+
: t("session.context_size_unknown");
|
|
98
105
|
return `<div class="token-hero" style="--drawer-provider:${providerInfo(session.provider).accent}">
|
|
99
106
|
<div class="token-hero-head">
|
|
100
|
-
<span
|
|
101
|
-
<b>${context.window ? `${fullNumber(context.used)} / ${fullNumber(context.window)}
|
|
107
|
+
<span>${esc(t("session.context_usage"))}</span>
|
|
108
|
+
<b>${esc(t("drawer.tokens", { count: context.window ? `${fullNumber(context.used)} / ${fullNumber(context.window)}` : fullNumber(context.used) }))}</b>
|
|
102
109
|
</div>
|
|
103
110
|
<div class="big-context"><span style="width:${Math.min(100, context.percent || 0)}%"></span></div>
|
|
104
111
|
<div class="context-scale">
|
|
@@ -107,15 +114,15 @@ window.LoadToAgentAppFactories.createDrawerContent = function createDrawerConten
|
|
|
107
114
|
</div>
|
|
108
115
|
</div>
|
|
109
116
|
<div class="token-grid">
|
|
110
|
-
<div class="token-tile"><span
|
|
111
|
-
<div class="token-tile"><span
|
|
112
|
-
<div class="token-tile"><span
|
|
113
|
-
<div class="token-tile"><span
|
|
114
|
-
<div class="token-tile"><span
|
|
115
|
-
<div class="token-tile"><span
|
|
116
|
-
<div class="token-tile"><span
|
|
117
|
-
<div class="token-tile"><span
|
|
118
|
-
</div><div class="token-note">${esc(
|
|
117
|
+
<div class="token-tile"><span>${esc(t("drawer.input"))}</span><strong>${fullNumber(usage.input)}</strong><small>${esc(t("drawer.input_help"))}</small></div>
|
|
118
|
+
<div class="token-tile"><span>${esc(t("drawer.output"))}</span><strong>${fullNumber(usage.output)}</strong><small>${esc(t("drawer.output_help"))}</small></div>
|
|
119
|
+
<div class="token-tile"><span>${esc(t("drawer.cached"))}</span><strong>${fullNumber(usage.cachedInput)}</strong><small>${esc(t("drawer.cached_help"))}</small></div>
|
|
120
|
+
<div class="token-tile"><span>${esc(t("drawer.cache_write"))}</span><strong>${fullNumber(usage.cacheWrite)}</strong><small>${esc(t("drawer.cache_write_help"))}</small></div>
|
|
121
|
+
<div class="token-tile"><span>${esc(t("drawer.reasoning"))}</span><strong>${fullNumber(usage.reasoning)}</strong><small>${esc(t("drawer.reasoning_help"))}</small></div>
|
|
122
|
+
<div class="token-tile"><span>${esc(t("drawer.total"))}</span><strong>${fullNumber(usage.total)}</strong><small>${esc(t("drawer.total_help"))}</small></div>
|
|
123
|
+
<div class="token-tile"><span>${esc(t("drawer.last_input"))}</span><strong>${fullNumber(turn.input)}</strong><small>${esc(t("drawer.latest_turn"))}</small></div>
|
|
124
|
+
<div class="token-tile"><span>${esc(t("drawer.last_total"))}</span><strong>${fullNumber(turn.total)}</strong><small>${esc(t("drawer.last_total_help"))}</small></div>
|
|
125
|
+
</div><div class="token-note">${esc(t("drawer.token_note", { source: sourceLabel }))}</div>`;
|
|
119
126
|
}
|
|
120
127
|
|
|
121
128
|
function subagentCommunicationEvents(session) {
|
|
@@ -136,68 +143,78 @@ window.LoadToAgentAppFactories.createDrawerContent = function createDrawerConten
|
|
|
136
143
|
return { text: `${text.slice(0, maxCharacters).trimEnd()}…`, truncated: true };
|
|
137
144
|
}
|
|
138
145
|
|
|
139
|
-
function
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
146
|
+
function subagentCoordinationEvents(session) {
|
|
147
|
+
return subagentCommunicationEvents(session).filter((event) => {
|
|
148
|
+
if (!event || event.protected || event.kind === "started") return false;
|
|
149
|
+
if (!["assignment", "followup", "message", "result", "interrupt"].includes(event.kind)) return false;
|
|
150
|
+
const text = String(event.text || "").trim();
|
|
151
|
+
return Boolean(text && text.toLowerCase() !== "started");
|
|
152
|
+
});
|
|
143
153
|
}
|
|
144
154
|
|
|
145
|
-
function
|
|
146
|
-
const
|
|
155
|
+
function subagentWorkMessages(session) {
|
|
156
|
+
const messages = [...(session.messages || [])];
|
|
157
|
+
const hasConversation = messages.some((message) =>
|
|
158
|
+
(message.role === "user" || message.role === "assistant") && String(message.text || "").trim(),
|
|
159
|
+
);
|
|
160
|
+
if (hasConversation) return messages;
|
|
161
|
+
const delegation = session.delegation || {};
|
|
162
|
+
if (delegation.assignmentObserved && !delegation.assignmentProtected && String(delegation.assignment || "").trim()) {
|
|
163
|
+
messages.push({
|
|
164
|
+
id: `${session.id}:delegation`, role: "user", text: delegation.assignment,
|
|
165
|
+
timestamp: delegation.startedAt || session.startedAt || session.updatedAt,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
if (String(session.result || delegation.result || "").trim()) {
|
|
169
|
+
messages.push({
|
|
170
|
+
id: `${session.id}:result`, role: "assistant", text: session.result || delegation.result,
|
|
171
|
+
timestamp: session.completedAt || delegation.completedAt || session.updatedAt,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
return messages.sort((left, right) => Date.parse(left.timestamp || 0) - Date.parse(right.timestamp || 0));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function subagentCoordinationHtml(session) {
|
|
178
|
+
const events = subagentCoordinationEvents(session);
|
|
179
|
+
if (!events.length) return "";
|
|
147
180
|
const taskName = session.taskName || (session.delegation && session.delegation.taskName) || session.title;
|
|
148
181
|
const childPath = String(session.agentPath || "");
|
|
149
182
|
const endpointIsChild = (value) => {
|
|
150
183
|
const endpoint = String(value || "");
|
|
151
184
|
return endpoint === childPath || endpoint === session.id || agentPathTaskName(endpoint) === taskName;
|
|
152
185
|
};
|
|
153
|
-
const
|
|
154
|
-
const received = enriched.filter((event) => !event.fromChild && event.kind !== "started").length;
|
|
155
|
-
const answered = enriched.filter((event) => event.fromChild).length;
|
|
156
|
-
if (!events.length)
|
|
157
|
-
return '<div class="empty-state"><h3>메인 AI와 주고받은 기록이 없습니다</h3><p>세션 로그에서 배정·추가 지시·결과 반환 이벤트를 찾지 못했습니다.</p></div>';
|
|
158
|
-
const provider = providerInfo(session.provider);
|
|
159
|
-
const rows = enriched
|
|
186
|
+
const rows = events
|
|
160
187
|
.map((event) => {
|
|
161
|
-
const
|
|
162
|
-
const
|
|
163
|
-
const label =
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const preview = subagentTextPreview(text);
|
|
170
|
-
const eventLabel = `${event.label || event.kind}${event.assignmentSource === "parent-narration" ? " · 작업 시작 직전 메인 AI 설명" : ""}`;
|
|
171
|
-
return `<div class="chat-row ${role} subagent-dialog-row" data-subagent-communication="${esc(event.kind)}">
|
|
172
|
-
<span class="chat-avatar">${esc(avatar)}</span>
|
|
173
|
-
<div class="chat-bubble">
|
|
174
|
-
<div class="chat-bubble-head">
|
|
175
|
-
<b>${esc(label)}</b><span class="subagent-route">${esc(route)}</span>
|
|
176
|
-
<span>${esc(timeOnly(event.timestamp))}</span>
|
|
177
|
-
</div>
|
|
178
|
-
<small class="subagent-event-label">${esc(eventLabel)}</small>
|
|
179
|
-
<div class="chat-content subagent-message-preview${preview.truncated ? " is-truncated" : ""}"
|
|
180
|
-
data-subagent-message-preview data-truncated="${preview.truncated ? "true" : "false"}">
|
|
181
|
-
<p>${esc(preview.text)}</p>
|
|
182
|
-
</div>
|
|
183
|
-
</div>
|
|
184
|
-
</div>`;
|
|
188
|
+
const fromChild = event.kind === "result" || endpointIsChild(event.from);
|
|
189
|
+
const preview = subagentTextPreview(event.text);
|
|
190
|
+
const label = fromChild ? t("drawer.child_to_main", { child: session.agentName || taskName }) : t("drawer.main_to_child");
|
|
191
|
+
return `<article data-subagent-communication="${esc(event.kind)}">
|
|
192
|
+
<header><b>${esc(window.LoadToAgentI18n.observedText(event.label || event.kind))}</b><span>${esc(label)} · ${esc(timeOnly(event.timestamp))}</span></header>
|
|
193
|
+
<div class="chat-content plain subagent-message-preview${preview.truncated ? " is-truncated" : ""}"
|
|
194
|
+
data-subagent-message-preview data-truncated="${preview.truncated ? "true" : "false"}"><p>${esc(preview.text)}</p></div>
|
|
195
|
+
</article>`;
|
|
185
196
|
})
|
|
186
197
|
.join("");
|
|
187
|
-
return `<
|
|
188
|
-
<
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
198
|
+
return `<details class="chat-activities subagent-coordination" data-subagent-coordination-count="${events.length}" data-disclosure-key="${esc(`drawer:${session.id}:coordination`)}">
|
|
199
|
+
<summary>${esc(t("drawer.coordination_events", { count: events.length }))}</summary><div>${rows}</div>
|
|
200
|
+
</details>`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function subagentConversationHtml(session) {
|
|
204
|
+
const messages = subagentWorkMessages(session);
|
|
205
|
+
const conversationCount = messages.filter((message) => message.role === "user" || message.role === "assistant").length;
|
|
206
|
+
const workSession = { ...session, messages };
|
|
207
|
+
const sourceCopy = session.source === "collaboration-history"
|
|
208
|
+
? t("drawer.subagent_history_reconstructed")
|
|
209
|
+
: t("drawer.subagent_history_actual");
|
|
210
|
+
return `<section class="subagent-work-source" data-subagent-work-messages="${conversationCount}">
|
|
211
|
+
<b>${esc(t("drawer.subagent_work_history"))}</b><span>${esc(sourceCopy)}</span>
|
|
212
|
+
</section>${chatHtml(workSession, {
|
|
213
|
+
userLabel: t("drawer.assignment"),
|
|
214
|
+
assistantLabel: session.agentName || t("drawer.sub_ai"),
|
|
215
|
+
conversationLabel: t("drawer.work_history"),
|
|
216
|
+
})}${subagentCoordinationHtml(session)}`;
|
|
200
217
|
}
|
|
201
218
|
|
|
202
|
-
return { chatHtml, lifecycleHtml, tokensHtml, subagentCommunicationEvents,
|
|
219
|
+
return { chatHtml, lifecycleHtml, tokensHtml, subagentCommunicationEvents, subagentCoordinationEvents, subagentTextPreview, subagentConversationHtml };
|
|
203
220
|
};
|
|
@@ -3,27 +3,40 @@
|
|
|
3
3
|
window.LoadToAgentAppFactories = window.LoadToAgentAppFactories || {};
|
|
4
4
|
|
|
5
5
|
window.LoadToAgentAppFactories.createDrawerData = function createDrawerData(context = {}) {
|
|
6
|
+
const t = (key, params) => window.LoadToAgentI18n.t(key, params);
|
|
6
7
|
const { reportRecoverableError, state } = context;
|
|
8
|
+
const detailRequests = new Map();
|
|
9
|
+
let detailRequestGeneration = 0;
|
|
7
10
|
|
|
8
11
|
async function loadSessionDetail(id, force = false) {
|
|
9
12
|
if (!force && state.details.has(id)) return state.details.get(id);
|
|
13
|
+
if (!force && detailRequests.has(id)) return detailRequests.get(id).promise;
|
|
14
|
+
const generation = ++detailRequestGeneration;
|
|
10
15
|
state.detailErrors.delete(id);
|
|
11
16
|
state.detailLoadingIds.add(id);
|
|
12
17
|
context.renderDrawer();
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
const promise = (async () => {
|
|
19
|
+
try {
|
|
20
|
+
const detail = await window.loadtoagent.sessionDetail(id);
|
|
21
|
+
if (detailRequests.get(id)?.generation === generation && detail) state.details.set(id, detail);
|
|
22
|
+
return detail;
|
|
23
|
+
} catch (error) {
|
|
24
|
+
if (detailRequests.get(id)?.generation === generation)
|
|
25
|
+
state.detailErrors.set(id, window.LoadToAgentI18n.errorText(error, "drawer.history_failed"));
|
|
26
|
+
return null;
|
|
27
|
+
} finally {
|
|
28
|
+
if (detailRequests.get(id)?.generation === generation) {
|
|
29
|
+
detailRequests.delete(id);
|
|
30
|
+
state.detailLoadingIds.delete(id);
|
|
31
|
+
if (state.selectedId === id) {
|
|
32
|
+
state.drawerForceLatest = state.drawerTab === "chat";
|
|
33
|
+
context.renderDrawer();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
25
36
|
}
|
|
26
|
-
}
|
|
37
|
+
})();
|
|
38
|
+
detailRequests.set(id, { generation, promise });
|
|
39
|
+
return promise;
|
|
27
40
|
}
|
|
28
41
|
|
|
29
42
|
async function loadSubagentParentDetail(child) {
|
package/renderer/app-drawer.js
CHANGED
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
window.LoadToAgentAppFactories = window.LoadToAgentAppFactories || {};
|
|
4
4
|
|
|
5
5
|
window.LoadToAgentAppFactories.createDrawer = function createDrawer(context = {}) {
|
|
6
|
+
const t = (key, params) => window.LoadToAgentI18n.t(key, params);
|
|
6
7
|
const {
|
|
7
|
-
$, $$, esc, state, motionPreference, motionState, STATUS, markGuideStep, rememberDialogTrigger, restoreDialogTrigger,
|
|
8
|
-
providerInfo, isLiveSession, subagentWorkState, subagentWorkLabel, isProjectlessSession, sessionWorkspaceLabel,
|
|
8
|
+
$, $$, esc, state, motionPreference, motionState, STATUS, markGuideStep, rememberDialogTrigger, restoreDialogTrigger, setDialogOpenState,
|
|
9
|
+
providerInfo, isLiveSession, subagentWorkState, subagentWorkLabel, isProjectlessSession, sessionOriginPath, sessionWorkspaceLabel,
|
|
9
10
|
agentResumeSupport, originAppInfo, selectedSession, snapshotSession, loadSessionDetail, loadSubagentParentDetail,
|
|
10
|
-
chatHtml, lifecycleHtml, tokensHtml,
|
|
11
|
+
chatHtml, lifecycleHtml, tokensHtml, outcomeHtml, subagentCoordinationEvents, subagentConversationHtml,
|
|
12
|
+
rememberDisclosureStates = () => {}, restoreDisclosureStates = () => {},
|
|
11
13
|
} = context;
|
|
12
14
|
|
|
13
15
|
function openDrawer(id) {
|
|
@@ -15,17 +17,16 @@ window.LoadToAgentAppFactories.createDrawer = function createDrawer(context = {}
|
|
|
15
17
|
markGuideStep("detail");
|
|
16
18
|
state.selectedId = id;
|
|
17
19
|
state.drawerMode = "session";
|
|
18
|
-
state.drawerTab = "
|
|
20
|
+
state.drawerTab = "summary";
|
|
19
21
|
state.drawerForceLatest = true;
|
|
20
22
|
clearTimeout(motionState.drawerTimer);
|
|
21
23
|
$("#drawerBackdrop").classList.remove("hidden");
|
|
22
24
|
$("#drawerBackdrop").classList.remove("closing");
|
|
23
25
|
$("#detailDrawer").classList.add("open");
|
|
24
|
-
$("#detailDrawer")
|
|
25
|
-
$("#detailDrawer").setAttribute("aria-hidden", "false");
|
|
26
|
+
setDialogOpenState($("#detailDrawer"), true);
|
|
26
27
|
renderDrawer();
|
|
27
28
|
loadSessionDetail(id, true);
|
|
28
|
-
setTimeout(() => $("#closeDrawerBtn").focus(), 0);
|
|
29
|
+
setTimeout(() => $("#closeDrawerBtn").focus({ preventScroll: true }), 0);
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
function openSubagentConversation(id) {
|
|
@@ -41,25 +42,26 @@ window.LoadToAgentAppFactories.createDrawer = function createDrawer(context = {}
|
|
|
41
42
|
$("#drawerBackdrop").classList.remove("hidden");
|
|
42
43
|
$("#drawerBackdrop").classList.remove("closing");
|
|
43
44
|
$("#detailDrawer").classList.add("open");
|
|
44
|
-
$("#detailDrawer")
|
|
45
|
-
$("#detailDrawer").setAttribute("aria-hidden", "false");
|
|
45
|
+
setDialogOpenState($("#detailDrawer"), true);
|
|
46
46
|
renderDrawer();
|
|
47
|
+
loadSessionDetail(id);
|
|
47
48
|
loadSubagentParentDetail(child);
|
|
48
|
-
setTimeout(() => $("#closeDrawerBtn").focus(), 0);
|
|
49
|
+
setTimeout(() => $("#closeDrawerBtn").focus({ preventScroll: true }), 0);
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
function closeDrawer(restoreFocus = true) {
|
|
52
53
|
if (!$("#detailDrawer").classList.contains("open")) return;
|
|
54
|
+
const drawerGeneration = motionState.dialogGeneration;
|
|
53
55
|
$("#detailDrawer").classList.remove("open");
|
|
54
|
-
$("#detailDrawer")
|
|
55
|
-
$("#detailDrawer").setAttribute("inert", "");
|
|
56
|
+
setDialogOpenState($("#detailDrawer"), false);
|
|
56
57
|
$("#drawerBackdrop").classList.add("closing");
|
|
57
58
|
clearTimeout(motionState.drawerTimer);
|
|
58
59
|
motionState.drawerTimer = setTimeout(
|
|
59
60
|
() => {
|
|
60
61
|
$("#drawerBackdrop").classList.add("hidden");
|
|
61
62
|
$("#drawerBackdrop").classList.remove("closing");
|
|
62
|
-
if (
|
|
63
|
+
if (drawerGeneration !== motionState.dialogGeneration) return;
|
|
64
|
+
if (restoreFocus) restoreDialogTrigger(drawerGeneration);
|
|
63
65
|
else motionState.activeDialogTrigger = null;
|
|
64
66
|
},
|
|
65
67
|
motionPreference.matches ? 0 : 260,
|
|
@@ -71,60 +73,70 @@ window.LoadToAgentAppFactories.createDrawer = function createDrawer(context = {}
|
|
|
71
73
|
if (!session) return closeDrawer();
|
|
72
74
|
const provider = providerInfo(session.provider);
|
|
73
75
|
const subagentMode = state.drawerMode === "subagent" && Boolean(session.parentId);
|
|
74
|
-
const detailLoading =
|
|
76
|
+
const detailLoading = state.detailLoadingIds.has(state.selectedId);
|
|
75
77
|
$("#detailDrawer").style.setProperty("--drawer-provider", provider.accent);
|
|
76
78
|
$("#drawerProviderMark").style.setProperty("--provider", provider.accent);
|
|
77
79
|
$("#drawerProviderMark").textContent = provider.mark;
|
|
78
80
|
$("#drawerProvider").textContent = subagentMode
|
|
79
|
-
?
|
|
81
|
+
? t("drawer.subagent_title", { name: session.agentName || provider.label })
|
|
80
82
|
: `${provider.company} · ${STATUS[session.status] || session.status}`;
|
|
81
|
-
|
|
83
|
+
const drawerTitle = subagentMode ? session.taskName || (session.delegation && session.delegation.taskName) || session.title : session.title;
|
|
84
|
+
$("#drawerTitle").textContent = drawerTitle;
|
|
85
|
+
$("#drawerTitle").title = drawerTitle;
|
|
82
86
|
const stopping = session.runId && state.stopRequests.has(session.runId);
|
|
83
87
|
const stop =
|
|
84
88
|
session.runId && (session.status === "running" || session.status === "starting")
|
|
85
|
-
? `<button class="meta-chip stop-run" data-stop-run="${esc(session.runId)}"
|
|
89
|
+
? `<button type="button" class="meta-chip stop-run" data-stop-run="${esc(session.runId)}"
|
|
86
90
|
${stopping ? 'disabled aria-busy="true"' : ""}>
|
|
87
|
-
${stopping ? "
|
|
91
|
+
${esc(t(stopping ? "drawer.stop_requested" : "drawer.stop_run"))}</button>`
|
|
88
92
|
: "";
|
|
89
93
|
const runtime = session.runtimePresence || [];
|
|
90
94
|
const resume =
|
|
91
95
|
!isLiveSession(session) && agentResumeSupport(session).supported
|
|
92
|
-
? `<button class="meta-chip resume-agent" data-resume-agent="${esc(session.id)}">▶
|
|
93
|
-
<b>${originAppInfo(session) ? "
|
|
96
|
+
? `<button type="button" class="meta-chip resume-agent" data-resume-agent="${esc(session.id)}">▶
|
|
97
|
+
<b>${esc(t(originAppInfo(session) ? "drawer.continue_background_terminal" : "drawer.resume_in_terminal"))}</b>
|
|
94
98
|
</button>`
|
|
95
99
|
: "";
|
|
96
|
-
const communicationCount = subagentMode ?
|
|
100
|
+
const communicationCount = subagentMode ? subagentCoordinationEvents(session).length : 0;
|
|
101
|
+
const subagentMessageCount = subagentMode
|
|
102
|
+
? (session.messages || []).filter((message) => message.role === "user" || message.role === "assistant").length
|
|
103
|
+
: 0;
|
|
104
|
+
const taskId = String(session.externalId || session.id || "");
|
|
105
|
+
const copyTask = taskId
|
|
106
|
+
? `<button type="button" class="meta-chip meta-copy" data-copy-text="${esc(taskId)}" aria-label="${esc(t("quality.copy_task_id"))}">${esc(t("quality.task_id"))} <b>${esc(taskId.slice(0, 12))}</b><span aria-hidden="true">⧉</span></button>`
|
|
107
|
+
: "";
|
|
108
|
+
const originPath = sessionOriginPath(session);
|
|
109
|
+
const copyWorkspace = !isProjectlessSession(session) && originPath
|
|
110
|
+
? `<button type="button" class="meta-chip meta-copy origin-project-meta" data-copy-text="${esc(originPath)}" aria-label="${esc(t("quality.copy_workspace"))}">${esc(t("project.origin"))} <b>${esc(sessionWorkspaceLabel(session))}</b><span aria-hidden="true">⧉</span></button>`
|
|
111
|
+
: "";
|
|
97
112
|
$("#drawerMeta").innerHTML = subagentMode
|
|
98
113
|
? `<span class="meta-chip work-state ${subagentWorkState(session)}">
|
|
99
114
|
<b>${esc(subagentWorkLabel(session))}</b>
|
|
100
115
|
</span>
|
|
101
|
-
<span class="meta-chip"
|
|
116
|
+
<span class="meta-chip">${esc(t("drawer.model"))} <b>${esc(session.model || t("drawer.unknown"))}</b>
|
|
102
117
|
</span>
|
|
103
|
-
<span class="meta-chip"
|
|
104
|
-
</span>${resume}`
|
|
105
|
-
: `<span class="meta-chip">사용 모델 <b>${esc(session.model || "정보 없음")}</b>
|
|
118
|
+
<span class="meta-chip">${esc(t("drawer.work_history"))} <b>${esc(t("drawer.event_count", { count: subagentMessageCount }))}</b>
|
|
106
119
|
</span>
|
|
107
|
-
<span class="meta-chip"
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
</b>
|
|
120
|
+
<span class="meta-chip">${esc(t("drawer.main_instructions_results"))} <b>${esc(t("drawer.event_count", { count: communicationCount }))}</b>
|
|
121
|
+
</span>${copyTask}${copyWorkspace}${resume}`
|
|
122
|
+
: `<span class="meta-chip">${esc(t("drawer.model"))} <b>${esc(session.model || t("drawer.unknown"))}</b>
|
|
111
123
|
</span>
|
|
112
|
-
|
|
113
|
-
|
|
124
|
+
${copyWorkspace || `<span class="meta-chip origin-project-meta">${esc(t("project.origin"))} <b>${esc(sessionWorkspaceLabel(session))}</b></span>`}
|
|
125
|
+
${copyTask}${
|
|
114
126
|
session.parentId
|
|
115
|
-
? `<span class="meta-chip">⑂ <b
|
|
127
|
+
? `<span class="meta-chip">⑂ <b>${esc(t("drawer.helper_ai"))}</b>
|
|
116
128
|
</span>`
|
|
117
129
|
: ""
|
|
118
130
|
}${
|
|
119
131
|
runtime.length
|
|
120
|
-
? `<span class="meta-chip runtime-meta">● <b
|
|
132
|
+
? `<span class="meta-chip runtime-meta">● <b>${esc(t("session.running_programs", { count: runtime.length }))}</b>
|
|
121
133
|
</span>`
|
|
122
134
|
: ""
|
|
123
135
|
}${resume}${stop}`;
|
|
124
136
|
$$(".drawer-tab").forEach((tab) => {
|
|
125
137
|
const hidden = subagentMode && tab.dataset.tab !== "chat";
|
|
126
138
|
tab.classList.toggle("hidden", hidden);
|
|
127
|
-
if (tab.dataset.tab === "chat") tab.textContent = subagentMode ? "
|
|
139
|
+
if (tab.dataset.tab === "chat") tab.textContent = subagentMode ? t("drawer.work_content") : t("ui.conversation");
|
|
128
140
|
const active = tab.dataset.tab === state.drawerTab;
|
|
129
141
|
tab.classList.toggle("active", active);
|
|
130
142
|
tab.setAttribute("aria-selected", active ? "true" : "false");
|
|
@@ -133,8 +145,9 @@ window.LoadToAgentAppFactories.createDrawer = function createDrawer(context = {}
|
|
|
133
145
|
const activeTab = $(`.drawer-tab[data-tab="${state.drawerTab}"]`);
|
|
134
146
|
if (activeTab) $("#drawerContent").setAttribute("aria-labelledby", activeTab.id);
|
|
135
147
|
const content = $("#drawerContent");
|
|
148
|
+
rememberDisclosureStates(content);
|
|
136
149
|
const previousTop = content.scrollTop;
|
|
137
|
-
const
|
|
150
|
+
const wasAtBottom = window.LoadToAgentRendererUtils.isScrolledToEnd(content);
|
|
138
151
|
const renderKey = `${state.drawerMode}:${state.selectedId}:${state.drawerTab}:${detailLoading ? "loading" : "ready"}`;
|
|
139
152
|
const previousRenderKey = motionState.drawerRenderKey;
|
|
140
153
|
const shouldAnimateContent = previousRenderKey !== renderKey;
|
|
@@ -143,40 +156,51 @@ window.LoadToAgentAppFactories.createDrawer = function createDrawer(context = {}
|
|
|
143
156
|
motionState.drawerTab = state.drawerTab;
|
|
144
157
|
const detailError = state.detailErrors.get(state.selectedId);
|
|
145
158
|
content.innerHTML = detailLoading
|
|
146
|
-
?
|
|
147
|
-
: detailError
|
|
159
|
+
? `<div class="drawer-loading"><span></span><b>${esc(t("drawer.loading_history"))}</b><small>${esc(t("drawer.loading_history_help"))}</small></div>`
|
|
160
|
+
: detailError
|
|
148
161
|
? `<div class="drawer-error">
|
|
149
|
-
<b
|
|
162
|
+
<b>${esc(t("drawer.history_failed"))}</b>
|
|
150
163
|
<span>${esc(detailError)}</span>
|
|
151
|
-
<button type="button" data-retry-detail="${esc(state.selectedId)}"
|
|
164
|
+
<button type="button" data-retry-detail="${esc(state.selectedId)}">${esc(t("drawer.retry"))}</button>
|
|
152
165
|
</div>`
|
|
153
166
|
: subagentMode
|
|
154
167
|
? subagentConversationHtml(session)
|
|
155
|
-
: state.drawerTab === "
|
|
168
|
+
: state.drawerTab === "summary"
|
|
169
|
+
? outcomeHtml(session)
|
|
170
|
+
: state.drawerTab === "chat"
|
|
156
171
|
? chatHtml(session)
|
|
157
172
|
: state.drawerTab === "lifecycle"
|
|
158
173
|
? lifecycleHtml(session)
|
|
159
174
|
: tokensHtml(session);
|
|
175
|
+
restoreDisclosureStates(content);
|
|
160
176
|
content.classList.toggle("motion-content-in", shouldAnimateContent && !motionPreference.matches);
|
|
161
177
|
clearTimeout(motionState.drawerContentTimer);
|
|
162
178
|
if (shouldAnimateContent)
|
|
163
179
|
motionState.drawerContentTimer = setTimeout(() => content.classList.remove("motion-content-in"), motionPreference.matches ? 0 : 520);
|
|
164
|
-
if (!detailLoading)
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if (state.drawerTab === "chat" && forceLatest) {
|
|
168
|
-
const rows = [...content.querySelectorAll(".chat-row")];
|
|
169
|
-
const latest = rows[rows.length - 1];
|
|
170
|
-
if (latest && latest.offsetHeight > content.clientHeight - 90) {
|
|
171
|
-
const contentTop = content.getBoundingClientRect().top;
|
|
172
|
-
const stickyHeight = content.querySelector(".chat-history-head")?.getBoundingClientRect().height || 0;
|
|
173
|
-
content.scrollTop = Math.max(0, content.scrollTop + latest.getBoundingClientRect().top - contentTop - stickyHeight - 12);
|
|
174
|
-
} else content.scrollTop = content.scrollHeight;
|
|
175
|
-
} else if (tabChanged) content.scrollTop = 0;
|
|
176
|
-
else if (state.drawerTab === "chat" && wasNearBottom) content.scrollTop = content.scrollHeight;
|
|
177
|
-
else content.scrollTop = Math.min(previousTop, Math.max(0, content.scrollHeight - content.clientHeight));
|
|
180
|
+
if (!detailLoading) {
|
|
181
|
+
if (tabChanged) {
|
|
182
|
+
content.scrollTop = 0;
|
|
178
183
|
state.drawerForceLatest = false;
|
|
179
|
-
}
|
|
184
|
+
} else {
|
|
185
|
+
const scrollGeneration = (motionState.drawerScrollGeneration || 0) + 1;
|
|
186
|
+
motionState.drawerScrollGeneration = scrollGeneration;
|
|
187
|
+
requestAnimationFrame(() => {
|
|
188
|
+
if (motionState.drawerScrollGeneration !== scrollGeneration) return;
|
|
189
|
+
const forceLatest = state.drawerForceLatest;
|
|
190
|
+
if (state.drawerTab === "chat" && forceLatest) {
|
|
191
|
+
const rows = [...content.querySelectorAll(".chat-row")];
|
|
192
|
+
const latest = rows[rows.length - 1];
|
|
193
|
+
if (latest && latest.offsetHeight > content.clientHeight - 90) {
|
|
194
|
+
const contentTop = content.getBoundingClientRect().top;
|
|
195
|
+
const stickyHeight = content.querySelector(".chat-history-head")?.getBoundingClientRect().height || 0;
|
|
196
|
+
content.scrollTop = Math.max(0, content.scrollTop + latest.getBoundingClientRect().top - contentTop - stickyHeight - 12);
|
|
197
|
+
} else content.scrollTop = content.scrollHeight;
|
|
198
|
+
} else if (state.drawerTab === "chat" && wasAtBottom) content.scrollTop = content.scrollHeight;
|
|
199
|
+
else content.scrollTop = Math.min(previousTop, Math.max(0, content.scrollHeight - content.clientHeight));
|
|
200
|
+
state.drawerForceLatest = false;
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
180
204
|
}
|
|
181
205
|
|
|
182
206
|
return { openDrawer, openSubagentConversation, closeDrawer, renderDrawer };
|