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.
@@ -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 retained = isControlRoomSession(session) && !running;
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 = waitingWithBackground
488
- ? "control.waiting_background_session"
489
- : (waiting ? "control.waiting_session" : (retained ? "control.recently_completed" : "control.live_session"));
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)}"
@@ -246,6 +246,8 @@ window.LoadToAgentAppFactories.createManagement = function createManagement(cont
246
246
  function renderAttentionInbox() {
247
247
  const section = $("#attentionInbox");
248
248
  if (!section) return 0;
249
+ const preserveFocusedComposer = document.activeElement?.matches?.("[data-agent-command-draft]")
250
+ && section.contains(document.activeElement);
249
251
  const reviewSessions = context.filteredSessions().filter(needsManagementReview);
250
252
  const filter = ["critical", "warning", "attention"].includes(state.managementFilter) ? state.managementFilter : "all";
251
253
  const sessions = reviewSessions.filter(session => filter === "all" || matchesManagementFilter(session, filter));
@@ -255,13 +257,14 @@ window.LoadToAgentAppFactories.createManagement = function createManagement(cont
255
257
  attention: reviewSessions.filter(session => matchesManagementFilter(session, "attention")).length,
256
258
  };
257
259
  const filterButton = (value, label, count) => `<button type="button" data-management-inbox-filter="${value}" aria-pressed="${filter === value ? "true" : "false"}"><i></i><span>${esc(label)}</span><b>${count}</b></button>`;
258
- section.innerHTML = `<header class="attention-inbox-head"><div><p>${esc(t("management.inbox_eyebrow"))}</p><h2>${esc(t("management.inbox_title"))}</h2><span>${esc(t("management.inbox_description"))}</span></div><strong>${sessions.length}</strong></header>
260
+ const nextHtml = `<header class="attention-inbox-head"><div><p>${esc(t("management.inbox_eyebrow"))}</p><h2>${esc(t("management.inbox_title"))}</h2><span>${esc(t("management.inbox_description"))}</span></div><strong>${sessions.length}</strong></header>
259
261
  <div class="attention-inbox-summary" role="toolbar" aria-label="${esc(t("management.operations_severity_buckets"))}">
260
262
  <div class="management-filter-all">${filterButton("all", t("management.filter_all"), reviewSessions.length)}</div>
261
263
  <div class="management-filter-group response" role="group" aria-label="${esc(t("management.filter_group_response"))}"><small>${esc(t("management.filter_group_response"))}</small>${filterButton("attention", t("management.health.attention"), counts.attention)}</div>
262
264
  <div class="management-filter-group risk" role="group" aria-label="${esc(t("management.filter_group_risk"))}"><small>${esc(t("management.filter_group_risk"))}</small>${filterButton("critical", t("management.health.critical"), counts.critical)}${filterButton("warning", t("management.health.warning"), counts.warning)}</div>
263
265
  </div>
264
266
  <div class="attention-card-list">${sessions.length ? sessions.map(attentionCardHtml).join("") : `<div class="management-empty"><b>${esc(t("management.inbox_empty"))}</b><span>${esc(t("management.inbox_empty_detail"))}</span></div>`}</div>`;
267
+ if (!preserveFocusedComposer) section.innerHTML = nextHtml;
265
268
  return sessions.length;
266
269
  }
267
270
 
@@ -275,6 +275,13 @@ window.LoadToAgentAppFactories.createRuntimeOverview = function createRuntimeOve
275
275
  ${selected ? loopDetail(selected) : noActiveLoop()}
276
276
  </section>
277
277
  </div>`;
278
+ // Restore scroll synchronously before another snapshot render can replace
279
+ // this freshly-created DOM and accidentally capture zero as its position.
280
+ const immediateScheduleList = section.querySelector(".runtime-schedule-list");
281
+ const immediateSelectedTab = section.querySelector(".runtime-loop-tab.selected");
282
+ const immediateTabList = immediateSelectedTab?.closest(".runtime-loop-tabs");
283
+ if (immediateScheduleList) immediateScheduleList.scrollTop = scheduleScrollTop;
284
+ if (immediateTabList) immediateTabList.scrollLeft = loopScrollLeft;
278
285
  requestAnimationFrame(() => {
279
286
  if (renderVersion !== runtimeRenderVersion) return;
280
287
  const scheduleList = section.querySelector(".runtime-schedule-list");
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":"백그라운드 터미널에서 재개하고 보내기","en":"Resume in a background terminal and send ↵","zh-CN":"在后台终端恢复并发送 ↵"},
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":"대화에 메시지를 보냈습니다. 세션 연결과 AI 응답은 백그라운드에서 이어집니다.","en":"Sent the message. Session reconnection and the AI response will continue in the background.","zh-CN":"消息已发送。会话重连和 AI 回复将在后台继续。"},
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":"项目组展开和折叠控件"},
@@ -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>