loadtoagent 1.3.8 → 1.3.10
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 +13 -1
- package/README.md +13 -1
- package/README.zh-CN.md +13 -1
- package/package.json +9 -3
- package/renderer/app-agent-actions.js +23 -6
- package/renderer/app-dashboard.js +3 -1
- package/renderer/app-drawer-content.js +96 -52
- package/renderer/app-drawer.js +33 -4
- package/renderer/app-graph-orchestration.js +5 -3
- package/renderer/app-graph-view.js +33 -11
- package/renderer/app-management.js +4 -1
- package/renderer/app-runtime-overview.js +7 -0
- package/renderer/app.js +56 -0
- package/renderer/conversation-delivery.js +74 -0
- package/renderer/i18n-messages.js +44 -2
- package/renderer/index.html +1 -0
- package/renderer/styles-components.css +163 -62
- package/renderer/styles-overlays.css +2 -1
- package/renderer/styles-responsive-shell.css +6 -2
- package/renderer/terminal-agent.js +5 -2
- package/renderer/terminal.js +16 -5
package/README.ko.md
CHANGED
|
@@ -81,6 +81,18 @@ LoadToAgent는 시작할 때 현재 패키지 버전과 GitHub의 최신 정식
|
|
|
81
81
|
|
|
82
82
|
홈의 `10분 시작 가이드`도 같은 네 단계를 직접 눌러 연습하게 해 줍니다. 완료 상태는 이 컴퓨터에 저장되며 언제든 다시 펼칠 수 있습니다.
|
|
83
83
|
|
|
84
|
+
### 대화를 이어 보내고 실제 전달 상태 확인하기
|
|
85
|
+
|
|
86
|
+
작업의 **대화 내용** 탭에서 **내용을 이어서 보내기**를 사용하면 지원되는 연결 세션에 새 메시지를 보낼 수 있습니다. 별도 채팅을 만들지 않고 같은 작업과 같은 AI 세션에서 대화가 이어집니다.
|
|
87
|
+
|
|
88
|
+
LoadToAgent는 전달 상태를 실제로 관측한 세 단계로 보여 줍니다.
|
|
89
|
+
|
|
90
|
+
1. 앱에서 전송 요청을 보냈는지
|
|
91
|
+
2. 같은 사용자 메시지가 AI 세션 기록에 나타났는지
|
|
92
|
+
3. 그 메시지 뒤에 새 AI 응답 시작 신호가 나타났는지
|
|
93
|
+
|
|
94
|
+
12초 안에 세션 기록에서 수신이 확인되지 않으면 AI가 응답을 준비한다고 단정하지 않고 `전달 확인 지연`으로 표시합니다. AI의 중간 진행 문장도 별도 상자로 분리하지 않고 시간순 대화 안에 그대로 보여 주며, 마지막 완료 메시지에만 **최종 답변** 표시를 붙입니다. 모든 상태 문구는 로컬 세션 파일에서 확인한 근거를 설명하며 외부 AI 창의 동작을 추측하지 않습니다.
|
|
95
|
+
|
|
84
96
|
## 한눈에 볼 수 있는 것
|
|
85
97
|
|
|
86
98
|
| 화면 | 확인할 수 있는 내용 |
|
|
@@ -89,7 +101,7 @@ LoadToAgent는 시작할 때 현재 패키지 버전과 GitHub의 최신 정식
|
|
|
89
101
|
| 연결 관계 | 사용자 요청, 선택한 메인 AI, 직접 나눠 맡긴 도움 AI |
|
|
90
102
|
| 명령·백그라운드 실행 | AI가 시작한 일반 명령과 백그라운드 명령의 내용, 작업 폴더, 실행 ID와 현재 상태 |
|
|
91
103
|
| 응답·상태 확인함 | 최근 24시간 세션에서 내 응답이 필요한 요청과 현재 실행의 실패·지연·일시정지 위험만 확인 |
|
|
92
|
-
| 작업 상세 | 대화, 도구 호출, 진행 과정, 모델, 작업 폴더, 상태 |
|
|
104
|
+
| 작업 상세 | 시간순 대화, 근거가 확인된 전달 상태, 도구 호출, 진행 과정, 모델, 작업 폴더, 상태 |
|
|
93
105
|
| 관리 요약 | 최근 실행 이벤트, 상태 정보 확인 수준, 완료 신호, 로그에서 찾은 산출물 후보와 테스트 기록, 실행 제어 |
|
|
94
106
|
| 토큰 | 입력·출력·캐시·추론·전체 사용량과 보고된 컨텍스트 점유율 |
|
|
95
107
|
| AI 세션 터미널 | 선택한 AI의 이전 대화와 기존 PTY·tmux 화면을 나란히 보며 같은 세션에 이어서 입력 |
|
package/README.md
CHANGED
|
@@ -81,6 +81,18 @@ On startup, LoadToAgent compares its package version with the newest stable GitH
|
|
|
81
81
|
|
|
82
82
|
The `10-minute start guide` on Home lets you practice the same four steps. Progress is saved on this computer and the guide can be reopened at any time.
|
|
83
83
|
|
|
84
|
+
### Continue a conversation and verify delivery
|
|
85
|
+
|
|
86
|
+
Open a task's **Conversation** tab and use **Continue the conversation** to send another message to a supported connected session. The message stays in the same task and session instead of opening a separate chat.
|
|
87
|
+
|
|
88
|
+
LoadToAgent shows delivery as three observable steps:
|
|
89
|
+
|
|
90
|
+
1. the app dispatched the send request;
|
|
91
|
+
2. the exact user message appeared in the AI session log;
|
|
92
|
+
3. a new AI response-start signal appeared after that message.
|
|
93
|
+
|
|
94
|
+
If the session log does not confirm receipt within 12 seconds, the app reports a delivery-confirmation delay instead of claiming that the AI is already preparing a response. Intermediate AI updates remain in chronological chat order, and only the latest completed message is marked **Final answer**. These labels describe local evidence from the session files; they do not guess what an external AI window is doing.
|
|
95
|
+
|
|
84
96
|
## What LoadToAgent shows
|
|
85
97
|
|
|
86
98
|
| View | What you get |
|
|
@@ -89,7 +101,7 @@ The `10-minute start guide` on Home lets you practice the same four steps. Progr
|
|
|
89
101
|
| Relationship view | The request origin, selected agent, and every directly delegated subagent |
|
|
90
102
|
| Execution units | Foreground shells, background shells, and background jobs started by an AI, including command, workspace, execution ID, and live status |
|
|
91
103
|
| Operations and attention inbox | Prioritized failures, stalls, context risk, approvals, decisions, and input requests with immediate actions |
|
|
92
|
-
| Session detail |
|
|
104
|
+
| Session detail | Chronological conversation, evidence-based delivery state, tool activity, lifecycle events, model, workspace, and status |
|
|
93
105
|
| Management summary | Checkpoints, observation confidence, completion summary, artifacts, verification, and run controls |
|
|
94
106
|
| Token view | Input, output, cached, reasoning, total, and reported context-window usage |
|
|
95
107
|
| Session terminal | The selected AI's prior conversation beside its existing PTY or tmux pane, with input continuing in that exact session |
|
package/README.zh-CN.md
CHANGED
|
@@ -81,6 +81,18 @@ LoadToAgent 启动时会比较当前包版本与最新的稳定 GitHub Release
|
|
|
81
81
|
|
|
82
82
|
首页的`10 分钟入门指南`可以带你实际完成同样的四个步骤。进度只保存在本机,并且可随时重新打开。
|
|
83
83
|
|
|
84
|
+
### 继续对话并确认实际传递状态
|
|
85
|
+
|
|
86
|
+
打开任务的**对话**页,使用**继续对话**即可向受支持的已连接会话发送新消息。消息会保留在同一个任务和 AI 会话中,不会另开聊天。
|
|
87
|
+
|
|
88
|
+
LoadToAgent 会按照实际可观测证据显示三个传递步骤:
|
|
89
|
+
|
|
90
|
+
1. 应用是否已发出发送请求;
|
|
91
|
+
2. 相同的用户消息是否已出现在 AI 会话记录中;
|
|
92
|
+
3. 该消息之后是否出现了新的 AI 回复开始信号。
|
|
93
|
+
|
|
94
|
+
如果 12 秒内仍未在会话记录中确认收到消息,应用会显示“传递确认延迟”,而不会直接声称 AI 正在准备回复。AI 的中间进度消息也会按时间顺序保留在对话中,不再放入单独的折叠区域;只有最后一条已完成消息会标记为**最终回答**。所有状态文字都说明本地会话文件中的可验证证据,不会猜测外部 AI 窗口正在做什么。
|
|
95
|
+
|
|
84
96
|
## LoadToAgent 可以展示什么
|
|
85
97
|
|
|
86
98
|
| 视图 | 内容 |
|
|
@@ -89,7 +101,7 @@ LoadToAgent 启动时会比较当前包版本与最新的稳定 GitHub Release
|
|
|
89
101
|
| 关系视图 | 用户请求、当前代理以及它直接委派的所有子代理 |
|
|
90
102
|
| 执行单元 | AI 启动的前台 Shell、后台 Shell 与后台任务,包括命令、工作目录、执行 ID 和实时状态 |
|
|
91
103
|
| 运行概览与待确认收件箱 | 按优先级集中展示失败、停滞、上下文风险、审批、决策和输入请求,并可立即处理 |
|
|
92
|
-
| 会话详情 |
|
|
104
|
+
| 会话详情 | 按时间排列的对话、基于证据的传递状态、工具活动、执行过程、模型、工作目录和状态 |
|
|
93
105
|
| 管理摘要 | 检查点、观测置信度、完成摘要、产物、验证结果和执行控制 |
|
|
94
106
|
| Token 视图 | 输入、输出、缓存、推理、总量和已报告的上下文占用率 |
|
|
95
107
|
| 终端控制 | 本地 Shell,以及向 LoadToAgent 自有终端安全发送命令 |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loadtoagent",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.10",
|
|
4
4
|
"description": "A local desktop dashboard for Claude, Codex, Gemini, and Grok sessions.",
|
|
5
5
|
"main": "main.js",
|
|
6
6
|
"author": "wincube AX",
|
|
@@ -124,11 +124,17 @@
|
|
|
124
124
|
"target": [
|
|
125
125
|
{
|
|
126
126
|
"target": "dmg",
|
|
127
|
-
"arch": [
|
|
127
|
+
"arch": [
|
|
128
|
+
"arm64",
|
|
129
|
+
"x64"
|
|
130
|
+
]
|
|
128
131
|
},
|
|
129
132
|
{
|
|
130
133
|
"target": "zip",
|
|
131
|
-
"arch": [
|
|
134
|
+
"arch": [
|
|
135
|
+
"arm64",
|
|
136
|
+
"x64"
|
|
137
|
+
]
|
|
132
138
|
}
|
|
133
139
|
],
|
|
134
140
|
"artifactName": "LoadToAgent-${version}-${arch}.${ext}"
|
|
@@ -12,6 +12,7 @@ window.LoadToAgentAppFactories.createAgentActions = function createAgentActions(
|
|
|
12
12
|
selectView,
|
|
13
13
|
providerInfo,
|
|
14
14
|
isLiveSession,
|
|
15
|
+
conversationMessageKey,
|
|
15
16
|
} = context;
|
|
16
17
|
|
|
17
18
|
function agentCommandTargets(session) {
|
|
@@ -91,12 +92,6 @@ window.LoadToAgentAppFactories.createAgentActions = function createAgentActions(
|
|
|
91
92
|
return session?.parentId ? `${session.id}:${route}` : session.id;
|
|
92
93
|
}
|
|
93
94
|
|
|
94
|
-
function conversationMessageKey(message) {
|
|
95
|
-
const id = String(message?.id || "").trim();
|
|
96
|
-
if (id) return `id:${id}`;
|
|
97
|
-
return `${message?.role || ""}:${String(message?.text || "").replace(/\s+/g, " ").trim()}:${message?.timestamp || ""}`;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
95
|
function beginConversationMessage(session, command) {
|
|
101
96
|
const detail = state.details.get(session.id);
|
|
102
97
|
const baselineMessages = [...(session.messages || []), ...(detail?.messages || [])];
|
|
@@ -105,6 +100,8 @@ window.LoadToAgentAppFactories.createAgentActions = function createAgentActions(
|
|
|
105
100
|
text: command,
|
|
106
101
|
timestamp: new Date().toISOString(),
|
|
107
102
|
status: "sending",
|
|
103
|
+
phase: "sending",
|
|
104
|
+
dispatchedAt: null,
|
|
108
105
|
presented: false,
|
|
109
106
|
baselineMessageKeys: new Set(baselineMessages.map(conversationMessageKey)),
|
|
110
107
|
};
|
|
@@ -120,6 +117,25 @@ window.LoadToAgentAppFactories.createAgentActions = function createAgentActions(
|
|
|
120
117
|
if (!entry) return;
|
|
121
118
|
entry.status = status;
|
|
122
119
|
entry.error = error;
|
|
120
|
+
if (status === "awaiting") {
|
|
121
|
+
entry.dispatchedAt = entry.dispatchedAt || new Date().toISOString();
|
|
122
|
+
entry.phase = "confirming";
|
|
123
|
+
const delay = Number(window.LoadToAgentConversationDelivery?.CONFIRMATION_DELAY_MS || 12_000);
|
|
124
|
+
clearTimeout(entry.confirmationTimer);
|
|
125
|
+
entry.confirmationTimer = setTimeout(() => {
|
|
126
|
+
entry.confirmationTimer = 0;
|
|
127
|
+
const pending = state.pendingConversationMessages.get(sessionId) || [];
|
|
128
|
+
if (!pending.includes(entry) || entry.status !== "awaiting") return;
|
|
129
|
+
state.drawerForceLatest = true;
|
|
130
|
+
context.render?.();
|
|
131
|
+
context.renderDrawer?.();
|
|
132
|
+
}, delay + 40);
|
|
133
|
+
} else if (status === "failed") {
|
|
134
|
+
entry.phase = "failed";
|
|
135
|
+
entry.failedAt = new Date().toISOString();
|
|
136
|
+
clearTimeout(entry.confirmationTimer);
|
|
137
|
+
entry.confirmationTimer = 0;
|
|
138
|
+
}
|
|
123
139
|
state.drawerForceLatest = true;
|
|
124
140
|
context.renderDrawer?.();
|
|
125
141
|
}
|
|
@@ -314,6 +330,7 @@ window.LoadToAgentAppFactories.createAgentActions = function createAgentActions(
|
|
|
314
330
|
try {
|
|
315
331
|
await window.LoadToAgentTerminal.resumeForAgent(targetSession, routedCommand, true, { focus: false });
|
|
316
332
|
state.agentCommandDrafts.delete(sessionId);
|
|
333
|
+
if (input) input.value = "";
|
|
317
334
|
updateConversationMessage(sessionId, pendingMessage, "awaiting");
|
|
318
335
|
context.toast(t(routingEnabled && routeContext.route === "parent" ? "agent.command_routed_via_parent" : "agent.command_sent_background"));
|
|
319
336
|
} catch (error) {
|
|
@@ -211,7 +211,9 @@ window.LoadToAgentAppFactories.createDashboard = function createDashboard(contex
|
|
|
211
211
|
if (desktopList.dataset.overflowBound !== "true") {
|
|
212
212
|
desktopList.dataset.overflowBound = "true";
|
|
213
213
|
desktopList.addEventListener("scroll", updateProjectOverflow, { passive: true });
|
|
214
|
-
|
|
214
|
+
// Mutating layout-related classes directly inside ResizeObserver can
|
|
215
|
+
// trigger Chromium's "undelivered notifications" loop warning.
|
|
216
|
+
desktopList._overflowObserver = new ResizeObserver(() => requestAnimationFrame(updateProjectOverflow));
|
|
215
217
|
desktopList._overflowObserver.observe(desktopList);
|
|
216
218
|
}
|
|
217
219
|
requestAnimationFrame(updateProjectOverflow);
|
|
@@ -6,6 +6,7 @@ window.LoadToAgentAppFactories.createDrawerContent = function createDrawerConten
|
|
|
6
6
|
const {
|
|
7
7
|
esc, uiLocale, state, messageContentHtml, compact, fullNumber, timeOnly, providerInfo, statusIcon, agentPathTaskName, snapshotSession,
|
|
8
8
|
controlRoomAgentGoal, inferredExecutionSummary, executionActivityLabel, executionActivityStatus,
|
|
9
|
+
conversationDeliveryState, observeConversationDelivery,
|
|
9
10
|
} = context;
|
|
10
11
|
const t = (key, params) => window.LoadToAgentI18n.t(key, params);
|
|
11
12
|
|
|
@@ -57,12 +58,17 @@ window.LoadToAgentAppFactories.createDrawerContent = function createDrawerConten
|
|
|
57
58
|
const answerKind = assistant && options.answerKind
|
|
58
59
|
? `<span class="chat-answer-kind${options.live ? " is-live" : ""}">${esc(options.answerKind)}</span>`
|
|
59
60
|
: "";
|
|
61
|
+
const deliveryStatusKey = {
|
|
62
|
+
sending: "drawer.message_sending",
|
|
63
|
+
confirming: "drawer.message_confirming",
|
|
64
|
+
delayed: "drawer.message_unconfirmed",
|
|
65
|
+
received: "drawer.message_received",
|
|
66
|
+
responding: "drawer.message_responding",
|
|
67
|
+
failed: "drawer.message_failed",
|
|
68
|
+
};
|
|
60
69
|
const deliveryStatus = !assistant && message.deliveryStatus
|
|
61
|
-
? `<span class="chat-delivery-status ${esc(message.deliveryStatus)}">${esc(t(message.deliveryStatus
|
|
62
|
-
|
|
63
|
-
: message.deliveryStatus === "sending"
|
|
64
|
-
? "drawer.message_sending"
|
|
65
|
-
: "drawer.message_sent"))}</span>`
|
|
70
|
+
? `<span class="chat-delivery-status ${esc(message.deliveryStatus)}">${esc(t(deliveryStatusKey[message.deliveryStatus]
|
|
71
|
+
|| "drawer.message_sent"))}</span>`
|
|
66
72
|
: "";
|
|
67
73
|
const optimisticClasses = message.optimistic
|
|
68
74
|
? ` is-optimistic is-${esc(message.deliveryStatus || "awaiting")}${message.animate ? " is-new" : ""}`
|
|
@@ -81,21 +87,24 @@ window.LoadToAgentAppFactories.createDrawerContent = function createDrawerConten
|
|
|
81
87
|
const pending = state.pendingConversationMessages.get(session.id) || [];
|
|
82
88
|
if (!pending.length) return { session, forceLatestLive: false };
|
|
83
89
|
const messages = [...(session.messages || [])];
|
|
84
|
-
const messageKey = (message) => {
|
|
85
|
-
const id = String(message?.id || "").trim();
|
|
86
|
-
if (id) return `id:${id}`;
|
|
87
|
-
return `${message?.role || ""}:${String(message?.text || "").replace(/\s+/g, " ").trim()}:${message?.timestamp || ""}`;
|
|
88
|
-
};
|
|
89
|
-
const normalizedText = (value) => String(value || "").replace(/\s+/g, " ").trim();
|
|
90
90
|
const retained = [];
|
|
91
91
|
let forceLatestLive = false;
|
|
92
92
|
for (const entry of pending) {
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
93
|
+
const delivery = conversationDeliveryState({ ...session, messages }, entry);
|
|
94
|
+
if (!delivery) continue;
|
|
95
|
+
observeConversationDelivery(session, entry, delivery);
|
|
96
|
+
if (delivery.phase === "responded") {
|
|
97
|
+
clearTimeout(entry.confirmationTimer);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const tracking = {
|
|
101
|
+
phase: delivery.phase,
|
|
102
|
+
elapsedMs: delivery.elapsedMs,
|
|
103
|
+
error: entry.error || "",
|
|
104
|
+
receivedAt: delivery.receivedAt,
|
|
105
|
+
responseObservedAt: delivery.responseObservedAt,
|
|
106
|
+
};
|
|
107
|
+
if (!delivery.userMessage) {
|
|
99
108
|
messages.push({
|
|
100
109
|
id: entry.id,
|
|
101
110
|
role: "user",
|
|
@@ -103,12 +112,21 @@ window.LoadToAgentAppFactories.createDrawerContent = function createDrawerConten
|
|
|
103
112
|
timestamp: entry.timestamp,
|
|
104
113
|
optimistic: true,
|
|
105
114
|
animate: !entry.presented,
|
|
106
|
-
deliveryStatus:
|
|
115
|
+
deliveryStatus: delivery.phase,
|
|
116
|
+
deliveryTracking: tracking,
|
|
107
117
|
});
|
|
108
118
|
entry.presented = true;
|
|
119
|
+
} else {
|
|
120
|
+
const actualIndex = messages.indexOf(delivery.userMessage);
|
|
121
|
+
if (actualIndex >= 0) {
|
|
122
|
+
messages[actualIndex] = {
|
|
123
|
+
...delivery.userMessage,
|
|
124
|
+
deliveryStatus: delivery.phase,
|
|
125
|
+
deliveryTracking: tracking,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
109
128
|
}
|
|
110
|
-
|
|
111
|
-
if (entry.status === "sending" || entry.status === "awaiting") forceLatestLive = true;
|
|
129
|
+
forceLatestLive = true;
|
|
112
130
|
retained.push(entry);
|
|
113
131
|
}
|
|
114
132
|
if (retained.length) state.pendingConversationMessages.set(session.id, retained);
|
|
@@ -117,23 +135,6 @@ window.LoadToAgentAppFactories.createDrawerContent = function createDrawerConten
|
|
|
117
135
|
return { session: { ...session, messages }, forceLatestLive };
|
|
118
136
|
}
|
|
119
137
|
|
|
120
|
-
function progressUpdatesHtml(turn, session) {
|
|
121
|
-
if (!turn.progress.length) return "";
|
|
122
|
-
const rows = turn.progress.map((message, index) => {
|
|
123
|
-
const fullTime = new Date(message.timestamp).toLocaleString(uiLocale());
|
|
124
|
-
return `<article data-progress-message-id="${esc(message.id || "")}">
|
|
125
|
-
<header><b>${esc(t("drawer.progress_update_item", { count: index + 1 }))}</b><time title="${esc(fullTime)}">${esc(timeOnly(message.timestamp))}</time></header>
|
|
126
|
-
${messageContentHtml(message, session.id)}
|
|
127
|
-
</article>`;
|
|
128
|
-
}).join("");
|
|
129
|
-
return `<details class="chat-progress-updates" data-progress-count="${turn.progress.length}"
|
|
130
|
-
data-disclosure-key="${esc(`drawer:${session.id}:turn:${turn.id}:progress`)}">
|
|
131
|
-
<summary><span><b>${esc(t("drawer.progress_updates", { count: turn.progress.length }))}</b>
|
|
132
|
-
<small>${esc(t("drawer.progress_updates_help"))}</small></span><i aria-hidden="true">↓</i></summary>
|
|
133
|
-
<div class="chat-progress-list">${rows}</div>
|
|
134
|
-
</details>`;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
138
|
function subagentCallEvents(session) {
|
|
138
139
|
const spawns = session?.collaboration?.spawns || [];
|
|
139
140
|
const children = (session?.childIds || [])
|
|
@@ -261,18 +262,70 @@ window.LoadToAgentAppFactories.createDrawerContent = function createDrawerConten
|
|
|
261
262
|
});
|
|
262
263
|
return items.map(item => {
|
|
263
264
|
if (item.kind === "call") return subagentCallHtml(item.call, { showAnchor: false });
|
|
264
|
-
const finalMessage = item.
|
|
265
|
+
const finalMessage = item.index === turn.assistants.length - 1
|
|
266
|
+
|| (representativeId && item.message.id === representativeId);
|
|
265
267
|
return conversationRowHtml(item.message, session, {
|
|
266
268
|
userLabel: labels.userLabel,
|
|
267
269
|
assistantLabel: labels.assistantLabel,
|
|
268
270
|
answerKind: finalMessage
|
|
269
271
|
? t(turn.live ? "drawer.current_progress" : turn.awaitingFinal ? "drawer.last_progress" : "drawer.final_answer")
|
|
270
|
-
:
|
|
272
|
+
: "",
|
|
271
273
|
live: turn.live && finalMessage,
|
|
272
274
|
});
|
|
273
275
|
}).join("");
|
|
274
276
|
}
|
|
275
277
|
|
|
278
|
+
function deliveryProgressHtml(tracking) {
|
|
279
|
+
if (!tracking) {
|
|
280
|
+
return `<div class="chat-delivery-progress phase-observed" data-delivery-phase="observed" role="status" aria-live="polite">
|
|
281
|
+
<header><span aria-hidden="true"></span><div><b>${esc(t("drawer.delivery_observed_title"))}</b><small>${esc(t("drawer.delivery_observed_detail"))}</small></div></header>
|
|
282
|
+
<ol>
|
|
283
|
+
<li class="done"><i aria-hidden="true">✓</i><span><b>${esc(t("drawer.delivery_observed_message"))}</b><small>${esc(t("drawer.delivery_observed_message_help"))}</small></span></li>
|
|
284
|
+
<li class="active"><i aria-hidden="true">2</i><span><b>${esc(t("drawer.delivery_observed_session"))}</b><small>${esc(t("drawer.delivery_observed_session_help"))}</small></span></li>
|
|
285
|
+
</ol>
|
|
286
|
+
<footer><i aria-hidden="true">◎</i><span>${esc(t("drawer.delivery_evidence_observed"))}</span></footer>
|
|
287
|
+
</div>`;
|
|
288
|
+
}
|
|
289
|
+
const phase = tracking.phase || "confirming";
|
|
290
|
+
const copy = {
|
|
291
|
+
sending: ["drawer.delivery_sending_title", "drawer.delivery_sending_detail", "drawer.delivery_evidence_sending"],
|
|
292
|
+
confirming: ["drawer.delivery_confirming_title", "drawer.delivery_confirming_detail", "drawer.delivery_evidence_confirming"],
|
|
293
|
+
delayed: ["drawer.delivery_delayed_title", "drawer.delivery_delayed_detail", "drawer.delivery_evidence_delayed"],
|
|
294
|
+
received: ["drawer.delivery_received_title", "drawer.delivery_received_detail", "drawer.delivery_evidence_received"],
|
|
295
|
+
responding: ["drawer.delivery_responding_title", "drawer.delivery_responding_detail", "drawer.delivery_evidence_responding"],
|
|
296
|
+
failed: ["drawer.delivery_failed_title", "drawer.delivery_failed_detail", "drawer.delivery_evidence_failed"],
|
|
297
|
+
}[phase] || ["drawer.delivery_confirming_title", "drawer.delivery_confirming_detail", "drawer.delivery_evidence_confirming"];
|
|
298
|
+
const stepState = (step) => {
|
|
299
|
+
if (phase === "failed") return step === 0 ? "error" : "pending";
|
|
300
|
+
if (phase === "sending") return step === 0 ? "active" : "pending";
|
|
301
|
+
if (phase === "confirming" || phase === "delayed") {
|
|
302
|
+
if (step === 0) return "done";
|
|
303
|
+
if (step === 1) return phase === "delayed" ? "warning" : "active";
|
|
304
|
+
return "pending";
|
|
305
|
+
}
|
|
306
|
+
if (phase === "received") return step < 2 ? "done" : "active";
|
|
307
|
+
if (phase === "responding") return step < 2 ? "done" : "active";
|
|
308
|
+
return "pending";
|
|
309
|
+
};
|
|
310
|
+
const steps = [
|
|
311
|
+
["drawer.delivery_step_dispatch", "drawer.delivery_step_dispatch_help"],
|
|
312
|
+
["drawer.delivery_step_received", "drawer.delivery_step_received_help"],
|
|
313
|
+
["drawer.delivery_step_response", "drawer.delivery_step_response_help"],
|
|
314
|
+
].map(([label, detail], index) => {
|
|
315
|
+
const status = stepState(index);
|
|
316
|
+
const marker = status === "done" ? "✓" : status === "error" ? "!" : String(index + 1);
|
|
317
|
+
return `<li class="${status}"><i aria-hidden="true">${marker}</i><span><b>${esc(t(label))}</b><small>${esc(t(detail))}</small></span></li>`;
|
|
318
|
+
}).join("");
|
|
319
|
+
const detail = phase === "failed" && tracking.error
|
|
320
|
+
? t("drawer.delivery_failed_with_reason", { reason: tracking.error })
|
|
321
|
+
: t(copy[1]);
|
|
322
|
+
return `<div class="chat-delivery-progress phase-${esc(phase)}" data-delivery-phase="${esc(phase)}" role="status" aria-live="polite">
|
|
323
|
+
<header><span aria-hidden="true"></span><div><b>${esc(t(copy[0]))}</b><small>${esc(detail)}</small></div></header>
|
|
324
|
+
<ol>${steps}</ol>
|
|
325
|
+
<footer><i aria-hidden="true">◎</i><span>${esc(t(copy[2]))}</span></footer>
|
|
326
|
+
</div>`;
|
|
327
|
+
}
|
|
328
|
+
|
|
276
329
|
function chatHtml(session, options = {}) {
|
|
277
330
|
const overlay = conversationOverlay(session);
|
|
278
331
|
session = overlay.session;
|
|
@@ -283,7 +336,6 @@ window.LoadToAgentAppFactories.createDrawerContent = function createDrawerConten
|
|
|
283
336
|
const assistantLabel = options.assistantLabel || providerInfo(session.provider).label;
|
|
284
337
|
const conversationLabel = options.conversationLabel || t("drawer.conversation");
|
|
285
338
|
const turns = conversationTurns(session, { ...options, forceLatestLive: overlay.forceLatestLive });
|
|
286
|
-
const progressCount = turns.reduce((sum, turn) => sum + turn.progress.length, 0);
|
|
287
339
|
const omitted = Number(session.omittedMessages || 0);
|
|
288
340
|
const notice =
|
|
289
341
|
omitted || session.truncated
|
|
@@ -291,14 +343,8 @@ window.LoadToAgentAppFactories.createDrawerContent = function createDrawerConten
|
|
|
291
343
|
: "";
|
|
292
344
|
const rows = turns.map((turn, turnIndex) => {
|
|
293
345
|
const user = conversationRowHtml(turn.user, session, { userLabel, assistantLabel });
|
|
294
|
-
const representative = conversationRowHtml(turn.representative, session, {
|
|
295
|
-
userLabel,
|
|
296
|
-
assistantLabel,
|
|
297
|
-
answerKind: t(turn.live ? "drawer.current_progress" : turn.awaitingFinal ? "drawer.last_progress" : "drawer.final_answer"),
|
|
298
|
-
live: turn.live,
|
|
299
|
-
});
|
|
300
346
|
const waiting = turn.live && !turn.representative
|
|
301
|
-
?
|
|
347
|
+
? deliveryProgressHtml(turn.user?.deliveryTracking)
|
|
302
348
|
: "";
|
|
303
349
|
const turnStartedAt = Date.parse(turn.user?.timestamp || turn.representative?.timestamp || 0);
|
|
304
350
|
const nextTurnTimestamp = turns[turnIndex + 1]?.user?.timestamp;
|
|
@@ -308,11 +354,9 @@ window.LoadToAgentAppFactories.createDrawerContent = function createDrawerConten
|
|
|
308
354
|
if (Number.isFinite(turnStartedAt) && calledAt < turnStartedAt) return false;
|
|
309
355
|
return !Number.isFinite(nextTurnStartedAt) || calledAt < nextTurnStartedAt;
|
|
310
356
|
});
|
|
311
|
-
const timeline = turnCalls
|
|
312
|
-
? turnWithSubagentCallsHtml(turn, session, turnCalls, { userLabel, assistantLabel })
|
|
313
|
-
: `${representative}${waiting}${progressUpdatesHtml(turn, session)}`;
|
|
357
|
+
const timeline = turnWithSubagentCallsHtml(turn, session, turnCalls, { userLabel, assistantLabel });
|
|
314
358
|
return `<section class="chat-turn${turn.live ? " is-live" : ""}" data-conversation-turn="${esc(turn.id)}">
|
|
315
|
-
${user}${timeline}${
|
|
359
|
+
${user}${timeline}${waiting}
|
|
316
360
|
</section>`;
|
|
317
361
|
}).join("");
|
|
318
362
|
const unmatchedCalls = calls.filter(call => !turns.some((turn, turnIndex) => {
|
|
@@ -325,7 +369,7 @@ window.LoadToAgentAppFactories.createDrawerContent = function createDrawerConten
|
|
|
325
369
|
const callOnlyRows = unmatchedCalls.map(subagentCallHtml).join("");
|
|
326
370
|
const emptyConversation = turns.length || calls.length ? "" : `<div class="empty-state compact"><h3>${esc(t("drawer.no_user_ai_conversation"))}</h3></div>`;
|
|
327
371
|
return `${notice}<div class="chat-history-head">
|
|
328
|
-
<span>${esc(t("drawer.turn_summary", { label: conversationLabel, count: turns.length, updates:
|
|
372
|
+
<span>${esc(t("drawer.turn_summary", { label: conversationLabel, count: turns.length, updates: "" }))}</span>
|
|
329
373
|
<button type="button" data-scroll-latest>${esc(t("drawer.latest_conversation"))} ↓</button>
|
|
330
374
|
</div>
|
|
331
375
|
<div class="chat-list">${callOnlyRows}${rows}${emptyConversation}<div class="chat-latest-anchor" aria-label="${esc(t("drawer.latest_conversation"))}">
|
package/renderer/app-drawer.js
CHANGED
|
@@ -7,11 +7,20 @@ window.LoadToAgentAppFactories.createDrawer = function createDrawer(context = {}
|
|
|
7
7
|
const {
|
|
8
8
|
$, $$, esc, state, motionPreference, motionState, STATUS, markGuideStep, rememberDialogTrigger, restoreDialogTrigger, setDialogOpenState,
|
|
9
9
|
providerInfo, isLiveSession, controlRoomStatus = session => session?.status, subagentWorkState, subagentWorkLabel, isProjectlessSession, sessionOriginPath, sessionWorkspaceLabel,
|
|
10
|
+
pendingConversationDelivery = () => null,
|
|
10
11
|
agentResumeSupport, originAppInfo, selectedSession, snapshotSession, loadSessionDetail, loadSubagentParentDetail,
|
|
11
12
|
chatHtml, lifecycleHtml, tokensHtml, outcomeHtml, subagentCoordinationEvents, subagentConversationHtml, executionActivityDetailHtml,
|
|
12
13
|
agentCommandComposer,
|
|
13
14
|
rememberDisclosureStates = () => {}, restoreDisclosureStates = () => {},
|
|
14
15
|
} = context;
|
|
16
|
+
const deliveryLabelKey = (phase) => ({
|
|
17
|
+
sending: "control.delivery_sending",
|
|
18
|
+
confirming: "control.delivery_confirming",
|
|
19
|
+
delayed: "control.delivery_delayed",
|
|
20
|
+
received: "control.delivery_received",
|
|
21
|
+
responding: "control.delivery_responding",
|
|
22
|
+
failed: "control.delivery_failed",
|
|
23
|
+
})[phase] || "control.delivery_confirming";
|
|
15
24
|
|
|
16
25
|
function openDrawer(id) {
|
|
17
26
|
rememberDialogTrigger();
|
|
@@ -98,6 +107,8 @@ window.LoadToAgentAppFactories.createDrawer = function createDrawer(context = {}
|
|
|
98
107
|
if (!session) return closeDrawer();
|
|
99
108
|
const provider = providerInfo(session.provider);
|
|
100
109
|
const presentationStatus = controlRoomStatus(session);
|
|
110
|
+
const delivery = pendingConversationDelivery(session);
|
|
111
|
+
const presentationLabel = delivery ? t(deliveryLabelKey(delivery.phase)) : STATUS[presentationStatus] || presentationStatus;
|
|
101
112
|
const subagentMode = state.drawerMode === "subagent" && Boolean(session.parentId);
|
|
102
113
|
const executionMode = state.drawerMode === "execution" && Boolean(state.drawerExecutionId);
|
|
103
114
|
const snapshot = snapshotSession(session.id);
|
|
@@ -116,8 +127,8 @@ window.LoadToAgentAppFactories.createDrawer = function createDrawer(context = {}
|
|
|
116
127
|
$("#drawerProvider").textContent = executionMode
|
|
117
128
|
? `${activity?.runtime || activity?.tool || t("drawer.execution_unit")} · ${activity ? context.executionActivityStatus(activity) : t("drawer.unknown")}`
|
|
118
129
|
: subagentMode
|
|
119
|
-
? `${t("control.subagent")} · ${
|
|
120
|
-
: `${provider.company} · ${
|
|
130
|
+
? `${t("control.subagent")} · ${presentationLabel}`
|
|
131
|
+
: `${provider.company} · ${presentationLabel}`;
|
|
121
132
|
const drawerTitle = executionMode
|
|
122
133
|
? context.inferredExecutionSummary(activity || {}).text
|
|
123
134
|
: subagentMode ? session.title || session.taskName || (session.delegation && session.delegation.taskName) : session.title;
|
|
@@ -186,7 +197,7 @@ window.LoadToAgentAppFactories.createDrawer = function createDrawer(context = {}
|
|
|
186
197
|
motionState.drawerRenderKey = renderKey;
|
|
187
198
|
motionState.drawerTab = state.drawerTab;
|
|
188
199
|
const detailError = state.detailErrors.get(state.selectedId);
|
|
189
|
-
|
|
200
|
+
const nextContentHtml = detailLoading
|
|
190
201
|
? `<div class="drawer-loading"><span></span><b>${esc(t("drawer.loading_history"))}</b><small>${esc(t("drawer.loading_history_help"))}</small></div>`
|
|
191
202
|
: detailError
|
|
192
203
|
? `<div class="drawer-error">
|
|
@@ -205,10 +216,28 @@ window.LoadToAgentAppFactories.createDrawer = function createDrawer(context = {}
|
|
|
205
216
|
: state.drawerTab === "lifecycle"
|
|
206
217
|
? lifecycleHtml(session)
|
|
207
218
|
: tokensHtml(session);
|
|
219
|
+
if (motionState.drawerContentHtml !== nextContentHtml) {
|
|
220
|
+
content.innerHTML = nextContentHtml;
|
|
221
|
+
motionState.drawerContentHtml = nextContentHtml;
|
|
222
|
+
}
|
|
208
223
|
const composer = $("#drawerComposer");
|
|
209
224
|
const showComposer = !executionMode && !detailLoading && !detailError && state.drawerTab === "chat" && typeof agentCommandComposer === "function";
|
|
210
225
|
composer.classList.toggle("hidden", !showComposer);
|
|
211
|
-
|
|
226
|
+
const nextComposerHtml = showComposer ? agentCommandComposer(session, { conversation: true }) : "";
|
|
227
|
+
const focusedDraft = document.activeElement?.matches?.("[data-agent-command-draft]")
|
|
228
|
+
&& composer.contains(document.activeElement)
|
|
229
|
+
&& document.activeElement.dataset.agentCommandDraft === session.id;
|
|
230
|
+
// Session snapshots arrive while the user is typing. Replacing the
|
|
231
|
+
// composer would destroy the focused textarea and its IME/caret state.
|
|
232
|
+
// Keep that exact node alive until focus leaves it; the delegated input
|
|
233
|
+
// handler already mirrors the current draft into state.
|
|
234
|
+
if (!focusedDraft && motionState.drawerComposerHtml !== nextComposerHtml) {
|
|
235
|
+
composer.innerHTML = nextComposerHtml;
|
|
236
|
+
motionState.drawerComposerHtml = nextComposerHtml;
|
|
237
|
+
} else if (!showComposer && composer.innerHTML) {
|
|
238
|
+
composer.innerHTML = "";
|
|
239
|
+
motionState.drawerComposerHtml = "";
|
|
240
|
+
}
|
|
212
241
|
restoreDisclosureStates(content);
|
|
213
242
|
content.classList.toggle("motion-content-in", shouldAnimateContent && !motionPreference.matches);
|
|
214
243
|
clearTimeout(motionState.drawerContentTimer);
|
|
@@ -35,6 +35,8 @@ window.LoadToAgentAppFactories.createGraphOrchestration = function createGraphOr
|
|
|
35
35
|
|
|
36
36
|
function renderAgentMap(sessions, motionKind = "refresh") {
|
|
37
37
|
const liveSessionGrid = $("#liveSessionGrid");
|
|
38
|
+
const preserveFocusedComposer = document.activeElement?.matches?.("[data-agent-command-draft]")
|
|
39
|
+
&& liveSessionGrid.contains(document.activeElement);
|
|
38
40
|
rememberDisclosureStates(liveSessionGrid);
|
|
39
41
|
const model = connectedGraphSessions(sessions);
|
|
40
42
|
const focus =
|
|
@@ -47,7 +49,7 @@ window.LoadToAgentAppFactories.createGraphOrchestration = function createGraphOr
|
|
|
47
49
|
? [...rootSessions].sort((a, b) => Number((b.context && b.context.percent) || 0) - Number((a.context && a.context.percent) || 0))
|
|
48
50
|
: stableSessionSort(rootSessions);
|
|
49
51
|
if (!model.nodes.length) {
|
|
50
|
-
liveSessionGrid.innerHTML = "";
|
|
52
|
+
if (!preserveFocusedComposer) liveSessionGrid.innerHTML = "";
|
|
51
53
|
$("#graphBreadcrumbs").innerHTML = "";
|
|
52
54
|
$("#graphResetBtn").classList.add("hidden");
|
|
53
55
|
$("#agentMapToolbar")?.classList.add("hidden");
|
|
@@ -61,7 +63,7 @@ window.LoadToAgentAppFactories.createGraphOrchestration = function createGraphOr
|
|
|
61
63
|
$("#agentMapToolbar")?.classList.remove("hidden");
|
|
62
64
|
$("#controlRoomProjectToolbar")?.classList.add("hidden");
|
|
63
65
|
$("#controlRoomListToolbar")?.classList.add("hidden");
|
|
64
|
-
liveSessionGrid.innerHTML = focusedGraph(focus, model, motionKind);
|
|
66
|
+
if (!preserveFocusedComposer) liveSessionGrid.innerHTML = focusedGraph(focus, model, motionKind);
|
|
65
67
|
const path = graphPath(focus, model.byId);
|
|
66
68
|
$("#graphBreadcrumbs").innerHTML = `<button type="button" data-graph-reset>${esc(t("graph.task_list"))}</button>${path
|
|
67
69
|
.map((item) => {
|
|
@@ -77,7 +79,7 @@ window.LoadToAgentAppFactories.createGraphOrchestration = function createGraphOr
|
|
|
77
79
|
scheduleAgentWorkflowConnections();
|
|
78
80
|
} else {
|
|
79
81
|
const runtime = runtimeAgentSummary(model, liveTmuxEntries(state.snapshot && state.snapshot.tmux));
|
|
80
|
-
liveSessionGrid.innerHTML = runtimeSeparatedOverview(roots, model, roots);
|
|
82
|
+
if (!preserveFocusedComposer) liveSessionGrid.innerHTML = runtimeSeparatedOverview(roots, model, roots);
|
|
81
83
|
restoreDisclosureStates(liveSessionGrid);
|
|
82
84
|
$("#graphBreadcrumbs").innerHTML = "";
|
|
83
85
|
$("#agentMapToolbar")?.classList.add("hidden");
|