loadtoagent 1.3.8 → 1.3.9
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 +1 -1
- package/renderer/app-agent-actions.js +22 -6
- package/renderer/app-drawer-content.js +96 -52
- package/renderer/app-drawer.js +13 -2
- package/renderer/app-graph-view.js +33 -11
- 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-responsive-shell.css +6 -2
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
|
@@ -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
|
}
|
|
@@ -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;
|
|
@@ -22,6 +22,7 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
|
|
|
22
22
|
isLiveSession,
|
|
23
23
|
isControlRoomSession = isLiveSession,
|
|
24
24
|
controlRoomStatus = session => session?.status,
|
|
25
|
+
pendingConversationDelivery = () => null,
|
|
25
26
|
sessionRetentionMinutes = () => 0,
|
|
26
27
|
subagentWorkState,
|
|
27
28
|
subagentWorkLabel,
|
|
@@ -41,6 +42,22 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
|
|
|
41
42
|
starting: t("ui.preparing"), running: t("ui.working"), waiting: t("ui.waiting_for_review"), idle: t("ui.idle"),
|
|
42
43
|
completed: t("ui.completed"), failed: t("ui.problem"), cancelled: t("ui.stopped"),
|
|
43
44
|
})[status] || STATUS[status] || status;
|
|
45
|
+
const deliveryLabelKey = (phase) => ({
|
|
46
|
+
sending: "control.delivery_sending",
|
|
47
|
+
confirming: "control.delivery_confirming",
|
|
48
|
+
delayed: "control.delivery_delayed",
|
|
49
|
+
received: "control.delivery_received",
|
|
50
|
+
responding: "control.delivery_responding",
|
|
51
|
+
failed: "control.delivery_failed",
|
|
52
|
+
})[phase] || "control.delivery_confirming";
|
|
53
|
+
const deliverySummaryKey = (phase) => ({
|
|
54
|
+
sending: "drawer.delivery_sending_title",
|
|
55
|
+
confirming: "drawer.delivery_confirming_title",
|
|
56
|
+
delayed: "drawer.delivery_delayed_title",
|
|
57
|
+
received: "drawer.delivery_received_title",
|
|
58
|
+
responding: "drawer.delivery_responding_title",
|
|
59
|
+
failed: "drawer.delivery_failed_title",
|
|
60
|
+
})[phase] || "drawer.delivery_confirming_title";
|
|
44
61
|
|
|
45
62
|
function graphNode(session, options = {}) {
|
|
46
63
|
const provider = providerInfo(session.provider);
|
|
@@ -50,7 +67,8 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
|
|
|
50
67
|
const percent = Math.max(0, Math.min(100, Number(context.percent || 0)));
|
|
51
68
|
const running = isLiveSession(session);
|
|
52
69
|
const presentationStatus = controlRoomStatus(session);
|
|
53
|
-
const
|
|
70
|
+
const delivery = pendingConversationDelivery(session);
|
|
71
|
+
const retained = isControlRoomSession(session) && !running && !delivery;
|
|
54
72
|
const childCount = (session.childIds || []).length;
|
|
55
73
|
const childMetrics = session.collaboration && session.collaboration.metrics;
|
|
56
74
|
const cumulativeChildren = childMetrics ? childMetrics.cumulativeCreated : childCount;
|
|
@@ -62,7 +80,7 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
|
|
|
62
80
|
? delegation.taskName || session.taskName
|
|
63
81
|
: session.title;
|
|
64
82
|
const goalPreview = readablePreview(displayedTask, options.focus ? 118 : 96);
|
|
65
|
-
const currentWork = latestWorkCopy(session);
|
|
83
|
+
const currentWork = delivery ? t(deliverySummaryKey(delivery.phase)) : latestWorkCopy(session);
|
|
66
84
|
const currentPreview = readablePreview(currentWork, options.focus ? 132 : 108);
|
|
67
85
|
const role = session.parentId
|
|
68
86
|
? t("graph.helper_ai_identity", {
|
|
@@ -79,7 +97,7 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
|
|
|
79
97
|
<span class="provider-mark">${esc(provider.mark)}</span>
|
|
80
98
|
<span class="agent-identity"><b>${esc(role)}</b><small>${esc(provider.label)} · ${esc(session.model || t("graph.model_unknown"))}</small></span>
|
|
81
99
|
${executionModeBadge(session, true)}
|
|
82
|
-
<span class="status-pill ${statusClass(presentationStatus)}">${esc(statusLabel(presentationStatus))}</span>
|
|
100
|
+
<span class="status-pill ${statusClass(presentationStatus)}">${esc(delivery ? t(deliveryLabelKey(delivery.phase)) : statusLabel(presentationStatus))}</span>
|
|
83
101
|
</span>
|
|
84
102
|
<span class="agent-task-label">
|
|
85
103
|
${session.parentId ? t("graph.assigned_task", { source: delegation.assignmentSource === "protected"
|
|
@@ -124,6 +142,7 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
|
|
|
124
142
|
const usage = session.usage || {};
|
|
125
143
|
const directChildren = graphChildren(session, model).length;
|
|
126
144
|
const presentationStatus = controlRoomStatus(session);
|
|
145
|
+
const delivery = pendingConversationDelivery(session);
|
|
127
146
|
const identity = session.parentId
|
|
128
147
|
? t("graph.helper_ai_named", { name: session.agentName || agentRoleLabel(session.agentRole) })
|
|
129
148
|
: t("project.origin_named", { name: sessionWorkspaceLabel(session) });
|
|
@@ -132,7 +151,7 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
|
|
|
132
151
|
const assignedWork = delegation.assignmentObserved && delegation.assignment ? delegation.assignment : taskName || session.title;
|
|
133
152
|
const sharedGoal = delegation.sharedGoal || session.sharedGoal || "";
|
|
134
153
|
const outcome = delegation.result || session.result || "";
|
|
135
|
-
const outcomeText = outcome || latestWorkCopy(session);
|
|
154
|
+
const outcomeText = delivery ? t(deliverySummaryKey(delivery.phase)) : outcome || latestWorkCopy(session);
|
|
136
155
|
const assignedWorkPreview = readablePreview(assignedWork, session.parentId ? 110 : 104);
|
|
137
156
|
const taskLabel = session.parentId ? `${label || agentRoleLabel(session.agentRole)}${taskName ? t("graph.assigned_name_suffix", { name: taskName }) : ""}` : label;
|
|
138
157
|
const assignmentSourceNote = session.parentId && delegation.assignmentSource === "protected"
|
|
@@ -197,7 +216,7 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
|
|
|
197
216
|
<em>${esc(identity)} · ${directChildren ? `${t("graph.helper_ai_count", { count: directChildren })} · ` : ""}${esc(timeAgo(session.updatedAt))}</em>
|
|
198
217
|
${assignmentSourceNote}${sharedGoalCopy}${outcomeCopy}
|
|
199
218
|
</span>
|
|
200
|
-
<span class="agent-flow-provider"><i>${esc(provider.mark)}</i><small>${esc(statusLabel(presentationStatus))}</small></span>
|
|
219
|
+
<span class="agent-flow-provider"><i>${esc(provider.mark)}</i><small>${esc(delivery ? t(deliveryLabelKey(delivery.phase)) : statusLabel(presentationStatus))}</small></span>
|
|
201
220
|
</button>`;
|
|
202
221
|
}
|
|
203
222
|
|
|
@@ -447,8 +466,9 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
|
|
|
447
466
|
function controlRoomSession(root, model) {
|
|
448
467
|
const provider = providerInfo(root.provider);
|
|
449
468
|
const presentationStatus = controlRoomStatus(root);
|
|
469
|
+
const delivery = pendingConversationDelivery(root);
|
|
450
470
|
const waiting = presentationStatus === "waiting";
|
|
451
|
-
const retained = isControlRoomSession(root) && !isLiveSession(root);
|
|
471
|
+
const retained = isControlRoomSession(root) && !isLiveSession(root) && !delivery;
|
|
452
472
|
const descendants = controlRoomDescendants(root, model);
|
|
453
473
|
const actors = [root, ...descendants];
|
|
454
474
|
const executionItems = actors.flatMap(owner => (owner.executions || []).map(activity => ({ activity, owner })));
|
|
@@ -463,14 +483,14 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
|
|
|
463
483
|
...completedChildren.map(child => ({ kind: "child", child, timestamp: child.completedAt || child.updatedAt })),
|
|
464
484
|
...completedExecutions.map(item => ({ kind: "execution", item, timestamp: item.activity.updatedAt || item.activity.startedAt })),
|
|
465
485
|
].sort((a, b) => Date.parse(b.timestamp || 0) - Date.parse(a.timestamp || 0)).slice(0, 3);
|
|
466
|
-
const current = controlRoomSummary(latestWorkCopy(root) || root.statusDetail || root.title, 74);
|
|
486
|
+
const current = controlRoomSummary(delivery ? t(deliverySummaryKey(delivery.phase)) : latestWorkCopy(root) || root.statusDetail || root.title, 74);
|
|
467
487
|
const title = controlRoomAgentGoal(root, 64);
|
|
468
488
|
const unitCount = activeUnits.length;
|
|
469
489
|
const main = `<button type="button" class="control-room-main" data-open-session="${esc(root.id)}"
|
|
470
490
|
data-control-summary="${esc(title.text)}"
|
|
471
491
|
data-motion-key="control-main:${esc(root.id)}" data-motion-value="${esc(root.updatedAt || "")}:${esc(root.status || "")}"
|
|
472
492
|
style="${providerStyle(root.provider)}">
|
|
473
|
-
<span class="control-main-top"><span class="provider-mark">${esc(provider.mark)}</span><span><small>${esc(t("control.main_agent"))}</small><b>${esc(provider.label)} · ${esc(root.model || t("session.model_unknown"))}</b></span><em><i aria-hidden="true"></i>${esc(statusLabel(presentationStatus))}</em></span>
|
|
493
|
+
<span class="control-main-top"><span class="provider-mark">${esc(provider.mark)}</span><span><small>${esc(t("control.main_agent"))}</small><b>${esc(provider.label)} · ${esc(root.model || t("session.model_unknown"))}</b></span><em><i aria-hidden="true"></i>${esc(delivery ? t(deliveryLabelKey(delivery.phase)) : statusLabel(presentationStatus))}</em></span>
|
|
474
494
|
<strong title="${esc(title.full)}">${esc(title.text)}</strong>
|
|
475
495
|
<span class="control-main-now"><small>${esc(t("graph.current_work"))}</small><b title="${esc(current.full)}">${esc(current.text)}</b></span>
|
|
476
496
|
<span class="control-main-meta"><small>${esc(t("control.unit_counts", { helpers: activeChildren.length, executions: activeExecutions.length }))}</small><b>${esc(t("graph.view_conversation"))} →</b></span>
|
|
@@ -484,9 +504,11 @@ window.LoadToAgentAppFactories.createGraphView = function createGraphView(contex
|
|
|
484
504
|
? completedUnits.map(unit => unit.kind === "child" ? controlRoomChildNode(unit.child) : controlRoomExecutionNode(unit.item)).join("")
|
|
485
505
|
: `<div class="control-room-complete-empty"><span>✓</span><small>${esc(t("control.completed_empty"))}</small></div>`;
|
|
486
506
|
const waitingWithBackground = waiting && activeExecutions.some(item => item.activity.mode === "background" || item.activity.kind === "background");
|
|
487
|
-
const sessionStateKey =
|
|
488
|
-
?
|
|
489
|
-
:
|
|
507
|
+
const sessionStateKey = delivery
|
|
508
|
+
? deliveryLabelKey(delivery.phase)
|
|
509
|
+
: waitingWithBackground
|
|
510
|
+
? "control.waiting_background_session"
|
|
511
|
+
: (waiting ? "control.waiting_session" : (retained ? "control.recently_completed" : "control.live_session"));
|
|
490
512
|
const retention = retained ? `<small class="control-session-retention">${esc(t("control.auto_history_in_minutes", { minutes: sessionRetentionMinutes(root) }))}</small>` : "";
|
|
491
513
|
const archive = retained ? `<button type="button" class="control-session-archive" data-session-archive="${esc(root.id)}">${esc(t("control.move_to_history"))}</button>` : "";
|
|
492
514
|
return `<article class="control-room-session ${waiting ? "is-waiting" : ""} ${waitingWithBackground ? "has-background-work" : ""}" data-control-session="${esc(root.id)}" data-session-sortable="${esc(root.id)}"
|
package/renderer/app.js
CHANGED
|
@@ -585,6 +585,54 @@ window.LoadToAgentAppFactories.createCore = function createCore(context = {}) {
|
|
|
585
585
|
const completedAt = Date.parse(session?.completedAt || session?.endedAt || 0);
|
|
586
586
|
return Number.isFinite(completedAt) ? completedAt : 0;
|
|
587
587
|
}
|
|
588
|
+
function conversationMessageKey(message) {
|
|
589
|
+
const delivery = window.LoadToAgentConversationDelivery;
|
|
590
|
+
if (delivery?.messageKey) return delivery.messageKey(message);
|
|
591
|
+
const id = String(message?.id || "").trim();
|
|
592
|
+
if (id) return `id:${id}`;
|
|
593
|
+
return `${message?.role || ""}:${String(message?.text || "").replace(/\s+/g, " ").trim()}:${message?.timestamp || ""}`;
|
|
594
|
+
}
|
|
595
|
+
function conversationDeliveryState(session, entry, now = Date.now()) {
|
|
596
|
+
return window.LoadToAgentConversationDelivery?.deliveryState?.(session, entry, now) || null;
|
|
597
|
+
}
|
|
598
|
+
function pendingConversationDelivery(session, now = Date.now()) {
|
|
599
|
+
const pending = state.pendingConversationMessages.get(String(session?.id || "")) || [];
|
|
600
|
+
const retained = [];
|
|
601
|
+
let latest = null;
|
|
602
|
+
for (const entry of pending) {
|
|
603
|
+
const delivery = conversationDeliveryState(session, entry, now);
|
|
604
|
+
if (!delivery) continue;
|
|
605
|
+
observeConversationDelivery(session, entry, delivery);
|
|
606
|
+
if (delivery.phase === "responded") {
|
|
607
|
+
clearTimeout(entry.confirmationTimer);
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
retained.push(entry);
|
|
611
|
+
latest = { ...delivery, entry };
|
|
612
|
+
}
|
|
613
|
+
if (retained.length !== pending.length) {
|
|
614
|
+
if (retained.length) state.pendingConversationMessages.set(String(session?.id || ""), retained);
|
|
615
|
+
else state.pendingConversationMessages.delete(String(session?.id || ""));
|
|
616
|
+
}
|
|
617
|
+
return latest;
|
|
618
|
+
}
|
|
619
|
+
function observeConversationDelivery(session, entry, delivery) {
|
|
620
|
+
if (!entry || !delivery || entry.observedPhase === delivery.phase) return;
|
|
621
|
+
const previousPhase = entry.observedPhase || "";
|
|
622
|
+
entry.observedPhase = delivery.phase;
|
|
623
|
+
entry.phase = delivery.phase;
|
|
624
|
+
entry.phaseChangedAt = new Date().toISOString();
|
|
625
|
+
console.info("[LoadToAgent:conversation-delivery]", {
|
|
626
|
+
event: "conversation-delivery-phase-changed",
|
|
627
|
+
sessionId: String(session?.id || ""),
|
|
628
|
+
previousPhase,
|
|
629
|
+
phase: delivery.phase,
|
|
630
|
+
elapsedMs: Math.round(Number(delivery.elapsedMs || 0)),
|
|
631
|
+
userMessageObserved: Boolean(delivery.userMessage),
|
|
632
|
+
responseStartObserved: Boolean(delivery.responseStartEvent),
|
|
633
|
+
assistantMessageObserved: Boolean(delivery.assistantMessage),
|
|
634
|
+
});
|
|
635
|
+
}
|
|
588
636
|
function loadSessionArchives() {
|
|
589
637
|
try {
|
|
590
638
|
const saved = JSON.parse(localStorage.getItem(SESSION_ARCHIVE_STORAGE_KEY) || "{}");
|
|
@@ -614,6 +662,10 @@ window.LoadToAgentAppFactories.createCore = function createCore(context = {}) {
|
|
|
614
662
|
}
|
|
615
663
|
function isControlRoomSession(session, now = Date.now()) {
|
|
616
664
|
if (!session) return false;
|
|
665
|
+
if (pendingConversationDelivery(session, now)) {
|
|
666
|
+
state.controlRoomObservedIds.add(String(session.id || ""));
|
|
667
|
+
return true;
|
|
668
|
+
}
|
|
617
669
|
if (isLiveSession(session)) {
|
|
618
670
|
state.controlRoomObservedIds.add(String(session.id || ""));
|
|
619
671
|
return true;
|
|
@@ -786,6 +838,10 @@ window.LoadToAgentAppFactories.createCore = function createCore(context = {}) {
|
|
|
786
838
|
isLiveSession,
|
|
787
839
|
hasRunningExecution,
|
|
788
840
|
sessionResponseTimestamp,
|
|
841
|
+
conversationMessageKey,
|
|
842
|
+
conversationDeliveryState,
|
|
843
|
+
pendingConversationDelivery,
|
|
844
|
+
observeConversationDelivery,
|
|
789
845
|
loadSessionArchives,
|
|
790
846
|
saveSessionArchives,
|
|
791
847
|
isSessionManuallyArchived,
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
(function exposeConversationDelivery(root, factory) {
|
|
4
|
+
const api = factory();
|
|
5
|
+
if (typeof module === "object" && module.exports) module.exports = api;
|
|
6
|
+
if (root) root.LoadToAgentConversationDelivery = api;
|
|
7
|
+
})(typeof window === "object" ? window : null, function createConversationDelivery() {
|
|
8
|
+
const CONFIRMATION_DELAY_MS = 12_000;
|
|
9
|
+
|
|
10
|
+
function normalizedText(value) {
|
|
11
|
+
return String(value || "").replace(/\s+/g, " ").trim();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function messageKey(message) {
|
|
15
|
+
const id = String(message?.id || "").trim();
|
|
16
|
+
if (id) return `id:${id}`;
|
|
17
|
+
return `${message?.role || ""}:${normalizedText(message?.text)}:${message?.timestamp || ""}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function newMessagesForEntry(session, entry) {
|
|
21
|
+
const baseline = entry?.baselineMessageKeys instanceof Set
|
|
22
|
+
? entry.baselineMessageKeys
|
|
23
|
+
: new Set(entry?.baselineMessageKeys || []);
|
|
24
|
+
return (session?.messages || []).filter(message => !baseline.has(messageKey(message)));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function deliveryState(session, entry, now = Date.now()) {
|
|
28
|
+
if (!entry) return null;
|
|
29
|
+
const newMessages = newMessagesForEntry(session, entry);
|
|
30
|
+
const expectedText = normalizedText(entry.text);
|
|
31
|
+
const userMessage = newMessages.find(message =>
|
|
32
|
+
message?.role === "user" && normalizedText(message.text) === expectedText) || null;
|
|
33
|
+
const assistantMessage = newMessages.find(message =>
|
|
34
|
+
message?.role === "assistant" && normalizedText(message.text)) || null;
|
|
35
|
+
const dispatchedAt = Date.parse(entry.dispatchedAt || entry.timestamp || 0);
|
|
36
|
+
const elapsedMs = Number.isFinite(dispatchedAt) ? Math.max(0, Number(now) - dispatchedAt) : 0;
|
|
37
|
+
const userObservedAt = Date.parse(userMessage?.timestamp || 0);
|
|
38
|
+
const responseStartEvent = userMessage
|
|
39
|
+
? (session?.lifecycle || []).find(event => {
|
|
40
|
+
const eventAt = Date.parse(event?.timestamp || 0);
|
|
41
|
+
return Number.isFinite(eventAt)
|
|
42
|
+
&& (!Number.isFinite(userObservedAt) || eventAt >= userObservedAt)
|
|
43
|
+
&& event?.status === "running"
|
|
44
|
+
&& /start|turn|run/i.test(String(event?.type || event?.label || ""));
|
|
45
|
+
}) || null
|
|
46
|
+
: null;
|
|
47
|
+
|
|
48
|
+
let phase = "confirming";
|
|
49
|
+
if (entry.status === "failed") phase = "failed";
|
|
50
|
+
else if (assistantMessage) phase = "responded";
|
|
51
|
+
else if (userMessage && responseStartEvent) phase = "responding";
|
|
52
|
+
else if (userMessage) phase = "received";
|
|
53
|
+
else if (entry.status === "sending") phase = "sending";
|
|
54
|
+
else if (elapsedMs >= CONFIRMATION_DELAY_MS) phase = "delayed";
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
phase,
|
|
58
|
+
elapsedMs,
|
|
59
|
+
userMessage,
|
|
60
|
+
assistantMessage,
|
|
61
|
+
responseStartEvent,
|
|
62
|
+
receivedAt: userMessage?.timestamp || null,
|
|
63
|
+
responseObservedAt: assistantMessage?.timestamp || responseStartEvent?.timestamp || null,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
CONFIRMATION_DELAY_MS,
|
|
69
|
+
normalizedText,
|
|
70
|
+
messageKey,
|
|
71
|
+
newMessagesForEntry,
|
|
72
|
+
deliveryState,
|
|
73
|
+
};
|
|
74
|
+
});
|
|
@@ -257,7 +257,7 @@
|
|
|
257
257
|
"agent.handing_off": {"ko":"이어받는 중…","en":"Handing off…","zh-CN":"正在接管…"},
|
|
258
258
|
"agent.handoff_and_send": {"ko":"관리 터미널로 전환하고 보내기 ↵","en":"Switch to a managed terminal and send ↵","zh-CN":"切换到托管终端并发送 ↵"},
|
|
259
259
|
"agent.connecting": {"ko":"연결하는 중…","en":"Connecting…","zh-CN":"正在连接…"},
|
|
260
|
-
"agent.background_and_send": {"ko":"
|
|
260
|
+
"agent.background_and_send": {"ko":"내용을 이어서 보내기","en":"Continue the conversation","zh-CN":"继续对话"},
|
|
261
261
|
"agent.copy_bridge": {"ko":"연결용 시작 명령 복사","en":"Copy connection start command","zh-CN":"复制连接启动命令"},
|
|
262
262
|
"agent.command_example": {"ko":"예: 이전 작업에 이어서 테스트를 실행하고 결과를 알려줘","en":"Example: Continue the previous work, run the tests, and report the results","zh-CN":"示例:继续之前的工作,运行测试并告知结果"},
|
|
263
263
|
"agent.command_title": {"ko":"연결된 입력 채널에 지시 보내기","en":"Send an instruction through the linked input channel","zh-CN":"通过已连接的输入通道发送指令"},
|
|
@@ -286,7 +286,7 @@
|
|
|
286
286
|
"agent.select_target_first": {"ko":"지시를 보낼 터미널을 먼저 선택하세요.","en":"Choose a terminal before sending an instruction.","zh-CN":"请先选择要发送指令的终端。"},
|
|
287
287
|
"agent.no_writable_terminal": {"ko":"이 AI에 연결된 입력 가능한 터미널이 없습니다.","en":"There is no writable terminal connected to this AI.","zh-CN":"没有连接到此 AI 的可输入终端。"},
|
|
288
288
|
"agent.command_sent": {"ko":"{target}에 지시를 보냈습니다.","en":"Sent the instruction to {target}.","zh-CN":"已将指令发送到 {target}。"},
|
|
289
|
-
"agent.command_sent_background": {"ko":"
|
|
289
|
+
"agent.command_sent_background": {"ko":"전송 요청을 보냈습니다. 실제 수신과 응답 시작은 대화 상태에서 확인합니다.","en":"The send request was submitted. Check the conversation status for confirmed receipt and response start.","zh-CN":"发送请求已提交,请在对话状态中确认实际接收和回复开始。"},
|
|
290
290
|
"agent.recovered_and_sent": {"ko":"원래 터미널 연결이 종료되어 같은 AI 세션으로 복구한 뒤 지시를 보냈습니다.","en":"The original terminal connection ended, so the AI session was restored before sending the instruction.","zh-CN":"原终端连接已结束,因此恢复同一 AI 会话后发送了指令。"},
|
|
291
291
|
"agent.recovery_failed": {"ko":"터미널 연결이 끊어졌고 세션 복구에도 실패했습니다.","en":"The terminal disconnected and session recovery also failed.","zh-CN":"终端连接已断开,会话恢复也失败。"},
|
|
292
292
|
"agent.send_failed": {"ko":"터미널에 지시를 보내지 못했습니다.","en":"Could not send the instruction to the terminal.","zh-CN":"无法向终端发送指令。"},
|
|
@@ -352,7 +352,43 @@
|
|
|
352
352
|
"drawer.preparing_response": {"ko":"AI가 현재 응답을 준비하고 있습니다","en":"The AI is preparing a response","zh-CN":"AI 正在准备回复"},
|
|
353
353
|
"drawer.message_sending": {"ko":"보내는 중","en":"Sending","zh-CN":"发送中"},
|
|
354
354
|
"drawer.message_sent": {"ko":"전송됨","en":"Sent","zh-CN":"已发送"},
|
|
355
|
+
"drawer.message_confirming": {"ko":"수신 확인 중","en":"Confirming receipt","zh-CN":"正在确认接收"},
|
|
356
|
+
"drawer.message_unconfirmed": {"ko":"수신 미확인","en":"Receipt unconfirmed","zh-CN":"尚未确认接收"},
|
|
357
|
+
"drawer.message_received": {"ko":"AI 수신 확인","en":"Received by AI session","zh-CN":"AI 会话已接收"},
|
|
358
|
+
"drawer.message_responding": {"ko":"응답 중","en":"Responding","zh-CN":"正在回复"},
|
|
355
359
|
"drawer.message_failed": {"ko":"전송 실패","en":"Send failed","zh-CN":"发送失败"},
|
|
360
|
+
"drawer.delivery_sending_title": {"ko":"메시지를 보내고 있습니다","en":"Sending your message","zh-CN":"正在发送消息"},
|
|
361
|
+
"drawer.delivery_sending_detail": {"ko":"관리 터미널에 전송 요청을 전달하는 중입니다.","en":"The send request is being passed to the managed terminal.","zh-CN":"正在将发送请求传递给托管终端。"},
|
|
362
|
+
"drawer.delivery_confirming_title": {"ko":"AI 세션의 수신을 확인하고 있습니다","en":"Confirming receipt in the AI session","zh-CN":"正在确认 AI 会话是否接收"},
|
|
363
|
+
"drawer.delivery_confirming_detail": {"ko":"전송 요청은 성공했습니다. AI 대화 기록에 메시지가 나타나는지 확인 중입니다.","en":"The send request succeeded. Waiting for the message to appear in the AI conversation log.","zh-CN":"发送请求已成功,正在等待消息出现在 AI 对话记录中。"},
|
|
364
|
+
"drawer.delivery_delayed_title": {"ko":"아직 AI 수신을 확인하지 못했습니다","en":"AI receipt is not confirmed yet","zh-CN":"尚未确认 AI 已接收"},
|
|
365
|
+
"drawer.delivery_delayed_detail": {"ko":"전송 요청은 완료됐지만 AI 대화 기록에는 아직 메시지가 없습니다. 응답 중이라고 단정하지 않습니다.","en":"The send request completed, but the message is not yet in the AI conversation log. The app is not claiming the AI is responding.","zh-CN":"发送请求已完成,但 AI 对话记录中尚无该消息,因此应用不会声称 AI 正在回复。"},
|
|
366
|
+
"drawer.delivery_received_title": {"ko":"AI 세션에서 메시지를 확인했습니다","en":"Message confirmed in the AI session","zh-CN":"已在 AI 会话中确认消息"},
|
|
367
|
+
"drawer.delivery_received_detail": {"ko":"대화 기록에 메시지가 들어왔습니다. AI 실행 시작 신호를 기다리고 있습니다.","en":"The message is in the conversation log. Waiting for an AI execution-start signal.","zh-CN":"消息已进入对话记录,正在等待 AI 执行开始信号。"},
|
|
368
|
+
"drawer.delivery_responding_title": {"ko":"AI가 이 메시지에 응답하고 있습니다","en":"The AI is responding to this message","zh-CN":"AI 正在回复此消息"},
|
|
369
|
+
"drawer.delivery_responding_detail": {"ko":"메시지 수신과 새 실행 시작을 모두 확인했습니다. 첫 응답을 기다리는 중입니다.","en":"Both message receipt and a new execution start were observed. Waiting for the first response.","zh-CN":"已确认消息接收和新执行开始,正在等待首条回复。"},
|
|
370
|
+
"drawer.delivery_failed_title": {"ko":"메시지를 보내지 못했습니다","en":"The message could not be sent","zh-CN":"消息发送失败"},
|
|
371
|
+
"drawer.delivery_failed_detail": {"ko":"전송 요청이 실패했습니다. AI는 이 메시지를 받지 못했을 수 있습니다.","en":"The send request failed. The AI may not have received this message.","zh-CN":"发送请求失败,AI 可能没有收到此消息。"},
|
|
372
|
+
"drawer.delivery_failed_with_reason": {"ko":"전송 요청이 실패했습니다: {reason}","en":"The send request failed: {reason}","zh-CN":"发送请求失败:{reason}"},
|
|
373
|
+
"drawer.delivery_observed_title": {"ko":"AI 세션이 실행 중입니다","en":"The AI session is running","zh-CN":"AI 会话正在运行"},
|
|
374
|
+
"drawer.delivery_observed_detail": {"ko":"대화 기록과 세션 실행 상태를 기준으로 확인했습니다. 이 앱에서 시작하지 않은 전송 단계는 추적하지 않습니다.","en":"Confirmed from the conversation log and session runtime state. Send steps started outside this app are not tracked.","zh-CN":"已根据对话记录和会话运行状态确认;不会跟踪在本应用之外发起的发送步骤。"},
|
|
375
|
+
"drawer.delivery_observed_message": {"ko":"대화 기록 확인","en":"Conversation log confirmed","zh-CN":"已确认对话记录"},
|
|
376
|
+
"drawer.delivery_observed_message_help": {"ko":"사용자 메시지가 실제 기록에 있습니다","en":"The user message exists in the actual log","zh-CN":"用户消息已存在于实际记录中"},
|
|
377
|
+
"drawer.delivery_observed_session": {"ko":"AI 실행 상태 확인","en":"AI runtime state confirmed","zh-CN":"已确认 AI 运行状态"},
|
|
378
|
+
"drawer.delivery_observed_session_help": {"ko":"현재 세션이 실행 중으로 관측됩니다","en":"The current session is observed as running","zh-CN":"当前会话被观察为正在运行"},
|
|
379
|
+
"drawer.delivery_evidence_sending": {"ko":"확인 근거 · 터미널 전송 요청 진행 중","en":"Evidence · terminal send request in progress","zh-CN":"确认依据 · 终端发送请求进行中"},
|
|
380
|
+
"drawer.delivery_evidence_confirming": {"ko":"확인 근거 · 전송 요청 성공, 대화 기록 미확인","en":"Evidence · send request succeeded, conversation log not yet confirmed","zh-CN":"确认依据 · 发送请求成功,对话记录尚未确认"},
|
|
381
|
+
"drawer.delivery_evidence_delayed": {"ko":"확인 근거 · 12초 이상 대화 기록에서 메시지 미발견","en":"Evidence · message absent from the conversation log for over 12 seconds","zh-CN":"确认依据 · 超过 12 秒未在对话记录中发现消息"},
|
|
382
|
+
"drawer.delivery_evidence_received": {"ko":"확인 근거 · 대화 기록에서 사용자 메시지 발견","en":"Evidence · user message found in the conversation log","zh-CN":"确认依据 · 已在对话记录中发现用户消息"},
|
|
383
|
+
"drawer.delivery_evidence_responding": {"ko":"확인 근거 · 사용자 메시지와 새 실행 시작 이벤트 발견","en":"Evidence · user message and new execution-start event observed","zh-CN":"确认依据 · 已发现用户消息和新的执行开始事件"},
|
|
384
|
+
"drawer.delivery_evidence_failed": {"ko":"확인 근거 · 터미널 전송 오류","en":"Evidence · terminal send error","zh-CN":"确认依据 · 终端发送错误"},
|
|
385
|
+
"drawer.delivery_evidence_observed": {"ko":"확인 근거 · 사용자 메시지 기록 + 세션 실행 상태","en":"Evidence · user-message log + session runtime state","zh-CN":"确认依据 · 用户消息记录 + 会话运行状态"},
|
|
386
|
+
"drawer.delivery_step_dispatch": {"ko":"앱에서 전송 요청","en":"Send request from app","zh-CN":"应用发起发送请求"},
|
|
387
|
+
"drawer.delivery_step_dispatch_help": {"ko":"관리 터미널에 메시지를 전달합니다","en":"Pass the message to the managed terminal","zh-CN":"将消息传递给托管终端"},
|
|
388
|
+
"drawer.delivery_step_received": {"ko":"AI 세션에서 수신 확인","en":"Confirm receipt in AI session","zh-CN":"确认 AI 会话接收"},
|
|
389
|
+
"drawer.delivery_step_received_help": {"ko":"실제 대화 기록에서 같은 메시지를 찾습니다","en":"Find the same message in the actual conversation log","zh-CN":"在实际对话记录中查找相同消息"},
|
|
390
|
+
"drawer.delivery_step_response": {"ko":"AI 응답 시작 확인","en":"Confirm AI response start","zh-CN":"确认 AI 开始回复"},
|
|
391
|
+
"drawer.delivery_step_response_help": {"ko":"새 실행 이벤트나 응답 메시지를 확인합니다","en":"Observe a new execution event or response message","zh-CN":"观察新的执行事件或回复消息"},
|
|
356
392
|
"drawer.progress_updates": {"ko":"진행 업데이트 {count}개","en":"{count} progress updates","zh-CN":"{count} 条进度更新"},
|
|
357
393
|
"drawer.progress_updates_help": {"ko":"중간 작업 설명과 원문 전체 보기","en":"View intermediate notes and full text","zh-CN":"查看中间说明和完整原文"},
|
|
358
394
|
"drawer.progress_update_item": {"ko":"진행 {count}","en":"Update {count}","zh-CN":"进度 {count}"},
|
|
@@ -1371,6 +1407,12 @@
|
|
|
1371
1407
|
"control.live_session": {"ko":"실행 중인 세션","en":"Live session","zh-CN":"运行中的会话"},
|
|
1372
1408
|
"control.waiting_session": {"ko":"대기 중인 세션","en":"Waiting session","zh-CN":"等待中的会话"},
|
|
1373
1409
|
"control.waiting_background_session": {"ko":"응답 대기 · 백그라운드 실행 중","en":"Waiting for response · background work running","zh-CN":"等待回复 · 后台任务运行中"},
|
|
1410
|
+
"control.delivery_sending": {"ko":"메시지 전송 중","en":"Sending message","zh-CN":"正在发送消息"},
|
|
1411
|
+
"control.delivery_confirming": {"ko":"AI 수신 확인 중","en":"Confirming AI receipt","zh-CN":"正在确认 AI 接收"},
|
|
1412
|
+
"control.delivery_delayed": {"ko":"전달 확인 지연","en":"Delivery confirmation delayed","zh-CN":"发送确认延迟"},
|
|
1413
|
+
"control.delivery_received": {"ko":"AI 메시지 수신 확인","en":"AI message receipt confirmed","zh-CN":"已确认 AI 接收消息"},
|
|
1414
|
+
"control.delivery_responding": {"ko":"AI 응답 생성 중","en":"AI response in progress","zh-CN":"AI 正在生成回复"},
|
|
1415
|
+
"control.delivery_failed": {"ko":"메시지 전송 실패","en":"Message send failed","zh-CN":"消息发送失败"},
|
|
1374
1416
|
"control.auto_history_in_minutes": {"ko":"{minutes}분 뒤 지난 기록으로 이동","en":"Moves to history in {minutes} min","zh-CN":"{minutes} 分钟后移至历史记录"},
|
|
1375
1417
|
"control.move_to_history": {"ko":"지난 기록으로 이동","en":"Move to history","zh-CN":"移至历史记录"},
|
|
1376
1418
|
"control.project_group_actions": {"ko":"프로젝트 그룹 열기와 닫기","en":"Project group expand and collapse controls","zh-CN":"项目组展开和折叠控件"},
|
package/renderer/index.html
CHANGED
|
@@ -468,6 +468,7 @@
|
|
|
468
468
|
<script src="../node_modules/@xterm/xterm/lib/xterm.js"></script>
|
|
469
469
|
<script src="../node_modules/@xterm/addon-fit/lib/addon-fit.js"></script>
|
|
470
470
|
<script src="i18n-messages.js"></script>
|
|
471
|
+
<script src="conversation-delivery.js"></script>
|
|
471
472
|
<script src="i18n.js"></script>
|
|
472
473
|
<script src="shared.js"></script>
|
|
473
474
|
<script src="app.js"></script>
|
|
@@ -525,6 +525,30 @@ button[aria-busy="true"] {
|
|
|
525
525
|
animation: control-room-pulse 1s ease-in-out infinite;
|
|
526
526
|
}
|
|
527
527
|
|
|
528
|
+
.chat-delivery-status.confirming,
|
|
529
|
+
.chat-delivery-status.received,
|
|
530
|
+
.chat-delivery-status.responding {
|
|
531
|
+
background: rgba(91,169,255,.09);
|
|
532
|
+
color: #8fc3ff !important;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
.chat-delivery-status.confirming::before,
|
|
536
|
+
.chat-delivery-status.responding::before {
|
|
537
|
+
display: inline-block;
|
|
538
|
+
width: 5px;
|
|
539
|
+
height: 5px;
|
|
540
|
+
margin-right: 5px;
|
|
541
|
+
border-radius: 50%;
|
|
542
|
+
background: #71b2ff;
|
|
543
|
+
content: "";
|
|
544
|
+
animation: control-room-pulse 1s ease-in-out infinite;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
.chat-delivery-status.delayed {
|
|
548
|
+
background: rgba(246,190,75,.11);
|
|
549
|
+
color: #f1c66f !important;
|
|
550
|
+
}
|
|
551
|
+
|
|
528
552
|
.chat-delivery-status.failed {
|
|
529
553
|
background: rgba(255,107,127,.1);
|
|
530
554
|
color: #ff9baa !important;
|
|
@@ -1120,100 +1144,177 @@ button[aria-busy="true"] {
|
|
|
1120
1144
|
animation: control-room-pulse 1.35s ease-in-out infinite;
|
|
1121
1145
|
}
|
|
1122
1146
|
|
|
1123
|
-
.chat-progress
|
|
1147
|
+
.chat-delivery-progress {
|
|
1148
|
+
display: grid;
|
|
1149
|
+
gap: 11px;
|
|
1124
1150
|
margin-left: 43px;
|
|
1125
|
-
|
|
1126
|
-
border
|
|
1127
|
-
|
|
1128
|
-
|
|
1151
|
+
padding: 13px;
|
|
1152
|
+
border: 1px solid rgba(91,169,255,.24);
|
|
1153
|
+
border-radius: 12px;
|
|
1154
|
+
background: linear-gradient(145deg,rgba(35,76,125,.13),rgba(10,15,22,.76));
|
|
1155
|
+
color: #b9c7d8;
|
|
1129
1156
|
}
|
|
1130
1157
|
|
|
1131
|
-
.chat-progress
|
|
1132
|
-
display:
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
gap:
|
|
1136
|
-
padding: 11px 13px;
|
|
1137
|
-
cursor: pointer;
|
|
1138
|
-
list-style: none;
|
|
1158
|
+
.chat-delivery-progress > header {
|
|
1159
|
+
display: grid;
|
|
1160
|
+
grid-template-columns: 8px minmax(0,1fr);
|
|
1161
|
+
align-items: start;
|
|
1162
|
+
gap: 9px;
|
|
1139
1163
|
}
|
|
1140
1164
|
|
|
1141
|
-
.chat-progress
|
|
1142
|
-
|
|
1165
|
+
.chat-delivery-progress > header > span {
|
|
1166
|
+
width: 7px;
|
|
1167
|
+
height: 7px;
|
|
1168
|
+
margin-top: 5px;
|
|
1169
|
+
border-radius: 50%;
|
|
1170
|
+
background: #71b2ff;
|
|
1171
|
+
box-shadow: 0 0 0 4px rgba(91,169,255,.09);
|
|
1172
|
+
animation: control-room-pulse 1.35s ease-in-out infinite;
|
|
1143
1173
|
}
|
|
1144
1174
|
|
|
1145
|
-
.chat-progress
|
|
1146
|
-
|
|
1147
|
-
|
|
1175
|
+
.chat-delivery-progress > header > div {
|
|
1176
|
+
display: grid;
|
|
1177
|
+
gap: 3px;
|
|
1178
|
+
min-width: 0;
|
|
1148
1179
|
}
|
|
1149
1180
|
|
|
1150
|
-
.chat-progress
|
|
1151
|
-
color: #
|
|
1181
|
+
.chat-delivery-progress > header b {
|
|
1182
|
+
color: #d8e6f7;
|
|
1152
1183
|
font-size: 12px;
|
|
1153
1184
|
}
|
|
1154
1185
|
|
|
1155
|
-
.chat-progress
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1186
|
+
.chat-delivery-progress > header small,
|
|
1187
|
+
.chat-delivery-progress li small,
|
|
1188
|
+
.chat-delivery-progress footer {
|
|
1189
|
+
color: #8494a8;
|
|
1190
|
+
font-size: 10px;
|
|
1191
|
+
line-height: 1.5;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
.chat-delivery-progress ol {
|
|
1195
|
+
display: grid;
|
|
1196
|
+
grid-template-columns: repeat(3,minmax(0,1fr));
|
|
1197
|
+
gap: 7px;
|
|
1198
|
+
margin: 0;
|
|
1199
|
+
padding: 0;
|
|
1200
|
+
list-style: none;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
.chat-delivery-progress.phase-observed ol {
|
|
1204
|
+
grid-template-columns: repeat(2,minmax(0,1fr));
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
.chat-delivery-progress li {
|
|
1208
|
+
display: grid;
|
|
1209
|
+
grid-template-columns: 22px minmax(0,1fr);
|
|
1210
|
+
align-items: start;
|
|
1211
|
+
gap: 7px;
|
|
1212
|
+
min-width: 0;
|
|
1213
|
+
padding: 8px;
|
|
1214
|
+
border: 1px solid #202b38;
|
|
1215
|
+
border-radius: 9px;
|
|
1216
|
+
background: rgba(9,14,21,.62);
|
|
1159
1217
|
}
|
|
1160
1218
|
|
|
1161
|
-
.chat-progress
|
|
1219
|
+
.chat-delivery-progress li > i {
|
|
1220
|
+
width: 21px;
|
|
1221
|
+
height: 21px;
|
|
1222
|
+
display: grid;
|
|
1223
|
+
place-items: center;
|
|
1224
|
+
border: 1px solid #334154;
|
|
1225
|
+
border-radius: 50%;
|
|
1162
1226
|
color: #718196;
|
|
1163
|
-
font-size:
|
|
1227
|
+
font-size: 9px;
|
|
1164
1228
|
font-style: normal;
|
|
1165
|
-
|
|
1229
|
+
font-weight: 800;
|
|
1166
1230
|
}
|
|
1167
1231
|
|
|
1168
|
-
.chat-progress
|
|
1169
|
-
|
|
1232
|
+
.chat-delivery-progress li > span {
|
|
1233
|
+
display: grid;
|
|
1234
|
+
gap: 2px;
|
|
1235
|
+
min-width: 0;
|
|
1170
1236
|
}
|
|
1171
1237
|
|
|
1172
|
-
.chat-progress
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
padding: 0 10px 10px;
|
|
1238
|
+
.chat-delivery-progress li b {
|
|
1239
|
+
color: #aebdce;
|
|
1240
|
+
font-size: 10px;
|
|
1176
1241
|
}
|
|
1177
1242
|
|
|
1178
|
-
.chat-progress
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
border-radius: 9px;
|
|
1182
|
-
background: #0d141d;
|
|
1243
|
+
.chat-delivery-progress li.done {
|
|
1244
|
+
border-color: rgba(89,223,167,.2);
|
|
1245
|
+
background: rgba(35,104,78,.08);
|
|
1183
1246
|
}
|
|
1184
1247
|
|
|
1185
|
-
.chat-progress
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
margin-bottom: 7px;
|
|
1190
|
-
color: #7e8da0;
|
|
1191
|
-
font-size: 11px;
|
|
1248
|
+
.chat-delivery-progress li.done > i {
|
|
1249
|
+
border-color: rgba(89,223,167,.35);
|
|
1250
|
+
background: rgba(89,223,167,.1);
|
|
1251
|
+
color: #70d9ad;
|
|
1192
1252
|
}
|
|
1193
1253
|
|
|
1194
|
-
.chat-progress
|
|
1195
|
-
|
|
1254
|
+
.chat-delivery-progress li.active {
|
|
1255
|
+
border-color: rgba(91,169,255,.3);
|
|
1256
|
+
background: rgba(48,102,171,.1);
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
.chat-delivery-progress li.active > i {
|
|
1260
|
+
border-color: rgba(91,169,255,.48);
|
|
1261
|
+
color: #8fc3ff;
|
|
1262
|
+
animation: control-room-pulse 1.2s ease-in-out infinite;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
.chat-delivery-progress li.warning {
|
|
1266
|
+
border-color: rgba(246,190,75,.32);
|
|
1267
|
+
background: rgba(116,80,17,.11);
|
|
1196
1268
|
}
|
|
1197
1269
|
|
|
1198
|
-
.chat-
|
|
1270
|
+
.chat-delivery-progress li.warning > i {
|
|
1271
|
+
border-color: rgba(246,190,75,.46);
|
|
1272
|
+
color: #f1c66f;
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
.chat-delivery-progress li.error {
|
|
1276
|
+
border-color: rgba(255,107,127,.34);
|
|
1277
|
+
background: rgba(94,31,44,.12);
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
.chat-delivery-progress li.error > i {
|
|
1281
|
+
border-color: rgba(255,107,127,.46);
|
|
1282
|
+
color: #ff9baa;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
.chat-delivery-progress footer {
|
|
1199
1286
|
display: flex;
|
|
1200
1287
|
align-items: center;
|
|
1201
|
-
gap:
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
border: 1px solid rgba(71,214,153,.22);
|
|
1205
|
-
border-radius: 10px;
|
|
1206
|
-
background: rgba(34,118,83,.08);
|
|
1207
|
-
color: #91bca9;
|
|
1208
|
-
font-size: 12px;
|
|
1288
|
+
gap: 6px;
|
|
1289
|
+
padding-top: 9px;
|
|
1290
|
+
border-top: 1px solid #1f2a37;
|
|
1209
1291
|
}
|
|
1210
1292
|
|
|
1211
|
-
.chat-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1293
|
+
.chat-delivery-progress footer i {
|
|
1294
|
+
color: #6e829a;
|
|
1295
|
+
font-style: normal;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
.chat-delivery-progress.phase-delayed {
|
|
1299
|
+
border-color: rgba(246,190,75,.28);
|
|
1300
|
+
background: linear-gradient(145deg,rgba(111,75,18,.13),rgba(10,15,22,.76));
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
.chat-delivery-progress.phase-delayed > header > span {
|
|
1304
|
+
background: #efbd5d;
|
|
1305
|
+
box-shadow: 0 0 0 4px rgba(246,190,75,.09);
|
|
1306
|
+
animation: none;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
.chat-delivery-progress.phase-failed {
|
|
1310
|
+
border-color: rgba(255,107,127,.3);
|
|
1311
|
+
background: linear-gradient(145deg,rgba(94,31,44,.14),rgba(10,15,22,.76));
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
.chat-delivery-progress.phase-failed > header > span {
|
|
1315
|
+
background: #ff7e91;
|
|
1316
|
+
box-shadow: 0 0 0 4px rgba(255,107,127,.08);
|
|
1317
|
+
animation: none;
|
|
1217
1318
|
}
|
|
1218
1319
|
|
|
1219
1320
|
.subagent-call-moment {
|
|
@@ -405,8 +405,7 @@
|
|
|
405
405
|
align-items: flex-start;
|
|
406
406
|
}
|
|
407
407
|
|
|
408
|
-
.chat-progress
|
|
409
|
-
.chat-turn-waiting,
|
|
408
|
+
.chat-delivery-progress,
|
|
410
409
|
.subagent-call-moment,
|
|
411
410
|
.subagent-call-event {
|
|
412
411
|
margin-left: 0;
|
|
@@ -417,6 +416,11 @@
|
|
|
417
416
|
width: 100%;
|
|
418
417
|
}
|
|
419
418
|
|
|
419
|
+
.chat-delivery-progress ol,
|
|
420
|
+
.chat-delivery-progress.phase-observed ol {
|
|
421
|
+
grid-template-columns: 1fr;
|
|
422
|
+
}
|
|
423
|
+
|
|
420
424
|
.json-object>div {
|
|
421
425
|
grid-template-columns: 1fr;
|
|
422
426
|
}
|