loadtoagent 1.1.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 +135 -38
- package/package.json +13 -4
- package/preload.js +6 -0
- package/renderer/app-agent-actions.js +125 -53
- package/renderer/app-bootstrap.js +54 -19
- package/renderer/app-dashboard.js +190 -55
- package/renderer/app-drawer-content.js +48 -44
- package/renderer/app-drawer-data.js +26 -13
- package/renderer/app-drawer.js +72 -54
- package/renderer/app-events-dialogs.js +146 -37
- package/renderer/app-events-filters.js +172 -13
- package/renderer/app-events-navigation.js +77 -30
- package/renderer/app-events-sessions.js +156 -9
- package/renderer/app-events.js +2 -1
- package/renderer/app-graph-model.js +20 -38
- package/renderer/app-graph-orchestration.js +15 -13
- package/renderer/app-graph-view.js +228 -113
- 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 +52 -44
- package/renderer/app.js +153 -75
- package/renderer/i18n-messages.js +833 -16
- package/renderer/i18n.js +104 -0
- package/renderer/index.html +145 -59
- package/renderer/shared.js +17 -0
- package/renderer/styles-agent-map.css +6 -0
- package/renderer/styles-cards.css +26 -0
- package/renderer/styles-components.css +86 -9
- package/renderer/styles-management.css +355 -0
- package/renderer/styles-overlays.css +8 -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 +22 -14
- package/renderer/styles-responsive-shell.css +44 -48
- package/renderer/styles-responsive-workflows.css +37 -3
- 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-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 +165 -77
- package/renderer/terminal.js +341 -34
- 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 +14 -10
- 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 +80 -22
- 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/updateInstaller.js +175 -0
|
@@ -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, subagentCoordinationEvents, subagentConversationHtml,
|
|
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,26 +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
47
|
loadSessionDetail(id);
|
|
48
48
|
loadSubagentParentDetail(child);
|
|
49
|
-
setTimeout(() => $("#closeDrawerBtn").focus(), 0);
|
|
49
|
+
setTimeout(() => $("#closeDrawerBtn").focus({ preventScroll: true }), 0);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
function closeDrawer(restoreFocus = true) {
|
|
53
53
|
if (!$("#detailDrawer").classList.contains("open")) return;
|
|
54
|
+
const drawerGeneration = motionState.dialogGeneration;
|
|
54
55
|
$("#detailDrawer").classList.remove("open");
|
|
55
|
-
$("#detailDrawer")
|
|
56
|
-
$("#detailDrawer").setAttribute("inert", "");
|
|
56
|
+
setDialogOpenState($("#detailDrawer"), false);
|
|
57
57
|
$("#drawerBackdrop").classList.add("closing");
|
|
58
58
|
clearTimeout(motionState.drawerTimer);
|
|
59
59
|
motionState.drawerTimer = setTimeout(
|
|
60
60
|
() => {
|
|
61
61
|
$("#drawerBackdrop").classList.add("hidden");
|
|
62
62
|
$("#drawerBackdrop").classList.remove("closing");
|
|
63
|
-
if (
|
|
63
|
+
if (drawerGeneration !== motionState.dialogGeneration) return;
|
|
64
|
+
if (restoreFocus) restoreDialogTrigger(drawerGeneration);
|
|
64
65
|
else motionState.activeDialogTrigger = null;
|
|
65
66
|
},
|
|
66
67
|
motionPreference.matches ? 0 : 260,
|
|
@@ -77,60 +78,65 @@ window.LoadToAgentAppFactories.createDrawer = function createDrawer(context = {}
|
|
|
77
78
|
$("#drawerProviderMark").style.setProperty("--provider", provider.accent);
|
|
78
79
|
$("#drawerProviderMark").textContent = provider.mark;
|
|
79
80
|
$("#drawerProvider").textContent = subagentMode
|
|
80
|
-
?
|
|
81
|
+
? t("drawer.subagent_title", { name: session.agentName || provider.label })
|
|
81
82
|
: `${provider.company} · ${STATUS[session.status] || session.status}`;
|
|
82
|
-
|
|
83
|
+
const drawerTitle = subagentMode ? session.taskName || (session.delegation && session.delegation.taskName) || session.title : session.title;
|
|
84
|
+
$("#drawerTitle").textContent = drawerTitle;
|
|
85
|
+
$("#drawerTitle").title = drawerTitle;
|
|
83
86
|
const stopping = session.runId && state.stopRequests.has(session.runId);
|
|
84
87
|
const stop =
|
|
85
88
|
session.runId && (session.status === "running" || session.status === "starting")
|
|
86
|
-
? `<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)}"
|
|
87
90
|
${stopping ? 'disabled aria-busy="true"' : ""}>
|
|
88
|
-
${stopping ? "
|
|
91
|
+
${esc(t(stopping ? "drawer.stop_requested" : "drawer.stop_run"))}</button>`
|
|
89
92
|
: "";
|
|
90
93
|
const runtime = session.runtimePresence || [];
|
|
91
94
|
const resume =
|
|
92
95
|
!isLiveSession(session) && agentResumeSupport(session).supported
|
|
93
|
-
? `<button class="meta-chip resume-agent" data-resume-agent="${esc(session.id)}">▶
|
|
94
|
-
<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>
|
|
95
98
|
</button>`
|
|
96
99
|
: "";
|
|
97
100
|
const communicationCount = subagentMode ? subagentCoordinationEvents(session).length : 0;
|
|
98
101
|
const subagentMessageCount = subagentMode
|
|
99
102
|
? (session.messages || []).filter((message) => message.role === "user" || message.role === "assistant").length
|
|
100
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
|
+
: "";
|
|
101
112
|
$("#drawerMeta").innerHTML = subagentMode
|
|
102
113
|
? `<span class="meta-chip work-state ${subagentWorkState(session)}">
|
|
103
114
|
<b>${esc(subagentWorkLabel(session))}</b>
|
|
104
115
|
</span>
|
|
105
|
-
<span class="meta-chip"
|
|
106
|
-
</span>
|
|
107
|
-
<span class="meta-chip">작업 기록 <b>${subagentMessageCount}건</b>
|
|
116
|
+
<span class="meta-chip">${esc(t("drawer.model"))} <b>${esc(session.model || t("drawer.unknown"))}</b>
|
|
108
117
|
</span>
|
|
109
|
-
<span class="meta-chip"
|
|
110
|
-
</span>${resume}`
|
|
111
|
-
: `<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>
|
|
112
119
|
</span>
|
|
113
|
-
<span class="meta-chip"
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
</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>
|
|
117
123
|
</span>
|
|
118
|
-
|
|
119
|
-
|
|
124
|
+
${copyWorkspace || `<span class="meta-chip origin-project-meta">${esc(t("project.origin"))} <b>${esc(sessionWorkspaceLabel(session))}</b></span>`}
|
|
125
|
+
${copyTask}${
|
|
120
126
|
session.parentId
|
|
121
|
-
? `<span class="meta-chip">⑂ <b
|
|
127
|
+
? `<span class="meta-chip">⑂ <b>${esc(t("drawer.helper_ai"))}</b>
|
|
122
128
|
</span>`
|
|
123
129
|
: ""
|
|
124
130
|
}${
|
|
125
131
|
runtime.length
|
|
126
|
-
? `<span class="meta-chip runtime-meta">● <b
|
|
132
|
+
? `<span class="meta-chip runtime-meta">● <b>${esc(t("session.running_programs", { count: runtime.length }))}</b>
|
|
127
133
|
</span>`
|
|
128
134
|
: ""
|
|
129
135
|
}${resume}${stop}`;
|
|
130
136
|
$$(".drawer-tab").forEach((tab) => {
|
|
131
137
|
const hidden = subagentMode && tab.dataset.tab !== "chat";
|
|
132
138
|
tab.classList.toggle("hidden", hidden);
|
|
133
|
-
if (tab.dataset.tab === "chat") tab.textContent = subagentMode ? "
|
|
139
|
+
if (tab.dataset.tab === "chat") tab.textContent = subagentMode ? t("drawer.work_content") : t("ui.conversation");
|
|
134
140
|
const active = tab.dataset.tab === state.drawerTab;
|
|
135
141
|
tab.classList.toggle("active", active);
|
|
136
142
|
tab.setAttribute("aria-selected", active ? "true" : "false");
|
|
@@ -139,8 +145,9 @@ window.LoadToAgentAppFactories.createDrawer = function createDrawer(context = {}
|
|
|
139
145
|
const activeTab = $(`.drawer-tab[data-tab="${state.drawerTab}"]`);
|
|
140
146
|
if (activeTab) $("#drawerContent").setAttribute("aria-labelledby", activeTab.id);
|
|
141
147
|
const content = $("#drawerContent");
|
|
148
|
+
rememberDisclosureStates(content);
|
|
142
149
|
const previousTop = content.scrollTop;
|
|
143
|
-
const
|
|
150
|
+
const wasAtBottom = window.LoadToAgentRendererUtils.isScrolledToEnd(content);
|
|
144
151
|
const renderKey = `${state.drawerMode}:${state.selectedId}:${state.drawerTab}:${detailLoading ? "loading" : "ready"}`;
|
|
145
152
|
const previousRenderKey = motionState.drawerRenderKey;
|
|
146
153
|
const shouldAnimateContent = previousRenderKey !== renderKey;
|
|
@@ -149,40 +156,51 @@ window.LoadToAgentAppFactories.createDrawer = function createDrawer(context = {}
|
|
|
149
156
|
motionState.drawerTab = state.drawerTab;
|
|
150
157
|
const detailError = state.detailErrors.get(state.selectedId);
|
|
151
158
|
content.innerHTML = detailLoading
|
|
152
|
-
?
|
|
159
|
+
? `<div class="drawer-loading"><span></span><b>${esc(t("drawer.loading_history"))}</b><small>${esc(t("drawer.loading_history_help"))}</small></div>`
|
|
153
160
|
: detailError
|
|
154
161
|
? `<div class="drawer-error">
|
|
155
|
-
<b
|
|
162
|
+
<b>${esc(t("drawer.history_failed"))}</b>
|
|
156
163
|
<span>${esc(detailError)}</span>
|
|
157
|
-
<button type="button" data-retry-detail="${esc(state.selectedId)}"
|
|
164
|
+
<button type="button" data-retry-detail="${esc(state.selectedId)}">${esc(t("drawer.retry"))}</button>
|
|
158
165
|
</div>`
|
|
159
166
|
: subagentMode
|
|
160
167
|
? subagentConversationHtml(session)
|
|
161
|
-
: state.drawerTab === "
|
|
168
|
+
: state.drawerTab === "summary"
|
|
169
|
+
? outcomeHtml(session)
|
|
170
|
+
: state.drawerTab === "chat"
|
|
162
171
|
? chatHtml(session)
|
|
163
172
|
: state.drawerTab === "lifecycle"
|
|
164
173
|
? lifecycleHtml(session)
|
|
165
174
|
: tokensHtml(session);
|
|
175
|
+
restoreDisclosureStates(content);
|
|
166
176
|
content.classList.toggle("motion-content-in", shouldAnimateContent && !motionPreference.matches);
|
|
167
177
|
clearTimeout(motionState.drawerContentTimer);
|
|
168
178
|
if (shouldAnimateContent)
|
|
169
179
|
motionState.drawerContentTimer = setTimeout(() => content.classList.remove("motion-content-in"), motionPreference.matches ? 0 : 520);
|
|
170
|
-
if (!detailLoading)
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if (state.drawerTab === "chat" && forceLatest) {
|
|
174
|
-
const rows = [...content.querySelectorAll(".chat-row")];
|
|
175
|
-
const latest = rows[rows.length - 1];
|
|
176
|
-
if (latest && latest.offsetHeight > content.clientHeight - 90) {
|
|
177
|
-
const contentTop = content.getBoundingClientRect().top;
|
|
178
|
-
const stickyHeight = content.querySelector(".chat-history-head")?.getBoundingClientRect().height || 0;
|
|
179
|
-
content.scrollTop = Math.max(0, content.scrollTop + latest.getBoundingClientRect().top - contentTop - stickyHeight - 12);
|
|
180
|
-
} else content.scrollTop = content.scrollHeight;
|
|
181
|
-
} else if (tabChanged) content.scrollTop = 0;
|
|
182
|
-
else if (state.drawerTab === "chat" && wasNearBottom) content.scrollTop = content.scrollHeight;
|
|
183
|
-
else content.scrollTop = Math.min(previousTop, Math.max(0, content.scrollHeight - content.clientHeight));
|
|
180
|
+
if (!detailLoading) {
|
|
181
|
+
if (tabChanged) {
|
|
182
|
+
content.scrollTop = 0;
|
|
184
183
|
state.drawerForceLatest = false;
|
|
185
|
-
}
|
|
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
|
+
}
|
|
186
204
|
}
|
|
187
205
|
|
|
188
206
|
return { openDrawer, openSubagentConversation, closeDrawer, renderDrawer };
|
|
@@ -3,18 +3,22 @@
|
|
|
3
3
|
window.LoadToAgentAppFactories = window.LoadToAgentAppFactories || {};
|
|
4
4
|
|
|
5
5
|
window.LoadToAgentAppFactories.createDialogEventBindings = function createDialogEventBindings(context = {}) {
|
|
6
|
+
const t = (key, params) => window.LoadToAgentI18n.t(key, params);
|
|
6
7
|
const {
|
|
7
|
-
$, $$, state, providerInfo, renderProviderRail, scheduleAgentWorkflowConnections, resumeAgentTerminal, loadSessionDetail,
|
|
8
|
+
$, $$, state, providerInfo, visibleProviders = () => state.providers, renderProviderRail, scheduleAgentWorkflowConnections, resumeAgentTerminal, loadSessionDetail,
|
|
8
9
|
closeDrawer, renderDrawer, providerPickerHtml, syncRunComposer, openRunModal, closeRunModal, toast, performUiAction,
|
|
9
|
-
handleRun, trapDialogFocus,
|
|
10
|
+
handleRun, trapDialogFocus, currentDialog, selectView, saveRunDraft = () => {}, safeBackdrop = null,
|
|
11
|
+
copyText = async () => false,
|
|
12
|
+
dispatchAgentCommand, controlManagedRun, quickRespond, prepareReassignment,
|
|
10
13
|
} = context;
|
|
11
14
|
|
|
12
15
|
function bindRunComposerEvents() {
|
|
13
16
|
$("#newRunBtn").addEventListener("click", openRunModal);
|
|
14
17
|
$$("[data-open-run]").forEach((button) => button.addEventListener("click", openRunModal));
|
|
15
|
-
$("#closeRunModalBtn").addEventListener("click", closeRunModal);
|
|
16
|
-
$("#cancelRunBtn").addEventListener("click", closeRunModal);
|
|
17
|
-
$("#runModal")
|
|
18
|
+
$("#closeRunModalBtn").addEventListener("click", () => closeRunModal());
|
|
19
|
+
$("#cancelRunBtn").addEventListener("click", () => closeRunModal());
|
|
20
|
+
if (safeBackdrop) safeBackdrop($("#runModal"), () => closeRunModal());
|
|
21
|
+
else $("#runModal").addEventListener("click", (event) => {
|
|
18
22
|
if (event.target === $("#runModal")) closeRunModal();
|
|
19
23
|
});
|
|
20
24
|
$("#runProviderPicker").addEventListener("click", (event) => {
|
|
@@ -23,6 +27,20 @@ window.LoadToAgentAppFactories.createDialogEventBindings = function createDialog
|
|
|
23
27
|
state.runProvider = button.dataset.runProvider;
|
|
24
28
|
$("#runProviderPicker").innerHTML = providerPickerHtml();
|
|
25
29
|
syncRunComposer();
|
|
30
|
+
saveRunDraft();
|
|
31
|
+
});
|
|
32
|
+
$("#runProviderPicker").addEventListener("keydown", (event) => {
|
|
33
|
+
if (!["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End"].includes(event.key)) return;
|
|
34
|
+
const options = $$("#runProviderPicker [data-run-provider]:not(:disabled)");
|
|
35
|
+
const current = Math.max(0, options.indexOf(event.target.closest("[data-run-provider]")));
|
|
36
|
+
const next = event.key === "Home"
|
|
37
|
+
? 0
|
|
38
|
+
: event.key === "End"
|
|
39
|
+
? options.length - 1
|
|
40
|
+
: (current + (["ArrowRight", "ArrowDown"].includes(event.key) ? 1 : -1) + options.length) % options.length;
|
|
41
|
+
event.preventDefault();
|
|
42
|
+
options[next]?.click();
|
|
43
|
+
requestAnimationFrame(() => $("#runProviderPicker").querySelector(`[data-run-provider="${CSS.escape(state.runProvider)}"]`)?.focus());
|
|
26
44
|
});
|
|
27
45
|
$("#runProviderHelp").addEventListener("click", async (event) => {
|
|
28
46
|
const docs = event.target.closest("[data-provider-docs]");
|
|
@@ -31,31 +49,33 @@ window.LoadToAgentAppFactories.createDialogEventBindings = function createDialog
|
|
|
31
49
|
if (provider.docs)
|
|
32
50
|
await performUiAction(async () => {
|
|
33
51
|
const result = await window.loadtoagent.openExternal(provider.docs);
|
|
34
|
-
if (!result || result.ok === false) throw new Error("
|
|
35
|
-
}, "
|
|
52
|
+
if (!result || result.ok === false) throw new Error(t("run.docs_open_failed"));
|
|
53
|
+
}, t("run.docs_open_failed"), docs);
|
|
36
54
|
return;
|
|
37
55
|
}
|
|
38
|
-
|
|
39
|
-
|
|
56
|
+
const recheck = event.target.closest("[data-provider-recheck]");
|
|
57
|
+
if (recheck) {
|
|
58
|
+
const nextAvailability = await performUiAction(() => window.loadtoagent.probeProviders(), t("run.cli_check_failed"), recheck);
|
|
40
59
|
if (!nextAvailability) return;
|
|
41
60
|
state.availability = nextAvailability;
|
|
42
|
-
const installed =
|
|
61
|
+
const installed = visibleProviders().find((provider) => state.availability[provider.id]);
|
|
43
62
|
if (installed) state.runProvider = installed.id;
|
|
44
63
|
$("#runProviderPicker").innerHTML = providerPickerHtml();
|
|
45
64
|
renderProviderRail();
|
|
46
65
|
syncRunComposer();
|
|
47
|
-
toast(installed ?
|
|
66
|
+
toast(installed ? t("run.cli_ready", { provider: installed.label }) : t("run.cli_not_found"));
|
|
48
67
|
}
|
|
49
68
|
});
|
|
50
69
|
$("#runPrompt").addEventListener("input", syncRunComposer);
|
|
51
70
|
$(".run-prompt-examples").addEventListener("click", (event) => {
|
|
52
|
-
const example = event.target.closest("[data-run-prompt-
|
|
71
|
+
const example = event.target.closest("[data-run-prompt-key]");
|
|
53
72
|
if (!example) return;
|
|
54
73
|
const input = $("#runPrompt");
|
|
55
|
-
const text = example.dataset.
|
|
74
|
+
const text = t(example.dataset.runPromptKey);
|
|
56
75
|
if (!input.value.trim()) input.value = text;
|
|
57
76
|
else input.setRangeText(`${input.selectionStart ? "\n\n" : ""}${text}`, input.selectionStart, input.selectionEnd, "end");
|
|
58
77
|
syncRunComposer();
|
|
78
|
+
saveRunDraft();
|
|
59
79
|
input.focus();
|
|
60
80
|
});
|
|
61
81
|
$("#runWorkspaceSuggestions").addEventListener("click", (event) => {
|
|
@@ -63,14 +83,28 @@ window.LoadToAgentAppFactories.createDialogEventBindings = function createDialog
|
|
|
63
83
|
if (!workspace) return;
|
|
64
84
|
$("#runCwd").value = workspace.dataset.runWorkspace;
|
|
65
85
|
syncRunComposer();
|
|
86
|
+
saveRunDraft();
|
|
87
|
+
});
|
|
88
|
+
$("#runWorkspaceSuggestions").addEventListener("keydown", (event) => {
|
|
89
|
+
if (!["ArrowLeft", "ArrowRight", "Home", "End"].includes(event.key)) return;
|
|
90
|
+
const options = $$("#runWorkspaceSuggestions [data-run-workspace]");
|
|
91
|
+
const current = Math.max(0, options.indexOf(event.target.closest("[data-run-workspace]")));
|
|
92
|
+
const next = event.key === "Home"
|
|
93
|
+
? 0
|
|
94
|
+
: event.key === "End"
|
|
95
|
+
? options.length - 1
|
|
96
|
+
: (current + (event.key === "ArrowRight" ? 1 : -1) + options.length) % options.length;
|
|
97
|
+
event.preventDefault();
|
|
98
|
+
options[next]?.focus();
|
|
66
99
|
});
|
|
67
100
|
$("#runCwd").addEventListener("input", syncRunComposer);
|
|
68
101
|
$("#allowWrites").addEventListener("change", syncRunComposer);
|
|
69
102
|
$("#pickRunCwdBtn").addEventListener("click", async () => {
|
|
70
|
-
const folder = await performUiAction(() => window.loadtoagent.pickWorkspace(), "
|
|
103
|
+
const folder = await performUiAction(() => window.loadtoagent.pickWorkspace(), t("workspace.pick_failed"), $("#pickRunCwdBtn"));
|
|
71
104
|
if (folder) {
|
|
72
105
|
$("#runCwd").value = folder;
|
|
73
106
|
syncRunComposer();
|
|
107
|
+
saveRunDraft();
|
|
74
108
|
}
|
|
75
109
|
});
|
|
76
110
|
$("#runForm").addEventListener("keydown", (event) => {
|
|
@@ -80,11 +114,27 @@ window.LoadToAgentAppFactories.createDialogEventBindings = function createDialog
|
|
|
80
114
|
}
|
|
81
115
|
});
|
|
82
116
|
$("#runForm").addEventListener("submit", handleRun);
|
|
117
|
+
$("#runForm").addEventListener("invalid", (event) => {
|
|
118
|
+
event.target.setAttribute("aria-invalid", "true");
|
|
119
|
+
if (!$("#runForm").dataset.invalidFocusQueued) {
|
|
120
|
+
$("#runForm").dataset.invalidFocusQueued = "true";
|
|
121
|
+
queueMicrotask(() => {
|
|
122
|
+
delete $("#runForm").dataset.invalidFocusQueued;
|
|
123
|
+
$("#runForm").querySelector(":invalid")?.focus({ preventScroll: true });
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}, true);
|
|
127
|
+
$("#runForm").addEventListener("input", (event) => {
|
|
128
|
+
if (event.target.matches("input, textarea, select") && event.target.checkValidity()) event.target.removeAttribute("aria-invalid");
|
|
129
|
+
$("#runError").classList.add("hidden");
|
|
130
|
+
$("#runError").textContent = "";
|
|
131
|
+
});
|
|
83
132
|
}
|
|
84
133
|
|
|
85
134
|
function bindDrawerAndGlobalEvents() {
|
|
86
135
|
$("#closeDrawerBtn").addEventListener("click", closeDrawer);
|
|
87
|
-
$("#drawerBackdrop")
|
|
136
|
+
if (safeBackdrop) safeBackdrop($("#drawerBackdrop"), closeDrawer, $("#detailDrawer"));
|
|
137
|
+
else $("#drawerBackdrop").addEventListener("click", closeDrawer);
|
|
88
138
|
$(".drawer-tabs").addEventListener("click", (event) => {
|
|
89
139
|
const tab = event.target.closest("[data-tab]");
|
|
90
140
|
if (tab) {
|
|
@@ -94,7 +144,8 @@ window.LoadToAgentAppFactories.createDialogEventBindings = function createDialog
|
|
|
94
144
|
}
|
|
95
145
|
});
|
|
96
146
|
$(".drawer-tabs").addEventListener("keydown", (event) => {
|
|
97
|
-
|
|
147
|
+
const pageKey = event.ctrlKey && ["PageUp", "PageDown"].includes(event.key);
|
|
148
|
+
if (!pageKey && !["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End"].includes(event.key)) return;
|
|
98
149
|
const tabs = $$(".drawer-tab:not(.hidden)");
|
|
99
150
|
const current = Math.max(0, tabs.indexOf(event.target.closest(".drawer-tab")));
|
|
100
151
|
const next =
|
|
@@ -102,7 +153,7 @@ window.LoadToAgentAppFactories.createDialogEventBindings = function createDialog
|
|
|
102
153
|
? 0
|
|
103
154
|
: event.key === "End"
|
|
104
155
|
? tabs.length - 1
|
|
105
|
-
: (current + (
|
|
156
|
+
: (current + (["ArrowRight", "ArrowDown", "PageDown"].includes(event.key) ? 1 : -1) + tabs.length) % tabs.length;
|
|
106
157
|
event.preventDefault();
|
|
107
158
|
state.drawerTab = tabs[next].dataset.tab;
|
|
108
159
|
state.drawerForceLatest = state.drawerTab === "chat";
|
|
@@ -110,14 +161,50 @@ window.LoadToAgentAppFactories.createDialogEventBindings = function createDialog
|
|
|
110
161
|
$(`.drawer-tab[data-tab="${state.drawerTab}"]`)?.focus();
|
|
111
162
|
});
|
|
112
163
|
$("#detailDrawer").addEventListener("click", async (event) => {
|
|
164
|
+
const copy = event.target.closest("[data-copy-text]");
|
|
165
|
+
if (copy) {
|
|
166
|
+
await copyText(copy.dataset.copyText);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
113
169
|
const resume = event.target.closest("[data-resume-agent]");
|
|
114
170
|
if (resume) {
|
|
115
|
-
|
|
171
|
+
if (resume.dataset.busy === "true") return;
|
|
172
|
+
resume.dataset.busy = "true";
|
|
173
|
+
resume.disabled = true;
|
|
174
|
+
resume.setAttribute("aria-busy", "true");
|
|
175
|
+
try {
|
|
176
|
+
await resumeAgentTerminal(resume.dataset.resumeAgent);
|
|
177
|
+
} finally {
|
|
178
|
+
if (resume.isConnected) {
|
|
179
|
+
delete resume.dataset.busy;
|
|
180
|
+
resume.disabled = false;
|
|
181
|
+
resume.removeAttribute("aria-busy");
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
const quick = event.target.closest("[data-attention-quick]");
|
|
187
|
+
if (quick) {
|
|
188
|
+
quickRespond(quick.dataset.attentionSessionId || state.selectedId, quick.dataset.attentionQuick, $("#detailDrawer"));
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const managedAction = event.target.closest("[data-managed-run-action]");
|
|
192
|
+
if (managedAction) {
|
|
193
|
+
await controlManagedRun(managedAction.dataset.managementSessionId || state.selectedId, managedAction.dataset.managedRunAction);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const reassign = event.target.closest("[data-reassign-session]");
|
|
197
|
+
if (reassign) {
|
|
198
|
+
prepareReassignment(reassign.dataset.reassignSession);
|
|
116
199
|
return;
|
|
117
200
|
}
|
|
118
201
|
const retry = event.target.closest("[data-retry-detail]");
|
|
119
202
|
if (retry) {
|
|
120
|
-
|
|
203
|
+
if (retry.dataset.busy === "true") return;
|
|
204
|
+
retry.dataset.busy = "true";
|
|
205
|
+
retry.disabled = true;
|
|
206
|
+
retry.setAttribute("aria-busy", "true");
|
|
207
|
+
await loadSessionDetail(retry.dataset.retryDetail, true);
|
|
121
208
|
return;
|
|
122
209
|
}
|
|
123
210
|
const latest = event.target.closest("[data-scroll-latest]");
|
|
@@ -127,33 +214,55 @@ window.LoadToAgentAppFactories.createDialogEventBindings = function createDialog
|
|
|
127
214
|
return;
|
|
128
215
|
}
|
|
129
216
|
const stop = event.target.closest("[data-stop-run]");
|
|
130
|
-
if (
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
217
|
+
if (stop) await controlManagedRun(state.selectedId, "stop");
|
|
218
|
+
});
|
|
219
|
+
$("#detailDrawer").addEventListener("input", (event) => {
|
|
220
|
+
const input = event.target.closest("[data-agent-command-draft]");
|
|
221
|
+
if (input) state.agentCommandDrafts.set(input.dataset.agentCommandDraft, input.value);
|
|
222
|
+
});
|
|
223
|
+
$("#detailDrawer").addEventListener("change", (event) => {
|
|
224
|
+
const picker = event.target.closest("[data-agent-command-target]");
|
|
225
|
+
if (!picker) return;
|
|
226
|
+
if (picker.value) state.agentCommandTargets.set(picker.dataset.agentCommandTarget, picker.value);
|
|
227
|
+
else state.agentCommandTargets.delete(picker.dataset.agentCommandTarget);
|
|
228
|
+
picker.closest("form")?.querySelectorAll("button").forEach(button => { button.disabled = !picker.value; });
|
|
229
|
+
});
|
|
230
|
+
$("#detailDrawer").addEventListener("keydown", (event) => {
|
|
231
|
+
const input = event.target.closest("[data-agent-command-draft]");
|
|
232
|
+
if (!input || event.key !== "Enter" || event.shiftKey || event.isComposing || event.keyCode === 229) return;
|
|
233
|
+
event.preventDefault();
|
|
234
|
+
input.closest("form")?.requestSubmit();
|
|
235
|
+
});
|
|
236
|
+
$("#detailDrawer").addEventListener("submit", (event) => {
|
|
237
|
+
const form = event.target.closest("[data-agent-command-form]");
|
|
238
|
+
if (!form) return;
|
|
239
|
+
event.preventDefault();
|
|
240
|
+
dispatchAgentCommand(form.dataset.agentCommandForm, form);
|
|
144
241
|
});
|
|
145
242
|
document.addEventListener("keydown", (event) => {
|
|
146
243
|
trapDialogFocus(event);
|
|
147
|
-
|
|
244
|
+
const editable = event.target instanceof HTMLElement && Boolean(event.target.closest("input, textarea, select, [contenteditable='true']"));
|
|
245
|
+
const dialogOpen = Boolean(currentDialog?.());
|
|
246
|
+
const viewShortcuts = ["all", "active", "waiting", "runtime", "terminal", "tmux", "settings"];
|
|
247
|
+
if (!editable && !dialogOpen && (event.metaKey || event.ctrlKey) && /^[1-7]$/.test(event.key)) {
|
|
248
|
+
event.preventDefault();
|
|
249
|
+
selectView(viewShortcuts[Number(event.key) - 1], { focusMain: true });
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
if (!editable && !dialogOpen && event.key === "/" && !event.metaKey && !event.ctrlKey && !event.altKey) {
|
|
253
|
+
event.preventDefault();
|
|
254
|
+
if (state.view !== "all") selectView("all");
|
|
255
|
+
$("#searchInput").focus();
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (!editable && !dialogOpen && event.key.toLowerCase() === "n" && (event.metaKey || event.ctrlKey) && !event.shiftKey && !event.altKey) {
|
|
148
259
|
event.preventDefault();
|
|
149
260
|
if ($("#runModal").classList.contains("hidden")) openRunModal();
|
|
150
261
|
return;
|
|
151
262
|
}
|
|
152
263
|
if (event.key !== "Escape") return;
|
|
153
264
|
if (!$("#mobileToolsMenu").classList.contains("hidden")) {
|
|
154
|
-
$("#
|
|
155
|
-
$("#mobileMoreBtn").setAttribute("aria-expanded", "false");
|
|
156
|
-
$("#mobileMoreBtn").focus();
|
|
265
|
+
$("#mobileToolsCloseBtn")?.click();
|
|
157
266
|
} else if (!$("#runModal").classList.contains("hidden")) closeRunModal();
|
|
158
267
|
else closeDrawer();
|
|
159
268
|
});
|