newmark-agent 0.6.3 → 0.6.4
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/dist/conversation-utility-host.bundle.cjs +206 -9
- package/dist/core/agent.d.ts +23 -0
- package/dist/core/agent.js +60 -4
- package/dist/core/agentKernelRunner.js +15 -2
- package/dist/core/branchIdentity.d.ts +29 -0
- package/dist/core/branchIdentity.js +54 -0
- package/dist/core/continuation/contracts.d.ts +1 -1
- package/dist/core/continuation/store.d.ts +28 -0
- package/dist/core/continuation/store.js +89 -1
- package/dist/core/conversationKernel.d.ts +13 -1
- package/dist/core/conversationKernel.js +62 -0
- package/dist/main.js +1 -1
- package/dist/ui/index.html +343 -44
- package/dist/wsl-agent-host.bundle.cjs +206 -9
- package/package.json +4 -2
package/dist/ui/index.html
CHANGED
|
@@ -3373,6 +3373,19 @@ button.todo-item { background: transparent; }
|
|
|
3373
3373
|
.queue-item.dragging { opacity: 0.58; transform: scale(0.99); }
|
|
3374
3374
|
.queue-item.queue-drag-displaced { transition: transform 120ms var(--ease-out-expo); }
|
|
3375
3375
|
|
|
3376
|
+
/* dev-0.6.4: 该排队行绑定的是另一条分页,正在等待自己的运行分支。 */
|
|
3377
|
+
.queue-item[data-queue-branch-waiting="1"] { opacity: 0.68; }
|
|
3378
|
+
.queue-item[data-queue-branch-waiting="1"] .queue-edit { font-style: italic; }
|
|
3379
|
+
|
|
3380
|
+
/* dev-0.6.4: 该排队行被失败的前置 Build 阻断,恢复队列会重新挂载到已提交 frontier。 */
|
|
3381
|
+
.queue-item[data-queue-blocked="1"] .queue-edit { color: var(--danger, #e06c75); }
|
|
3382
|
+
.queue-item[data-queue-blocked="1"]::after {
|
|
3383
|
+
content: '!';
|
|
3384
|
+
color: var(--danger, #e06c75);
|
|
3385
|
+
font: 10px var(--font-mono);
|
|
3386
|
+
}
|
|
3387
|
+
|
|
3388
|
+
|
|
3376
3389
|
.queue-drag {
|
|
3377
3390
|
width: 22px;
|
|
3378
3391
|
height: 22px;
|
|
@@ -7211,6 +7224,8 @@ var state = {
|
|
|
7211
7224
|
pendingOptions: [],
|
|
7212
7225
|
pendingOptionAnswers: {},
|
|
7213
7226
|
pendingOptionBatchKey: '',
|
|
7227
|
+
pendingOptionsByTarget: {},
|
|
7228
|
+
pendingOptionAnswersByTarget: {},
|
|
7214
7229
|
promptAttachments: [],
|
|
7215
7230
|
nextQueue: [],
|
|
7216
7231
|
nextQueueRequests: [],
|
|
@@ -7221,6 +7236,7 @@ var state = {
|
|
|
7221
7236
|
backendQueue: { steering: [], followUp: [] },
|
|
7222
7237
|
backendQueuesByTarget: {},
|
|
7223
7238
|
queueItemsByTarget: {},
|
|
7239
|
+
pendingDrainRefreshByTarget: {},
|
|
7224
7240
|
defaultFlow: '',
|
|
7225
7241
|
settingsActiveTab: 'general',
|
|
7226
7242
|
globalAgentPrompt: '',
|
|
@@ -9954,8 +9970,12 @@ window.submitUserMessageEdit = async function(message, messageIndex, editedText,
|
|
|
9954
9970
|
if (!isActiveConversationTarget(target)) return;
|
|
9955
9971
|
clearAgentWorkEvents(conversationId);
|
|
9956
9972
|
resetConversationWorkUi(conversationId, false);
|
|
9957
|
-
|
|
9958
|
-
|
|
9973
|
+
// dev-0.6.4: 生成分页时绝不按新分支的清空队列覆盖本地投影。排队行是用户
|
|
9974
|
+
// 已经提交的输入,属于它受理时捕获的那条分支;旧逻辑在这里把队列整体
|
|
9975
|
+
// 清零,于是分页之后排队内容从界面上消失。内核的执行投影仍然只跟随当前
|
|
9976
|
+
// 激活分支(新分支绝不接管另一页的排队输入),刷新由切回该分页时的
|
|
9977
|
+
// 权威快照完成;这里只保留可见性并标记「等待所属分页」。
|
|
9978
|
+
if (result && Array.isArray(result.queueItems)) setQueueItemsForTarget(result.queueItems, target);
|
|
9959
9979
|
state.conversationBranches = Array.isArray(result.branches) ? result.branches : [];
|
|
9960
9980
|
state.conversationBranchGroups = Array.isArray(result.branchGroups) ? result.branchGroups : [];
|
|
9961
9981
|
state.activeConversationBranchId = String(result.activeBranchId || '');
|
|
@@ -10654,6 +10674,47 @@ function runtimeKeyFor(workspaceId, conversationId) {
|
|
|
10654
10674
|
return state.runtimeKeyAliases && state.runtimeKeyAliases[base] || base;
|
|
10655
10675
|
}
|
|
10656
10676
|
|
|
10677
|
+
// dev-0.6.4: 全对话 id(分支身份)。用户分页编辑产生的分支与实验性「分支交流」
|
|
10678
|
+
// 产生的分支共用这一条规则:`<conversationId>::branch:<branchNodeId>`。
|
|
10679
|
+
// 供应商会话序列 id 与所有分支级缓存都必须使用它;兄弟分支绝不共享消息、
|
|
10680
|
+
// Build 列表或折叠历史。
|
|
10681
|
+
function conversationBranchIdentity(conversationId, branchNodeId) {
|
|
10682
|
+
var conversation = String(conversationId || 'default') || 'default';
|
|
10683
|
+
var branch = String(branchNodeId || '');
|
|
10684
|
+
return branch ? conversation + '::branch:' + branch : conversation;
|
|
10685
|
+
}
|
|
10686
|
+
|
|
10687
|
+
// 记录每个会话最近一次「被浏览」的分支节点,供后台会话的缓存键使用。
|
|
10688
|
+
function rememberViewedBranchNodeId(target, branchNodeId) {
|
|
10689
|
+
var owner = normalizeQueuedConversationTarget(target);
|
|
10690
|
+
if (!owner) return '';
|
|
10691
|
+
if (!state.viewedBranchNodeIdsByTarget) state.viewedBranchNodeIdsByTarget = {};
|
|
10692
|
+
var key = runtimeKeyFor(owner.workspaceId, owner.conversationId);
|
|
10693
|
+
var branch = String(branchNodeId || '');
|
|
10694
|
+
if (branch) state.viewedBranchNodeIdsByTarget[key] = branch;
|
|
10695
|
+
return String(state.viewedBranchNodeIdsByTarget[key] || '');
|
|
10696
|
+
}
|
|
10697
|
+
|
|
10698
|
+
function viewedBranchNodeIdFor(target) {
|
|
10699
|
+
var owner = normalizeQueuedConversationTarget(target || currentConversationTarget());
|
|
10700
|
+
if (!owner) return '';
|
|
10701
|
+
if (isActiveConversationTarget(owner)) {
|
|
10702
|
+
var path = Array.isArray(state.viewedConversationBranchNodePath) ? state.viewedConversationBranchNodePath : [];
|
|
10703
|
+
var leaf = String(path[path.length - 1] || state.activeConversationBranchId || '');
|
|
10704
|
+
if (leaf) return rememberViewedBranchNodeId(owner, leaf);
|
|
10705
|
+
}
|
|
10706
|
+
if (!state.viewedBranchNodeIdsByTarget) return '';
|
|
10707
|
+
return String(state.viewedBranchNodeIdsByTarget[runtimeKeyFor(owner.workspaceId, owner.conversationId)] || '');
|
|
10708
|
+
}
|
|
10709
|
+
|
|
10710
|
+
// 分支级缓存键:同一会话的两个分页绝不共用一份缓存。
|
|
10711
|
+
function branchConversationViewKey(target) {
|
|
10712
|
+
var owner = normalizeQueuedConversationTarget(target || currentConversationTarget());
|
|
10713
|
+
if (!owner) return '';
|
|
10714
|
+
var branch = viewedBranchNodeIdFor(owner) || 'root';
|
|
10715
|
+
return runtimeKeyFor(owner.workspaceId, owner.conversationId) + '::branch::' + branch;
|
|
10716
|
+
}
|
|
10717
|
+
|
|
10657
10718
|
function registerRuntimeKey(target, runtimeKey) {
|
|
10658
10719
|
runtimeKey = String(runtimeKey || '');
|
|
10659
10720
|
if (!target || !runtimeKey) return runtimeKeyFor(target && target.workspaceId, target && target.conversationId);
|
|
@@ -10811,6 +10872,11 @@ function renderPendingGuideMessages(target, persistedGuideIds) {
|
|
|
10811
10872
|
var messages = guideMessagesForTarget(target);
|
|
10812
10873
|
var pending = Object.keys(messages).map(function(id) { return messages[id]; }).filter(function(item) {
|
|
10813
10874
|
if (!item || persistedGuideIds[item.clientMessageId]) return false;
|
|
10875
|
+
// dev-0.6.4: Guide 只有被内核接收后才出现在时间线上,并且显示在接收位置。
|
|
10876
|
+
// 乐观/待确认(awaitingAck)与 deferred/rejected 的 Guide 都不进入对话区。
|
|
10877
|
+
if (item.awaitingAck) return false;
|
|
10878
|
+
var guideStatus = String(item.status || '').toLowerCase();
|
|
10879
|
+
if (['accepted', 'applied'].indexOf(guideStatus) < 0) return false;
|
|
10814
10880
|
// A Guide already owned by its work run is rendered by the Build block
|
|
10815
10881
|
// (expanded events or collapsed guide list), never as a separate trailing
|
|
10816
10882
|
// message. Without this guard, collapse/expand rebuilds would re-add the
|
|
@@ -10829,26 +10895,6 @@ function renderPendingGuideMessages(target, persistedGuideIds) {
|
|
|
10829
10895
|
element.remove();
|
|
10830
10896
|
element = null;
|
|
10831
10897
|
}
|
|
10832
|
-
if (item.awaitingAck && item.runId) {
|
|
10833
|
-
var runElement = findWorkRunElement({ runId: item.runId });
|
|
10834
|
-
var runBody = runElement && runElement.querySelector('.conversation-work-run-body');
|
|
10835
|
-
if (runBody) {
|
|
10836
|
-
var pendingStack = runBody.querySelector(':scope > .work-run-pending-guides');
|
|
10837
|
-
if (!pendingStack) {
|
|
10838
|
-
pendingStack = document.createElement('div');
|
|
10839
|
-
pendingStack.className = 'work-run-pending-guides';
|
|
10840
|
-
runBody.appendChild(pendingStack);
|
|
10841
|
-
}
|
|
10842
|
-
if (!element || !pendingStack.contains(element)) {
|
|
10843
|
-
var holder = document.createElement('div');
|
|
10844
|
-
holder.innerHTML = renderWorkRunGuideMessage(Object.assign({}, item, { status: 'accepted' }));
|
|
10845
|
-
element = holder.firstElementChild;
|
|
10846
|
-
if (element) pendingStack.appendChild(element);
|
|
10847
|
-
}
|
|
10848
|
-
if (element) applyGuideMessageMeta(element, item);
|
|
10849
|
-
return;
|
|
10850
|
-
}
|
|
10851
|
-
}
|
|
10852
10898
|
if (element) applyGuideMessageMeta(element, item);
|
|
10853
10899
|
else addMsg('user', item.content || 'Guide', 'guide', item.model || state.model, undefined, {
|
|
10854
10900
|
clientMessageId: item.clientMessageId,
|
|
@@ -11258,13 +11304,15 @@ function workRunBeforeElement(run) {
|
|
|
11258
11304
|
|
|
11259
11305
|
function workRunsForTarget(target) {
|
|
11260
11306
|
target = target || currentConversationTarget();
|
|
11261
|
-
|
|
11262
|
-
|
|
11263
|
-
|
|
11307
|
+
// 读取「当前浏览的分支节点」自己的 Build 列表。`workRunsByTarget` 只是
|
|
11308
|
+
// 兼容性的展示镜像,按会话共享会让另一个分页的运行结果覆盖本页(新发送的
|
|
11309
|
+
// 消息看起来消失),因此渲染路径一律走按分支节点存储的账本。
|
|
11310
|
+
var viewedBranchId = viewedBranchNodeIdFor(target);
|
|
11311
|
+
if (!viewedBranchId) {
|
|
11264
11312
|
var branchIds = conversationBranchIdsForTarget(target);
|
|
11265
|
-
|
|
11313
|
+
viewedBranchId = branchIds.viewed;
|
|
11266
11314
|
}
|
|
11267
|
-
return
|
|
11315
|
+
return workRunsForBranch(target, viewedBranchId);
|
|
11268
11316
|
}
|
|
11269
11317
|
|
|
11270
11318
|
function syncWorkRunsSnapshot(runs, target, branchId) {
|
|
@@ -11642,9 +11690,12 @@ function renderWorkRunGuideMessage(event) {
|
|
|
11642
11690
|
var guideId = String(guide.guideId || event.guideId || '');
|
|
11643
11691
|
var messageIndex = Number(state.guideMessageIndexByClientId && state.guideMessageIndexByClientId[clientMessageId]);
|
|
11644
11692
|
var indexed = Number.isFinite(messageIndex) && messageIndex >= 0;
|
|
11693
|
+
// dev-0.6.4: 时间线里的 Guide 记录不再提供编辑入口。Guide 是当前 Build 的
|
|
11694
|
+
// 历史干预记录,改写它既不会重放 Build,也会让分支归属变得含糊;这里只保留
|
|
11695
|
+
// 复制动作,消息索引属性仍然写回,供锚点/诊断使用。
|
|
11645
11696
|
return '<div class="chat-msg user work-run-guide-message" data-client-message-id="' + escAttr(clientMessageId) + '" data-guide-id="' + escAttr(guideId) + '" data-run-id="' + escAttr(String(guide.runId || event.runId || '')) + '"' + (indexed ? ' data-message-index="' + messageIndex + '"' : '') + '>' +
|
|
11646
11697
|
'<div class="meta"><span class="msg-role">' + esc(t('message.user')) + '</span>' + (time ? '<span> | ' + esc(time) + '</span>' : '') +
|
|
11647
|
-
'<span class="guide-receipt-status ' + escAttr(status) + '">Guide · ' + esc(guideUiStatusLabel(status)) + '</span>' + messageActionsHtml('user', content,
|
|
11698
|
+
'<span class="guide-receipt-status ' + escAttr(status) + '">Guide · ' + esc(guideUiStatusLabel(status)) + '</span>' + messageActionsHtml('user', content, -1) + '</div>' +
|
|
11648
11699
|
'<div class="msg-body">' + renderMessageContent(content) + '</div></div>';
|
|
11649
11700
|
}
|
|
11650
11701
|
|
|
@@ -12173,6 +12224,12 @@ function applyAgentWorkEventToRun(event) {
|
|
|
12173
12224
|
if (run.userToggled !== true) run.expanded = run.status === 'error';
|
|
12174
12225
|
}
|
|
12175
12226
|
}
|
|
12227
|
+
// dev-0.6.4 hotfix: 排队出队的回合结束时,把权威转录(助手回复、最终用户行)
|
|
12228
|
+
// 刷进对话区。正常发送路径有自己的回执刷新;这里只针对后台出队的运行,
|
|
12229
|
+
// 且由 scheduleDrainedTurnTranscriptRefresh 做去抖与“无发送在飞”保护。
|
|
12230
|
+
if (['done', 'error', 'interrupted', 'force_interrupted'].indexOf(type) >= 0) {
|
|
12231
|
+
scheduleDrainedTurnTranscriptRefresh(target, []);
|
|
12232
|
+
}
|
|
12176
12233
|
if (isActiveConversationTarget(target) && (!branchIds.viewed || !eventBranchId || branchIds.viewed === eventBranchId)) {
|
|
12177
12234
|
state.workRunsByTarget[runtimeKeyFor(target.workspaceId, target.conversationId)] = runs;
|
|
12178
12235
|
var existingElement = findWorkRunElement(run);
|
|
@@ -12455,7 +12512,8 @@ function renderPersistedToolMessage(message) {
|
|
|
12455
12512
|
|
|
12456
12513
|
function conversationMessageCache(target) {
|
|
12457
12514
|
var scopedTarget = target || currentConversationTarget();
|
|
12458
|
-
|
|
12515
|
+
// 分支级缓存:分页切换时另一个页面的消息快照绝不能覆盖当前页。
|
|
12516
|
+
var key = branchConversationViewKey(scopedTarget);
|
|
12459
12517
|
if (!state.conversationMessagesByTarget) state.conversationMessagesByTarget = {};
|
|
12460
12518
|
return Array.isArray(state.conversationMessagesByTarget[key])
|
|
12461
12519
|
? state.conversationMessagesByTarget[key].map(function(message) { return Object.assign({}, message); })
|
|
@@ -12464,7 +12522,7 @@ function conversationMessageCache(target) {
|
|
|
12464
12522
|
|
|
12465
12523
|
function cacheConversationMessages(messages, target) {
|
|
12466
12524
|
var scopedTarget = target || currentConversationTarget();
|
|
12467
|
-
var key =
|
|
12525
|
+
var key = branchConversationViewKey(scopedTarget);
|
|
12468
12526
|
var cloned = Array.isArray(messages) ? messages.map(function(message) { return Object.assign({}, message); }) : [];
|
|
12469
12527
|
if (!state.conversationMessagesByTarget) state.conversationMessagesByTarget = {};
|
|
12470
12528
|
state.conversationMessagesByTarget[key] = cloned;
|
|
@@ -12745,9 +12803,20 @@ function renderConversationBranchPagers() {
|
|
|
12745
12803
|
if (matched) anchor = candidate;
|
|
12746
12804
|
return matched;
|
|
12747
12805
|
});
|
|
12748
|
-
if (!anchor
|
|
12806
|
+
if (!anchor) {
|
|
12807
|
+
// dev-0.6.4: 分页锚点回退。Build 开头的用户消息可能以内联 Build/Guide
|
|
12808
|
+
// 卡片渲染,没有写回 data-message-id;旧逻辑此时直接放弃,导致「从 build
|
|
12809
|
+
// 开头编辑」创建的分页看不到分页条。现在按渲染索引,再按锚点文本回退。
|
|
12749
12810
|
var sourceMessageIndex = Math.max(0, Number(branchGroup.sourceMessageIndex === undefined ? activeBranch.sourceMessageIndex : branchGroup.sourceMessageIndex));
|
|
12750
12811
|
anchor = els['chat-area'].querySelector('.chat-msg.user[data-message-index="' + sourceMessageIndex + '"]');
|
|
12812
|
+
if (!anchor && String(activeBranch.sourceText || '').trim()) {
|
|
12813
|
+
var wantedAnchorText = String(activeBranch.sourceText || '').trim();
|
|
12814
|
+
Array.prototype.some.call(els['chat-area'].querySelectorAll('.chat-msg.user'), function(candidate) {
|
|
12815
|
+
var candidateText = String(candidate && candidate._newmarkMessageText || '').trim();
|
|
12816
|
+
if (candidateText && candidateText === wantedAnchorText) { anchor = candidate; return true; }
|
|
12817
|
+
return false;
|
|
12818
|
+
});
|
|
12819
|
+
}
|
|
12751
12820
|
}
|
|
12752
12821
|
if (anchor) {
|
|
12753
12822
|
var pager = document.createElement('div');
|
|
@@ -13288,6 +13357,9 @@ function hydrateConversationBranchState(snapshot) {
|
|
|
13288
13357
|
if (Object.prototype.hasOwnProperty.call(s, 'branchGroupId')) state.activeConversationBranchGroupId = String(s.branchGroupId || '');
|
|
13289
13358
|
if (Object.prototype.hasOwnProperty.call(s, 'viewedBranchNodePath')) state.viewedConversationBranchNodePath = Array.isArray(s.viewedBranchNodePath) ? s.viewedBranchNodePath.map(String) : [];
|
|
13290
13359
|
if (Object.prototype.hasOwnProperty.call(s, 'runtimeBranchNodePath')) state.runtimeConversationBranchNodePath = Array.isArray(s.runtimeBranchNodePath) ? s.runtimeBranchNodePath.map(String) : [];
|
|
13360
|
+
// 分支级缓存必须知道「当前浏览的是哪个节点」,否则换页后另一页的快照会覆盖本页。
|
|
13361
|
+
var viewedLeaf = String((state.viewedConversationBranchNodePath || []).slice(-1)[0] || state.activeConversationBranchId || '');
|
|
13362
|
+
rememberViewedBranchNodeId(currentConversationTarget(s.conversationId || activeConversationId()), viewedLeaf);
|
|
13291
13363
|
rememberRuntimeConversationBranchPath(currentConversationTarget(s.conversationId || activeConversationId()));
|
|
13292
13364
|
}
|
|
13293
13365
|
|
|
@@ -13449,10 +13521,54 @@ function optionQuestionKey(question, options, index) {
|
|
|
13449
13521
|
return JSON.stringify({ index: index, question: question || '', options: (options || []).map(optionLabel) });
|
|
13450
13522
|
}
|
|
13451
13523
|
|
|
13452
|
-
|
|
13524
|
+
// dev-0.6.4 hotfix: 选项块携带归属会话,作答必须回到提问的那个会话。
|
|
13525
|
+
function optionBlockOwner(element) {
|
|
13526
|
+
var block = element && element.closest ? element.closest('[data-option-owner-conversation]') : null;
|
|
13527
|
+
if (!block) return currentConversationTarget();
|
|
13528
|
+
return {
|
|
13529
|
+
workspaceId: String(block.getAttribute('data-option-owner-workspace') || '') || runtimeWorkspaceId(''),
|
|
13530
|
+
conversationId: String(block.getAttribute('data-option-owner-conversation') || '') || activeConversationId()
|
|
13531
|
+
};
|
|
13532
|
+
}
|
|
13533
|
+
|
|
13534
|
+
function pendingOptionsForTarget(target) {
|
|
13535
|
+
var owner = normalizeQueuedConversationTarget(target || currentConversationTarget());
|
|
13536
|
+
if (!owner) return [];
|
|
13537
|
+
if (!state.pendingOptionsByTarget) state.pendingOptionsByTarget = {};
|
|
13538
|
+
var key = runtimeKeyFor(owner.workspaceId, owner.conversationId);
|
|
13539
|
+
return Array.isArray(state.pendingOptionsByTarget[key]) ? state.pendingOptionsByTarget[key] : [];
|
|
13540
|
+
}
|
|
13541
|
+
|
|
13542
|
+
function setPendingOptionsForTarget(options, target) {
|
|
13543
|
+
var owner = normalizeQueuedConversationTarget(target || currentConversationTarget());
|
|
13544
|
+
if (!owner) return;
|
|
13545
|
+
if (!state.pendingOptionsByTarget) state.pendingOptionsByTarget = {};
|
|
13546
|
+
var key = runtimeKeyFor(owner.workspaceId, owner.conversationId);
|
|
13547
|
+
state.pendingOptionsByTarget[key] = (Array.isArray(options) ? options : []).map(function(question) {
|
|
13548
|
+
return {
|
|
13549
|
+
id: String(question && question.id || ''),
|
|
13550
|
+
question: String(question && question.question || ''),
|
|
13551
|
+
options: (Array.isArray(question && question.options) ? question.options : []).map(function(option) {
|
|
13552
|
+
return typeof option === 'string' ? option : { label: String(option && option.label || ''), description: String(option && option.description || '') };
|
|
13553
|
+
})
|
|
13554
|
+
};
|
|
13555
|
+
});
|
|
13556
|
+
if (isActiveConversationTarget(owner)) state.pendingOptions = state.pendingOptionsByTarget[key];
|
|
13557
|
+
}
|
|
13558
|
+
|
|
13559
|
+
function addOptionBlock(question, options, questionKey, ownerAttributes) {
|
|
13453
13560
|
var div = document.createElement('div');
|
|
13454
13561
|
div.className = 'chat-msg assistant';
|
|
13455
13562
|
div.setAttribute('data-option-question', encodeURIComponent(questionKey));
|
|
13563
|
+
if (ownerAttributes) {
|
|
13564
|
+
var ownerHolder = document.createElement('div');
|
|
13565
|
+
ownerHolder.innerHTML = '<span' + ownerAttributes + '></span>';
|
|
13566
|
+
var ownerNode = ownerHolder.firstElementChild;
|
|
13567
|
+
if (ownerNode) {
|
|
13568
|
+
div.setAttribute('data-option-owner-workspace', ownerNode.getAttribute('data-option-owner-workspace') || '');
|
|
13569
|
+
div.setAttribute('data-option-owner-conversation', ownerNode.getAttribute('data-option-owner-conversation') || '');
|
|
13570
|
+
}
|
|
13571
|
+
}
|
|
13456
13572
|
var html = '<div class="option-block"><div class="option-question">' + esc(question) + '</div>';
|
|
13457
13573
|
for (var i = 0; i < options.length; i++) {
|
|
13458
13574
|
var label = optionLabel(options[i]);
|
|
@@ -13471,11 +13587,18 @@ function addOptionBlock(question, options, questionKey) {
|
|
|
13471
13587
|
}
|
|
13472
13588
|
|
|
13473
13589
|
function renderPendingOptionsInChat(options) {
|
|
13590
|
+
// dev-0.6.4 hotfix: 选项提问按会话归属。旧实现把全局 pendingOptions 渲染到当前
|
|
13591
|
+
// 对话区,后台会话的提问会“溢出”到前台对话,点击后还会用当前会话发送答案,
|
|
13592
|
+
// 等于把提问激活给了另一个对话。
|
|
13593
|
+
if (options === undefined) options = pendingOptionsForTarget(currentConversationTarget());
|
|
13474
13594
|
options = options || [];
|
|
13475
13595
|
if (!options.length) return;
|
|
13596
|
+
var owner = currentConversationTarget();
|
|
13597
|
+
var ownerAttributes = ' data-option-owner-workspace="' + escAttr(String(owner.workspaceId || '')) + '" data-option-owner-conversation="' + escAttr(String(owner.conversationId || 'default')) + '"';
|
|
13476
13598
|
var batchKey = JSON.stringify(options.map(function(q, index) { return optionQuestionKey(q.question || q.id || '', q.options || [], index); }));
|
|
13477
|
-
|
|
13478
|
-
|
|
13599
|
+
var batchKeyWithOwner = String(owner.workspaceId || '') + '::' + String(owner.conversationId || 'default') + '::' + batchKey;
|
|
13600
|
+
if (state.pendingOptionBatchKey !== batchKeyWithOwner) {
|
|
13601
|
+
state.pendingOptionBatchKey = batchKeyWithOwner;
|
|
13479
13602
|
state.pendingOptionAnswers = {};
|
|
13480
13603
|
}
|
|
13481
13604
|
for (var i = 0; i < options.length; i++) {
|
|
@@ -13485,7 +13608,7 @@ function renderPendingOptionsInChat(options) {
|
|
|
13485
13608
|
var encodedKey = encodeURIComponent(key);
|
|
13486
13609
|
if (els['chat-area'] && els['chat-area'].querySelector('[data-option-question="' + encodedKey + '"]')) continue;
|
|
13487
13610
|
state.renderedOptionKeys[key] = true;
|
|
13488
|
-
addOptionBlock(q.question || q.id || t('status.question'), q.options || [], key);
|
|
13611
|
+
addOptionBlock(q.question || q.id || t('status.question'), q.options || [], key, ownerAttributes);
|
|
13489
13612
|
}
|
|
13490
13613
|
}
|
|
13491
13614
|
|
|
@@ -13544,6 +13667,15 @@ function handlePlanExecutionChoice(questionKey, opt) {
|
|
|
13544
13667
|
|
|
13545
13668
|
window.optionSelected = function(questionKey, opt, button) {
|
|
13546
13669
|
if (!questionKey || state.pendingOptionAnswers[questionKey] !== undefined) return;
|
|
13670
|
+
// dev-0.6.4 hotfix: 作答必须回到提问的那个会话;后台会话的提问不允许借用
|
|
13671
|
+
// 当前对话的输入框发送(旧实现用 window.sendMessage() 直接发到当前会话)。
|
|
13672
|
+
var answerOwner = optionBlockOwner(button);
|
|
13673
|
+
if (!isActiveConversationTarget(answerOwner)) {
|
|
13674
|
+
showUiNotice(currentLang() === 'zh'
|
|
13675
|
+
? '该提问属于另一个对话,请先切换到那个对话再作答。'
|
|
13676
|
+
: 'That question belongs to another conversation; switch to it before answering.', 'error', 'option-owner-mismatch');
|
|
13677
|
+
return;
|
|
13678
|
+
}
|
|
13547
13679
|
if (state.mode === 'plan' && handlePlanExecutionChoice(questionKey, opt)) {
|
|
13548
13680
|
var planBlock = button && button.closest ? button.closest('[data-option-question]') : null;
|
|
13549
13681
|
if (planBlock) Array.from(planBlock.querySelectorAll('.option-btn')).forEach(function(candidate) {
|
|
@@ -13560,7 +13692,7 @@ window.optionSelected = function(questionKey, opt, button) {
|
|
|
13560
13692
|
candidate.classList.toggle('selected', candidate === button);
|
|
13561
13693
|
});
|
|
13562
13694
|
}
|
|
13563
|
-
var questions =
|
|
13695
|
+
var questions = pendingOptionsForTarget(answerOwner);
|
|
13564
13696
|
var answered = questions.map(function(q, index) {
|
|
13565
13697
|
var key = optionQuestionKey(q.question || q.id || '', q.options || [], index);
|
|
13566
13698
|
return { question: q.question || q.id || t('status.question'), answer: state.pendingOptionAnswers[key] };
|
|
@@ -13581,6 +13713,13 @@ window.optionSelected = function(questionKey, opt, button) {
|
|
|
13581
13713
|
};
|
|
13582
13714
|
|
|
13583
13715
|
window.optionManualSubmitted = function(element) {
|
|
13716
|
+
var manualOwner = optionBlockOwner(element);
|
|
13717
|
+
if (!isActiveConversationTarget(manualOwner)) {
|
|
13718
|
+
showUiNotice(currentLang() === 'zh'
|
|
13719
|
+
? '该提问属于另一个对话,请先切换到那个对话再作答。'
|
|
13720
|
+
: 'That question belongs to another conversation; switch to it before answering.', 'error', 'option-owner-mismatch');
|
|
13721
|
+
return;
|
|
13722
|
+
}
|
|
13584
13723
|
var manual = element && element.closest ? element.closest('.option-manual') : null;
|
|
13585
13724
|
if (!manual) return;
|
|
13586
13725
|
var input = manual.querySelector('.option-manual-input');
|
|
@@ -17273,13 +17412,24 @@ function queueIndexesForTarget(target) {
|
|
|
17273
17412
|
var requests = state.nextQueueRequests || [];
|
|
17274
17413
|
for (var index = 0; index < queue.length; index++) {
|
|
17275
17414
|
if (!queuedRequestMatchesTarget(requests[index], target)) continue;
|
|
17276
|
-
|
|
17277
|
-
|
|
17278
|
-
|
|
17415
|
+
// dev-0.6.4: 排队行永远可见。它属于「受理时捕获的分支」,但它是这个会话里
|
|
17416
|
+
// 真实存在的用户输入;旧逻辑按当前运行分支过滤,分页之后整队行被隐形,
|
|
17417
|
+
// 用户既看不到内容也无法恢复。可见性与行绑定的分支解耦(分支只决定它
|
|
17418
|
+
// 何时、在哪一页执行)。
|
|
17419
|
+
indexes.push(index);
|
|
17279
17420
|
}
|
|
17280
17421
|
return indexes;
|
|
17281
17422
|
}
|
|
17282
17423
|
|
|
17424
|
+
// 行绑定的分支与当前运行分支不一致时,该行会等待(内核会暂停而不是改投)。
|
|
17425
|
+
// 这个状态必须在界面上可见,用户才能切回对应分页再恢复队列。
|
|
17426
|
+
function queueRequestBranchMismatch(target, request) {
|
|
17427
|
+
var requestBranchPath = String(request && request.branchPath || '');
|
|
17428
|
+
var runtimeBranchPath = queueBranchPathForTarget(target, 'runtime');
|
|
17429
|
+
if (!requestBranchPath || !runtimeBranchPath) return false;
|
|
17430
|
+
return requestBranchPath !== runtimeBranchPath;
|
|
17431
|
+
}
|
|
17432
|
+
|
|
17283
17433
|
function queueIndexesForRuntimeTarget(target) {
|
|
17284
17434
|
var indexes = [];
|
|
17285
17435
|
var queue = state.nextQueue || [];
|
|
@@ -17312,7 +17462,9 @@ function rebindQueueToRuntimeBranch(target) {
|
|
|
17312
17462
|
if (!runtimePath || !key) return;
|
|
17313
17463
|
(state.nextQueueRequests || []).forEach(function(request) {
|
|
17314
17464
|
if (!request || !queuedRequestMatchesTarget(request, target)) return;
|
|
17315
|
-
|
|
17465
|
+
// dev-0.6.4: 只刷新会话 runtime 身份,绝不改写受理时捕获的分支路径。
|
|
17466
|
+
// 静默改绑会把排队输入从用户提交的分页挪到另一条分支,正是
|
|
17467
|
+
// 「跨分支复用/串分支」必须禁止的行为。
|
|
17316
17468
|
request.runtimeKey = key;
|
|
17317
17469
|
});
|
|
17318
17470
|
}
|
|
@@ -17377,6 +17529,21 @@ function setQueueItemsForTarget(items, target) {
|
|
|
17377
17529
|
runId: String(item && item.runId || ''),
|
|
17378
17530
|
createdAt: String(item && item.createdAt || ''),
|
|
17379
17531
|
images: (Array.isArray(item && item.images) ? item.images : []).map(function(image) { return Object.assign({}, image); }),
|
|
17532
|
+
// dev-0.6.4: 保留受理时捕获的分支身份。旧实现只保留展示字段,排队行在
|
|
17533
|
+
// 重新投影时会被默认绑到「当前运行分支」,等于静默改绑到另一条分页,
|
|
17534
|
+
// 也让「等待所属分页」的状态永远无法显示。
|
|
17535
|
+
branchNodeId: String(item && item.branchNodeId || ''),
|
|
17536
|
+
branchPath: Array.isArray(item && item.branchPath)
|
|
17537
|
+
? item.branchPath.map(function(value) { return String(value || ''); }).filter(Boolean)
|
|
17538
|
+
: (item && item.branchPath ? [String(item.branchPath)] : []),
|
|
17539
|
+
ledgerBranchId: String(item && item.ledgerBranchId || ''),
|
|
17540
|
+
targetRuntimeKey: String(item && item.targetRuntimeKey || ''),
|
|
17541
|
+
workspaceKey: String(item && item.workspaceKey || ''),
|
|
17542
|
+
modelSelection: String(item && item.modelSelection || ''),
|
|
17543
|
+
// dev-0.6.4 hotfix: 保留账本给出的受阻原因,界面才能显示“被失败前置阻断”
|
|
17544
|
+
// 并提供显式的修复入口。
|
|
17545
|
+
waitingReason: String(item && item.waitingReason || ''),
|
|
17546
|
+
blocked: item && item.blocked === true,
|
|
17380
17547
|
};
|
|
17381
17548
|
});
|
|
17382
17549
|
}
|
|
@@ -17412,7 +17579,23 @@ function applyConversationCommandSnapshot(snapshot, target) {
|
|
|
17412
17579
|
if (!state.queuePausedByTarget) state.queuePausedByTarget = {};
|
|
17413
17580
|
state.queuePausedByTarget[key] = snapshot.queuePaused;
|
|
17414
17581
|
}
|
|
17415
|
-
if (Array.isArray(snapshot.queueItems))
|
|
17582
|
+
if (Array.isArray(snapshot.queueItems)) {
|
|
17583
|
+
// dev-0.6.4 hotfix: 排队行真正进入对话(出队)时,后端会把这一轮写成正式用户
|
|
17584
|
+
// 消息,但渲染端不会收到发送回执,因此此前对话区一直不显示这条用户输入。
|
|
17585
|
+
// 这里比较权威队列的条数变化,检测到出队就刷新一次转录。
|
|
17586
|
+
var previousQueueItems = queueItemsForTarget(target);
|
|
17587
|
+
var previousQueueIds = previousQueueItems.map(function(item) { return String(item && item.id || ''); }).filter(Boolean);
|
|
17588
|
+
var nextQueueIds = snapshot.queueItems.map(function(item) { return String(item && item.id || ''); }).filter(Boolean);
|
|
17589
|
+
var drainedIds = previousQueueIds.filter(function(id) { return nextQueueIds.indexOf(id) < 0; });
|
|
17590
|
+
setQueueItemsForTarget(snapshot.queueItems, target);
|
|
17591
|
+
if (drainedIds.length) scheduleDrainedTurnTranscriptRefresh(target, drainedIds);
|
|
17592
|
+
}
|
|
17593
|
+
if (Array.isArray(snapshot.pendingOptions)) setPendingOptionsForTarget(snapshot.pendingOptions, target);
|
|
17594
|
+
// dev-0.6.4: goal 停止/清除后,任何携带 goal 字段的权威快照都要同步前端显示。
|
|
17595
|
+
if (Object.prototype.hasOwnProperty.call(snapshot, 'goal') && isActiveConversationTarget(target)) {
|
|
17596
|
+
applyReturnedGoalState(snapshot);
|
|
17597
|
+
if (window.renderInputStack) window.renderInputStack();
|
|
17598
|
+
}
|
|
17416
17599
|
var queue = snapshot.queued && typeof snapshot.queued === 'object' ? snapshot.queued : snapshot.queue;
|
|
17417
17600
|
if (queue && typeof queue === 'object') {
|
|
17418
17601
|
setBackendQueueForTarget(queue, target);
|
|
@@ -17449,6 +17632,55 @@ function applyConversationCommandSnapshot(snapshot, target) {
|
|
|
17449
17632
|
}
|
|
17450
17633
|
}
|
|
17451
17634
|
|
|
17635
|
+
/**
|
|
17636
|
+
* dev-0.6.4 hotfix: 排队输入出队进入对话后,把权威转录刷新到对话区。
|
|
17637
|
+
*
|
|
17638
|
+
* 走发送路径的回合有本地乐观气泡 + 回执刷新;排队回合由内核在后台启动,渲染端
|
|
17639
|
+
* 既没有乐观气泡也没有回执,于是用户输入在对话区“消失”。这里在检测到排队行
|
|
17640
|
+
* 出队时按目标做一次去抖刷新:只在当前前台会话、且没有渲染端发送在飞时执行,
|
|
17641
|
+
* 避免与发送回执互相覆盖。
|
|
17642
|
+
*/
|
|
17643
|
+
function scheduleDrainedTurnTranscriptRefresh(target, drainedIds) {
|
|
17644
|
+
var owner = normalizeQueuedConversationTarget(target || currentConversationTarget());
|
|
17645
|
+
if (!owner || !api.getState) return;
|
|
17646
|
+
var key = runtimeKeyFor(owner.workspaceId, owner.conversationId);
|
|
17647
|
+
if (!isActiveConversationTarget(owner)) return;
|
|
17648
|
+
if (state.activeSendCallsByTarget && state.activeSendCallsByTarget[key]) return;
|
|
17649
|
+
if (!state.pendingDrainRefreshByTarget) state.pendingDrainRefreshByTarget = {};
|
|
17650
|
+
if (state.pendingDrainRefreshByTarget[key]) return;
|
|
17651
|
+
// 出队事件早于正式用户消息落盘,因此按几个时间点各刷新一次:首次拿到用户输入,
|
|
17652
|
+
// 后续拿到助手回复;全部完成后清除登记,下一次出队可再次触发。
|
|
17653
|
+
state.pendingDrainRefreshByTarget[key] = { ids: (drainedIds || []).slice(), timers: [] };
|
|
17654
|
+
var pending = state.pendingDrainRefreshByTarget[key];
|
|
17655
|
+
[150, 1200, 3000].forEach(function(delay, index, all) {
|
|
17656
|
+
pending.timers.push(setTimeout(function() {
|
|
17657
|
+
refreshConversationTranscriptFromBackend(owner);
|
|
17658
|
+
if (index === all.length - 1 && state.pendingDrainRefreshByTarget) delete state.pendingDrainRefreshByTarget[key];
|
|
17659
|
+
}, delay));
|
|
17660
|
+
});
|
|
17661
|
+
}
|
|
17662
|
+
|
|
17663
|
+
function refreshConversationTranscriptFromBackend(target) {
|
|
17664
|
+
var owner = normalizeQueuedConversationTarget(target || currentConversationTarget());
|
|
17665
|
+
if (!owner || !api.getState || !isActiveConversationTarget(owner)) return Promise.resolve(null);
|
|
17666
|
+
var key = runtimeKeyFor(owner.workspaceId, owner.conversationId);
|
|
17667
|
+
if (state.activeSendCallsByTarget && state.activeSendCallsByTarget[key]) return Promise.resolve(null);
|
|
17668
|
+
return Promise.resolve(api.getState(owner)).then(function(snapshot) {
|
|
17669
|
+
if (!snapshot || typeof snapshot !== 'object') return null;
|
|
17670
|
+
if (!isActiveConversationTarget(owner)) return null;
|
|
17671
|
+
if (state.activeSendCallsByTarget && state.activeSendCallsByTarget[key]) return null;
|
|
17672
|
+
// dev-0.6.4: goal 完成/停止后,前端必须在收到权威快照时同步清除 goal 显示。
|
|
17673
|
+
applyReturnedGoalState(snapshot);
|
|
17674
|
+
if (Array.isArray(snapshot.workRuns)) syncWorkRunsSnapshot(snapshot.workRuns, owner);
|
|
17675
|
+
if (Array.isArray(snapshot.chatMessages)) {
|
|
17676
|
+
hydrateConversationBranchState(snapshot);
|
|
17677
|
+
cacheConversationMessages(snapshot.chatMessages, owner);
|
|
17678
|
+
renderChatMessages(snapshot.chatMessages, owner);
|
|
17679
|
+
}
|
|
17680
|
+
return snapshot;
|
|
17681
|
+
}).catch(function() { return null; });
|
|
17682
|
+
}
|
|
17683
|
+
|
|
17452
17684
|
window.queueAction = function(action, input, target) {
|
|
17453
17685
|
if (!api.queueAction) return Promise.resolve({ ok: false, error: 'queueAction unavailable' });
|
|
17454
17686
|
var owner = normalizeQueuedConversationTarget(target || currentConversationTarget());
|
|
@@ -17549,6 +17781,15 @@ window.renderQueuePanel = function() {
|
|
|
17549
17781
|
var visibleIndexes = queueIndexesForTarget(target);
|
|
17550
17782
|
var count = visibleIndexes.length;
|
|
17551
17783
|
panel.style.display = count ? '' : 'none';
|
|
17784
|
+
// dev-0.6.4: 新排队输入必须被看见。旧行为把队列面板默认折叠,配合按分支
|
|
17785
|
+
// 过滤的隐形逻辑,用户提交的排队内容在界面上完全不出现。条数增加时自动
|
|
17786
|
+
// 展开,用户手动折叠在条数不变时仍然有效。
|
|
17787
|
+
if (!state.queueSeenCountsByTarget) state.queueSeenCountsByTarget = {};
|
|
17788
|
+
// 仅用于「新增排队行时自动展开面板」的本地记账键,不参与执行身份判定。
|
|
17789
|
+
var seenKey = String(target.workspaceId || '') + '::' + String(target.conversationId || 'default');
|
|
17790
|
+
var seenCount = Number(state.queueSeenCountsByTarget[seenKey] || 0);
|
|
17791
|
+
if (count > seenCount) state.queueCollapsed = false;
|
|
17792
|
+
state.queueSeenCountsByTarget[seenKey] = count;
|
|
17552
17793
|
panel.classList.toggle('collapsed', !!state.queueCollapsed);
|
|
17553
17794
|
label.textContent = t('queue.title') + ' ' + count;
|
|
17554
17795
|
var pauseToggle = document.getElementById('queue-pause-btn');
|
|
@@ -17592,17 +17833,73 @@ window.renderQueuePanel = function() {
|
|
|
17592
17833
|
'<button type="button" class="stack-icon-btn queue-guide-btn" onclick="window.guideQueueItem(' + eventIndex + ')" title="' + escAttr(t('queue.guideNow')) + '">' + iconSvg('corner-down-right', t('queue.guideNow'), 'tiny') + '<span>' + esc(t('queue.guideAction')) + '</span></button>' +
|
|
17593
17834
|
'<button class="stack-icon-btn" onclick="window.focusQueueItem(' + eventIndex + ')" title="' + escAttr(t('common.edit')) + '">' + iconSvg('square-pen', t('common.edit'), 'tiny') + '</button>' +
|
|
17594
17835
|
'<button class="stack-icon-btn danger" onclick="window.deleteQueueItem(' + eventIndex + ')" title="' + escAttr(t('common.delete')) + '">' + iconSvg('trash', t('common.delete'), 'tiny') + '</button>' : '';
|
|
17595
|
-
|
|
17836
|
+
// 绑定了另一条分页的行会等待而不是改投,这个状态必须在界面上显式可见,
|
|
17837
|
+
// 否则用户会以为排队输入被丢弃。
|
|
17838
|
+
var waitingForBranch = queueRequestBranchMismatch(target, request);
|
|
17839
|
+
var waitingLabel = currentLang() === 'zh'
|
|
17840
|
+
? '等待它所属的分页:切回该分页后队列可恢复执行'
|
|
17841
|
+
: 'Waiting for its own page: switch back to that page to resume the queue';
|
|
17842
|
+
// dev-0.6.4 hotfix: 被失败前置阻断的排队行必须显式可见,否则用户只会看到
|
|
17843
|
+
// “恢复后什么都不发生”。恢复队列时内核会把这些行重新挂到最后一个已提交 Build。
|
|
17844
|
+
var authoritativeItem = queueItemId
|
|
17845
|
+
? queueItemsForTarget(target).find(function(item) { return item.id === queueItemId; })
|
|
17846
|
+
: null;
|
|
17847
|
+
var blockedRow = !!(authoritativeItem && (authoritativeItem.blocked === true || authoritativeItem.waitingReason));
|
|
17848
|
+
var blockedLabel = currentLang() === 'zh'
|
|
17849
|
+
? '这一行被失败的前置 Build 阻断:按“恢复队列”会把它重新挂到最后一个已提交 Build 后执行'
|
|
17850
|
+
: 'Blocked by a failed predecessor: resuming the queue re-anchors this row to the last committed build';
|
|
17851
|
+
html += '<div class="queue-item" data-queue-index="' + queueIndex + '"' + identityAttributes
|
|
17852
|
+
+ (waitingForBranch ? ' data-queue-branch-waiting="1" title="' + escAttr(waitingLabel) + '"' : '')
|
|
17853
|
+
+ (blockedRow ? ' data-queue-blocked="1" title="' + escAttr(blockedLabel) + '"' : '')
|
|
17854
|
+
+ dragAttributes + '>' +
|
|
17596
17855
|
dragHandle +
|
|
17597
17856
|
'<input class="queue-edit" value="' + escAttr(state.nextQueue[queueIndex] || '') + '"' + inputAttributes + '>' +
|
|
17598
17857
|
'<span class="stack-actions">' + actions + '</span>' +
|
|
17599
17858
|
'</div>';
|
|
17600
17859
|
}
|
|
17601
17860
|
list.innerHTML = html;
|
|
17861
|
+
|
|
17862
|
+
// dev-0.6.4 hotfix: 存在受阻行时提供显式修复入口。0.6.3 合约要求失败前置继续
|
|
17863
|
+
// 阻断后继(不静默跳过),因此修复必须是用户主动动作,并且可见、可审计。
|
|
17864
|
+
var blockedCount = 0;
|
|
17865
|
+
for (var blockedIndex = 0; blockedIndex < visibleIndexes.length; blockedIndex++) {
|
|
17866
|
+
var blockedRequest = state.nextQueueRequests[visibleIndexes[blockedIndex]];
|
|
17867
|
+
var blockedItemId = blockedRequest ? String(blockedRequest.queueItemId || '') : '';
|
|
17868
|
+
var blockedItem = blockedItemId
|
|
17869
|
+
? queueItemsForTarget(target).find(function(item) { return item.id === blockedItemId; })
|
|
17870
|
+
: null;
|
|
17871
|
+
if (blockedItem && (blockedItem.blocked === true || blockedItem.waitingReason)) blockedCount++;
|
|
17872
|
+
}
|
|
17873
|
+
var headerActions = panel.querySelector('#queue-header .stack-actions');
|
|
17874
|
+
var repairButton = document.getElementById('queue-repair-btn');
|
|
17875
|
+
if (blockedCount) {
|
|
17876
|
+
if (!repairButton && headerActions) {
|
|
17877
|
+
repairButton = document.createElement('button');
|
|
17878
|
+
repairButton.className = 'stack-icon-btn';
|
|
17879
|
+
repairButton.id = 'queue-repair-btn';
|
|
17880
|
+
repairButton.setAttribute('type', 'button');
|
|
17881
|
+
repairButton.setAttribute('onclick', 'event.stopPropagation();window.repairBlockedQueue()');
|
|
17882
|
+
var pauseButton = document.getElementById('queue-pause-btn');
|
|
17883
|
+
if (pauseButton && pauseButton.parentNode === headerActions) headerActions.insertBefore(repairButton, pauseButton);
|
|
17884
|
+
else headerActions.appendChild(repairButton);
|
|
17885
|
+
}
|
|
17886
|
+
if (repairButton) {
|
|
17887
|
+
var repairLabel = currentLang() === 'zh' ? '修复受阻队列' : 'Repair blocked queue';
|
|
17888
|
+
repairButton.title = repairLabel;
|
|
17889
|
+
repairButton.innerHTML = iconSvg('wrench', repairLabel, 'tiny') + '<span class="icon-only-label">' + esc(repairLabel) + '</span>';
|
|
17890
|
+
}
|
|
17891
|
+
} else if (repairButton && repairButton.parentNode) {
|
|
17892
|
+
repairButton.remove();
|
|
17893
|
+
}
|
|
17602
17894
|
};
|
|
17603
17895
|
|
|
17604
17896
|
window.renderNextQueue = window.renderQueuePanel;
|
|
17605
17897
|
|
|
17898
|
+
// 显式修复受阻队列:把被失败前置阻断的排队行重新挂到最后一个已提交 Build。
|
|
17899
|
+
window.repairBlockedQueue = function() {
|
|
17900
|
+
return window.queueAction('repair_blocked', {}, currentConversationTarget());
|
|
17901
|
+
};
|
|
17902
|
+
|
|
17606
17903
|
window.toggleQueuePanel = function() {
|
|
17607
17904
|
state.queueCollapsed = !state.queueCollapsed;
|
|
17608
17905
|
window.renderInputStack();
|
|
@@ -18962,7 +19259,7 @@ window.refreshRightStatus = function(options) {
|
|
|
18962
19259
|
}
|
|
18963
19260
|
if (s && s.subagents) state.subagents = s.subagents;
|
|
18964
19261
|
if (s && s.fileDiffs) state.fileDiffs = s.fileDiffs;
|
|
18965
|
-
if (s && s.pendingOptions)
|
|
19262
|
+
if (s && s.pendingOptions) setPendingOptionsForTarget(s.pendingOptions, computerTarget || currentConversationTarget(s.conversationId || requestedConversationId || activeConversationId()));
|
|
18966
19263
|
// Bind the Flow takeover to backend per-conversation state. The takeover
|
|
18967
19264
|
// for the requested target reflects exactly what the backend holds for that
|
|
18968
19265
|
// conversation: a suspension (paused) or an active running Flow. It is
|
|
@@ -19848,7 +20145,9 @@ function applyReturnedGoalState(snapshot) {
|
|
|
19848
20145
|
if (!snapshot || !Object.prototype.hasOwnProperty.call(snapshot, 'goal')) return;
|
|
19849
20146
|
state.goalText = snapshot.goal && snapshot.goal.objective ? String(snapshot.goal.objective) : '';
|
|
19850
20147
|
state.goalVisible = !!state.goalText;
|
|
19851
|
-
|
|
20148
|
+
// dev-0.6.4: goal 结束后端会停止/清空 goal,但前端此前只清 text/visible,paused
|
|
20149
|
+
// 残留会让 goal 条继续以“暂停”状态显示。这里在无 goal 时一并清除暂停态。
|
|
20150
|
+
state.goalPaused = state.goalVisible ? !!(snapshot.goal && snapshot.goal.paused) : false;
|
|
19852
20151
|
if (!snapshot.goal && snapshot.mode === 'build') {
|
|
19853
20152
|
state.mode = 'build';
|
|
19854
20153
|
if (els['mode-select']) {
|