newmark-agent 0.3.10 → 0.3.12
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/config.example.json +6 -0
- package/dist/cli-commands.d.ts +7 -0
- package/dist/cli-commands.js +206 -15
- package/dist/cli-discovery.d.ts +15 -0
- package/dist/cli-discovery.js +182 -0
- package/dist/cli-help.d.ts +4 -0
- package/dist/cli-help.js +46 -0
- package/dist/conversation-utility-host.bundle.cjs +548 -137
- package/dist/core/agent.d.ts +18 -3
- package/dist/core/agent.js +236 -34
- package/dist/core/agentKernelRunner.js +72 -10
- package/dist/core/browserControl.d.ts +8 -0
- package/dist/core/browserUsePageAdapter.d.ts +3 -0
- package/dist/core/browserUsePageAdapter.js +19 -2
- package/dist/core/computerUseSession.d.ts +44 -0
- package/dist/core/computerUseSession.js +105 -0
- package/dist/core/config.d.ts +7 -2
- package/dist/core/config.js +24 -6
- package/dist/core/conversationKernel.d.ts +1 -0
- package/dist/core/conversationKernel.js +39 -1
- package/dist/core/electronBrowserUseHost.js +7 -0
- package/dist/core/electronUtilityAgentClient.js +84 -2
- package/dist/core/electronUtilityRuntimePool.d.ts +11 -0
- package/dist/core/electronUtilityRuntimePool.js +62 -0
- package/dist/core/flow-runner.js +1 -1
- package/dist/core/modelValidationStore.d.ts +4 -1
- package/dist/core/modelValidationStore.js +7 -1
- package/dist/core/utilityHostToolRouter.d.ts +7 -0
- package/dist/core/utilityHostToolRouter.js +25 -33
- package/dist/core/workspace.d.ts +6 -0
- package/dist/core/workspace.js +14 -0
- package/dist/core/wslAgentRuntimePool.d.ts +4 -0
- package/dist/core/wslAgentRuntimePool.js +56 -0
- package/dist/launcher.js +51 -10
- package/dist/llm/provider.d.ts +8 -5
- package/dist/llm/provider.js +85 -33
- package/dist/main.js +297 -61
- package/dist/preload.js +10 -2
- package/dist/providers/chat-completions.adapter.js +1 -5
- package/dist/providers/provider-events.d.ts +7 -0
- package/dist/providers/provider-events.js +44 -0
- package/dist/providers/responses.adapter.js +1 -3
- package/dist/tools/index.js +36 -49
- package/dist/tui/src/adapters/core-runtime-adapter.js +40 -3
- package/dist/tui/src/app.js +47 -13
- package/dist/tui/src/render.js +23 -7
- package/dist/tui/src/state.js +61 -9
- package/dist/ui/index.html +545 -122
- package/dist/wsl-agent-host.bundle.cjs +548 -137
- package/package.json +14 -5
package/dist/ui/index.html
CHANGED
|
@@ -5461,6 +5461,7 @@ html.newmark-memory-overview-viewer body {
|
|
|
5461
5461
|
<button class="shell-btn browser-action-btn" onclick="window.browserReload()" title="Reload"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#refresh-cw"></use></svg><span class="icon-only-label">Reload</span></button>
|
|
5462
5462
|
<input class="browser-url-input" id="browser-url" placeholder="Enter URL..." onkeydown="if(event.key==='Enter')window.navigateBrowser(this.value)">
|
|
5463
5463
|
<button class="shell-btn" onclick="window.navigateBrowser(document.getElementById('browser-url').value)" title="Go"><svg class="nm-icon small" aria-hidden="true" focusable="false"><use href="#send"></use></svg><span class="icon-only-label">Go</span></button>
|
|
5464
|
+
<button class="shell-btn" id="computer-use-toggle" onclick="window.toggleComputerUse()" title="ComputerUse: enabled for this conversation" aria-pressed="true">CU</button>
|
|
5464
5465
|
</div>
|
|
5465
5466
|
<div class="browser-frame" id="browser-frame">
|
|
5466
5467
|
<div class="browser-guest-placeholder" id="browser-guest-placeholder">Browser loads on first use</div>
|
|
@@ -5681,6 +5682,7 @@ var state = {
|
|
|
5681
5682
|
terminalInterruptTimeoutMs: 0,
|
|
5682
5683
|
nativeTools: [],
|
|
5683
5684
|
nativeToolEnabled: {},
|
|
5685
|
+
computerUseByTarget: {},
|
|
5684
5686
|
sshConnections: [],
|
|
5685
5687
|
_layoutSaveTimer: null,
|
|
5686
5688
|
_sendInFlight: false
|
|
@@ -6061,6 +6063,7 @@ var NEWMARK_I18N = {
|
|
|
6061
6063
|
'flow.nextDisabled': 'Flow only accepts Guide input; Next is disabled.',
|
|
6062
6064
|
'flow.saved': 'Flow saved',
|
|
6063
6065
|
'flow.emptyRun': 'Flow is empty and cannot run.',
|
|
6066
|
+
'flow.noAvailable': 'No workflows are available to run.',
|
|
6064
6067
|
'flow.started': 'Flow started',
|
|
6065
6068
|
'plugins.title': 'Plugins',
|
|
6066
6069
|
'plugins.mcp': 'MCP Management',
|
|
@@ -6626,6 +6629,7 @@ var NEWMARK_I18N = {
|
|
|
6626
6629
|
'flow.nextDisabled': 'Flow 仅允许 Guide 输入,已禁用 Next。',
|
|
6627
6630
|
'flow.saved': 'Flow 已保存',
|
|
6628
6631
|
'flow.emptyRun': 'Flow 为空,无法运行。',
|
|
6632
|
+
'flow.noAvailable': '当前没有可运行的工作流。',
|
|
6629
6633
|
'flow.started': 'Flow 已启动',
|
|
6630
6634
|
'plugins.title': '插件',
|
|
6631
6635
|
'plugins.mcp': 'MCP 管理',
|
|
@@ -8327,15 +8331,26 @@ function addWorkReview(diffs) {
|
|
|
8327
8331
|
return review;
|
|
8328
8332
|
}
|
|
8329
8333
|
|
|
8330
|
-
|
|
8334
|
+
var STREAMING_PLAIN_TEXT_THRESHOLD = 12000;
|
|
8335
|
+
var STREAMING_LARGE_TEXT_INTERVAL_MS = 100;
|
|
8336
|
+
|
|
8337
|
+
function updateMsg(div, text, role, mode, model, options) {
|
|
8331
8338
|
if (!div) return;
|
|
8332
8339
|
if (role) div.className = 'chat-msg ' + role;
|
|
8333
8340
|
text = redactSensitiveText(text);
|
|
8334
8341
|
div._newmarkMessageText = String(text || '');
|
|
8335
8342
|
var body = div.querySelector('.msg-body');
|
|
8336
|
-
|
|
8343
|
+
var streaming = !!(options && options.streaming);
|
|
8344
|
+
if (body) {
|
|
8345
|
+
// Re-running the full Markdown parser and rebuilding the entire message
|
|
8346
|
+
// DOM for every streamed token makes long responses monopolize the
|
|
8347
|
+
// renderer event loop. Keep large in-flight responses as one safe text
|
|
8348
|
+
// node; the terminal `done`/`final_response` path renders Markdown once.
|
|
8349
|
+
if (streaming && String(text || '').length >= STREAMING_PLAIN_TEXT_THRESHOLD) body.textContent = text;
|
|
8350
|
+
else body.innerHTML = renderMessageContent(text);
|
|
8351
|
+
}
|
|
8337
8352
|
var meta = div.querySelector('.meta');
|
|
8338
|
-
if (meta && (mode || model)) {
|
|
8353
|
+
if (meta && (mode || model) && !streaming) {
|
|
8339
8354
|
var roleLabel = div.classList.contains('user')
|
|
8340
8355
|
? (String(mode || '').toLowerCase() === 'flow-user-input' ? t('flow.userInput') : t('message.user'))
|
|
8341
8356
|
: (div.classList.contains('workflow') ? t('message.workflow') : (div.classList.contains('system') ? t('message.system') : t('message.agent')));
|
|
@@ -8744,6 +8759,60 @@ function setConversationRuntimeState(target, status, runId, extra) {
|
|
|
8744
8759
|
return next;
|
|
8745
8760
|
}
|
|
8746
8761
|
|
|
8762
|
+
// A renderer send marks its target as provisionally running before the IPC
|
|
8763
|
+
// request reaches the target runtime. Escape/Stop can therefore arrive in
|
|
8764
|
+
// that handoff window while the backend still reports `not_running`. Remember
|
|
8765
|
+
// the exact provisional run so the pending send can be cancelled before it
|
|
8766
|
+
// allocates a provider request; once the backend has a real run, the normal
|
|
8767
|
+
// target-scoped stop path takes over.
|
|
8768
|
+
function markProvisionalStop(target, runId) {
|
|
8769
|
+
if (!state.pendingProvisionalStops) state.pendingProvisionalStops = {};
|
|
8770
|
+
state.pendingProvisionalStops[runtimeKeyFor(target.workspaceId, target.conversationId)] = String(runId || '');
|
|
8771
|
+
}
|
|
8772
|
+
|
|
8773
|
+
function pendingProvisionalStopRunId(target) {
|
|
8774
|
+
var key = runtimeKeyFor(target.workspaceId, target.conversationId);
|
|
8775
|
+
if (!state.pendingProvisionalStops || !Object.prototype.hasOwnProperty.call(state.pendingProvisionalStops, key)) return undefined;
|
|
8776
|
+
return state.pendingProvisionalStops[key];
|
|
8777
|
+
}
|
|
8778
|
+
|
|
8779
|
+
function clearProvisionalStop(target) {
|
|
8780
|
+
if (state.pendingProvisionalStops) delete state.pendingProvisionalStops[runtimeKeyFor(target.workspaceId, target.conversationId)];
|
|
8781
|
+
}
|
|
8782
|
+
|
|
8783
|
+
function consumeProvisionalStop(target, runId) {
|
|
8784
|
+
var pending = pendingProvisionalStopRunId(target);
|
|
8785
|
+
if (pending === undefined || String(pending) !== String(runId || '')) return false;
|
|
8786
|
+
clearProvisionalStop(target);
|
|
8787
|
+
return true;
|
|
8788
|
+
}
|
|
8789
|
+
|
|
8790
|
+
function requestBackendStopForProvisionalStart(target, runId) {
|
|
8791
|
+
var stopPromise = api.stopConversation
|
|
8792
|
+
? api.stopConversation({ target: target, runId: String(runId || ''), force: false })
|
|
8793
|
+
: (api.abortConversation ? api.abortConversation(target) : Promise.resolve({ action: 'not_running' }));
|
|
8794
|
+
Promise.resolve(stopPromise).then(function(result) {
|
|
8795
|
+
var action = String(result && result.action || '');
|
|
8796
|
+
if (action === 'not_running' || action === 'stale') {
|
|
8797
|
+
return refreshConversationRuntimeAfterStopRace(target, runId, result);
|
|
8798
|
+
}
|
|
8799
|
+
if (action === 'force' || String(result && result.status || '') === 'force_interrupted') {
|
|
8800
|
+
setConversationRuntimeState(target, 'force_interrupted', runId, { provisional: false });
|
|
8801
|
+
return;
|
|
8802
|
+
}
|
|
8803
|
+
if (action === 'graceful') {
|
|
8804
|
+
setConversationRuntimeState(target, 'stopping', runId, { provisional: false });
|
|
8805
|
+
return;
|
|
8806
|
+
}
|
|
8807
|
+
if (result === true || ['idle', 'interrupted'].indexOf(String(result && result.status || '')) >= 0) {
|
|
8808
|
+
setConversationRuntimeState(target, 'interrupted', runId, { provisional: false });
|
|
8809
|
+
}
|
|
8810
|
+
}).catch(function(error) {
|
|
8811
|
+
setConversationRuntimeState(target, 'running', runId, { provisional: false });
|
|
8812
|
+
showUiNotice(error && error.message ? error.message : String(error), 'error', 'stop-provisional-' + runtimeKeyFor(target.workspaceId, target.conversationId) + '-' + String(runId || ''));
|
|
8813
|
+
});
|
|
8814
|
+
}
|
|
8815
|
+
|
|
8747
8816
|
function formatWorkDuration(milliseconds) {
|
|
8748
8817
|
var seconds = Math.max(0, Math.floor(Number(milliseconds || 0) / 1000));
|
|
8749
8818
|
var hours = Math.floor(seconds / 3600);
|
|
@@ -9474,7 +9543,14 @@ function renderWorkRunEvents(run, includeGuides) {
|
|
|
9474
9543
|
var responseLabel = type === 'response' && terminalInterrupted
|
|
9475
9544
|
? (currentLang() === 'zh' ? '未完成回复片段\n' : 'Incomplete response fragment\n') + workEventLabel(event)
|
|
9476
9545
|
: workEventLabel(event);
|
|
9477
|
-
|
|
9546
|
+
// While a run is still receiving text, this view can be rebuilt once per
|
|
9547
|
+
// event. Avoid invoking the full Markdown parser for a large live
|
|
9548
|
+
// narrative; the final response path still gets the normal rich render.
|
|
9549
|
+
var liveNarrative = ['running', 'stopping', 'force_restarting'].indexOf(String(run && run.status || '').toLowerCase()) >= 0;
|
|
9550
|
+
var narrativeHtml = liveNarrative && String(responseLabel || '').length >= STREAMING_PLAIN_TEXT_THRESHOLD
|
|
9551
|
+
? esc(responseLabel)
|
|
9552
|
+
: renderMessageContent(responseLabel);
|
|
9553
|
+
return '<div class="conversation-work-event narrative"><div class="conversation-work-event-content">' + narrativeHtml + '</div></div>';
|
|
9478
9554
|
}
|
|
9479
9555
|
if (type.indexOf('guide') === 0 || event.guide) return renderWorkRunGuideMessage(event);
|
|
9480
9556
|
if (type === 'tool_group') return renderWorkToolGroup(event, eventIndex);
|
|
@@ -9594,17 +9670,22 @@ function renderConversationWorkRuns(runs, target) {
|
|
|
9594
9670
|
return normalized;
|
|
9595
9671
|
}
|
|
9596
9672
|
|
|
9597
|
-
|
|
9598
|
-
|
|
9599
|
-
|
|
9673
|
+
// Work events can arrive much faster than the browser can paint them (text
|
|
9674
|
+
// deltas are especially noisy). Keep the authoritative run in memory for
|
|
9675
|
+
// every event, but coalesce its DOM replacement to one animation frame. This
|
|
9676
|
+
// is important for long real-model runs: replacing the whole work block for
|
|
9677
|
+
// every delta can starve Chromium's renderer/CDP event loop.
|
|
9678
|
+
var pendingConversationWorkRunRenders = {};
|
|
9679
|
+
var scheduledConversationWorkRunRenders = {};
|
|
9680
|
+
function scheduleConversationWorkRunRender(run) {
|
|
9600
9681
|
var key = runtimeKeyFor(run.target && run.target.workspaceId, run.target && run.target.conversationId) + '::' + String(run.runId || '');
|
|
9601
|
-
|
|
9602
|
-
if (
|
|
9603
|
-
|
|
9682
|
+
pendingConversationWorkRunRenders[key] = run;
|
|
9683
|
+
if (scheduledConversationWorkRunRenders[key]) return;
|
|
9684
|
+
scheduledConversationWorkRunRenders[key] = true;
|
|
9604
9685
|
requestAnimationFrame(function() {
|
|
9605
|
-
var latest =
|
|
9606
|
-
delete
|
|
9607
|
-
delete
|
|
9686
|
+
var latest = pendingConversationWorkRunRenders[key];
|
|
9687
|
+
delete pendingConversationWorkRunRenders[key];
|
|
9688
|
+
delete scheduledConversationWorkRunRenders[key];
|
|
9608
9689
|
if (latest && isActiveConversationTarget(latest.target)) renderConversationWorkRun(latest);
|
|
9609
9690
|
});
|
|
9610
9691
|
}
|
|
@@ -9640,7 +9721,9 @@ function applyAgentWorkEventToRun(event) {
|
|
|
9640
9721
|
var priorGuideEvent = run.events[guideEventIndex];
|
|
9641
9722
|
if (guideWorkEventKey(priorGuideEvent) === incomingGuideKey) {
|
|
9642
9723
|
run.events[guideEventIndex] = mergeGuideWorkEvent(priorGuideEvent, event);
|
|
9643
|
-
|
|
9724
|
+
if (isActiveConversationTarget(target) && (!branchIds.viewed || !eventBranchId || branchIds.viewed === eventBranchId)) {
|
|
9725
|
+
scheduleConversationWorkRunRender(run);
|
|
9726
|
+
}
|
|
9644
9727
|
return run;
|
|
9645
9728
|
}
|
|
9646
9729
|
}
|
|
@@ -9667,8 +9750,8 @@ function applyAgentWorkEventToRun(event) {
|
|
|
9667
9750
|
if (isActiveConversationTarget(target) && (!branchIds.viewed || !eventBranchId || branchIds.viewed === eventBranchId)) {
|
|
9668
9751
|
state.workRunsByTarget[runtimeKeyFor(target.workspaceId, target.conversationId)] = runs;
|
|
9669
9752
|
var existingElement = findWorkRunElement(run);
|
|
9670
|
-
if (existingElement
|
|
9671
|
-
else
|
|
9753
|
+
if (!existingElement || type === 'start') renderConversationWorkRun(run);
|
|
9754
|
+
else scheduleConversationWorkRunRender(run);
|
|
9672
9755
|
}
|
|
9673
9756
|
return run;
|
|
9674
9757
|
}
|
|
@@ -10214,7 +10297,13 @@ function applyStreamingWorkflowText(ui, eventConversationId, mode, model) {
|
|
|
10214
10297
|
}
|
|
10215
10298
|
return;
|
|
10216
10299
|
}
|
|
10217
|
-
|
|
10300
|
+
var largeText = ui.activeWorkflowText.length >= STREAMING_PLAIN_TEXT_THRESHOLD;
|
|
10301
|
+
if (largeText) {
|
|
10302
|
+
var now = Date.now();
|
|
10303
|
+
if (ui._lastLargeStreamRenderAt && now - ui._lastLargeStreamRenderAt < STREAMING_LARGE_TEXT_INTERVAL_MS) return;
|
|
10304
|
+
ui._lastLargeStreamRenderAt = now;
|
|
10305
|
+
}
|
|
10306
|
+
updateMsg(ensureActiveAssistantMsg(mode, model, eventConversationId), ui.activeWorkflowText, 'assistant', mode, model, { streaming: true });
|
|
10218
10307
|
}
|
|
10219
10308
|
|
|
10220
10309
|
function scheduleStreamingWorkflowTextFlush(ui, eventConversationId, eventWorkspaceId) {
|
|
@@ -10245,6 +10334,7 @@ function renderAgentWorkEvent(event) {
|
|
|
10245
10334
|
ui.activeWorkflowText = '';
|
|
10246
10335
|
ui.lastCompletedWorkflow = null;
|
|
10247
10336
|
ui._streamFlushPending = false;
|
|
10337
|
+
ui._lastLargeStreamRenderAt = 0;
|
|
10248
10338
|
} else if (type === 'text') {
|
|
10249
10339
|
ui.activeWorkflowText = (ui.activeWorkflowText || '') + content;
|
|
10250
10340
|
if (workRun) return;
|
|
@@ -10255,6 +10345,7 @@ function renderAgentWorkEvent(event) {
|
|
|
10255
10345
|
ui.activeWorkflowText = '';
|
|
10256
10346
|
ui.activeWorkflowMsg = null;
|
|
10257
10347
|
ui._streamFlushPending = false;
|
|
10348
|
+
ui._lastLargeStreamRenderAt = 0;
|
|
10258
10349
|
} else if (type === 'final_response' && workRun) {
|
|
10259
10350
|
ui.activeWorkflowText = '';
|
|
10260
10351
|
ui.activeWorkflowMsg = null;
|
|
@@ -10288,6 +10379,7 @@ function renderAgentWorkEvent(event) {
|
|
|
10288
10379
|
}
|
|
10289
10380
|
ui.activeWorkflowMsg = null;
|
|
10290
10381
|
ui.activeWorkflowText = '';
|
|
10382
|
+
ui._lastLargeStreamRenderAt = 0;
|
|
10291
10383
|
finishToolBatch(eventConversationId);
|
|
10292
10384
|
} else if (type === 'error') {
|
|
10293
10385
|
flushStreamingWorkflowTextNow(ui, eventConversationId, event.mode || state.mode, event.model || state.model);
|
|
@@ -10357,7 +10449,14 @@ function appendAgentWorkEvent(event) {
|
|
|
10357
10449
|
var visibleRuntimeBranch = active && isViewingRuntimeConversationBranch(target);
|
|
10358
10450
|
if (active) markConversationTracked(id, state.conversationTrackMs || 300000, workspaceId);
|
|
10359
10451
|
if (active || isConversationTracked(id, workspaceId)) cacheAgentWorkEvent(event);
|
|
10360
|
-
|
|
10452
|
+
var pendingProvisionalStop = (event.type === 'start' || event.status === 'running')
|
|
10453
|
+
? pendingProvisionalStopRunId(target)
|
|
10454
|
+
: undefined;
|
|
10455
|
+
if (pendingProvisionalStop !== undefined && event.runId) {
|
|
10456
|
+
clearProvisionalStop(target);
|
|
10457
|
+
setConversationRuntimeState(target, 'stopping', event.runId, { provisional: false, generation: event.generation || 0, runtimeKey: event.runtimeKey || '' });
|
|
10458
|
+
requestBackendStopForProvisionalStart(target, event.runId);
|
|
10459
|
+
} else if (event.type === 'start') setConversationRuntimeState(target, event.status || 'running', event.runId || '', { provisional: false, generation: event.generation || 0, runtimeKey: event.runtimeKey || '' });
|
|
10361
10460
|
else if (event.status && ['running', 'stopping', 'force_restarting'].indexOf(String(event.status)) >= 0) setConversationRuntimeState(target, event.status, event.runId || '');
|
|
10362
10461
|
else if (event.status && ['completed', 'interrupted', 'force_interrupted', 'error'].indexOf(String(event.status)) >= 0) {
|
|
10363
10462
|
setConversationRuntimeState(target, event.status, event.runId || '');
|
|
@@ -10459,7 +10558,14 @@ function hydrateConversationBranchState(snapshot) {
|
|
|
10459
10558
|
}
|
|
10460
10559
|
|
|
10461
10560
|
function applyConversationSnapshot(s, requestedConversationId) {
|
|
10462
|
-
|
|
10561
|
+
if (!s) return;
|
|
10562
|
+
if (s.computerUse) {
|
|
10563
|
+
var computerTarget = currentConversationTarget(s.conversationId || requestedConversationId || activeConversationId());
|
|
10564
|
+
var computerKey = computerUseTargetKey(computerTarget);
|
|
10565
|
+
if (!state.computerUseByTarget) state.computerUseByTarget = {};
|
|
10566
|
+
state.computerUseByTarget[computerKey] = s.computerUse;
|
|
10567
|
+
if (isActiveConversationTarget(computerTarget)) renderComputerUseToggle(s.computerUse, computerTarget);
|
|
10568
|
+
}
|
|
10463
10569
|
state.goalText = s.goal && s.goal.objective ? String(s.goal.objective) : '';
|
|
10464
10570
|
state.goalVisible = !!state.goalText;
|
|
10465
10571
|
state.goalPaused = !!(s.goal && s.goal.paused);
|
|
@@ -10813,6 +10919,44 @@ function isCurrentConversationRunning() {
|
|
|
10813
10919
|
return !!(id && runningConversationRecord(id));
|
|
10814
10920
|
}
|
|
10815
10921
|
|
|
10922
|
+
// A terminal final_response event can reach the renderer before the awaited
|
|
10923
|
+
// send promise performs its final local cleanup. If a user submits in that
|
|
10924
|
+
// small window, trusting the stale local running/active-send flags silently
|
|
10925
|
+
// drops the new message. Reconcile only when a send is already considered
|
|
10926
|
+
// active; a backend-confirmed running snapshot still keeps the guard intact.
|
|
10927
|
+
async function reconcileStaleLocalRuntimeBeforeSend(target) {
|
|
10928
|
+
if (!target || !api.getState) return false;
|
|
10929
|
+
var snapshot;
|
|
10930
|
+
try {
|
|
10931
|
+
snapshot = await api.getState(target);
|
|
10932
|
+
} catch (_) {
|
|
10933
|
+
return false;
|
|
10934
|
+
}
|
|
10935
|
+
var runtimeSnapshot = snapshot && snapshot.runtime || null;
|
|
10936
|
+
var snapshotStatus = String(snapshot && snapshot.status || '').toLowerCase();
|
|
10937
|
+
var backendStillRunning = !!(runtimeSnapshot && (runtimeSnapshot.running || runtimeSnapshot.stopRequested))
|
|
10938
|
+
|| ['running', 'stopping', 'force_restarting'].indexOf(snapshotStatus) >= 0;
|
|
10939
|
+
if (backendStillRunning) return false;
|
|
10940
|
+
if (['idle', 'completed', 'interrupted', 'force_interrupted', 'error'].indexOf(snapshotStatus) < 0) return false;
|
|
10941
|
+
var runId = String((runtimeSnapshot && runtimeSnapshot.runId) || snapshot.runId || '');
|
|
10942
|
+
var generation = Number((runtimeSnapshot && runtimeSnapshot.generation) || snapshot.generation || 0);
|
|
10943
|
+
setConversationRuntimeState(target, snapshotStatus, runId, { provisional: false, generation: generation });
|
|
10944
|
+
var targetKey = runtimeKeyFor(target.workspaceId, target.conversationId);
|
|
10945
|
+
if (state.activeSendCallsByTarget) {
|
|
10946
|
+
delete state.activeSendCallsByTarget[targetKey];
|
|
10947
|
+
delete state.activeSendCallsByTarget[runtimeKeyFor('', target.conversationId)];
|
|
10948
|
+
}
|
|
10949
|
+
if (state.nextQueueSchedulesByTarget) {
|
|
10950
|
+
delete state.nextQueueSchedulesByTarget[targetKey];
|
|
10951
|
+
delete state.nextQueueSchedulesByTarget[runtimeKeyFor('', target.conversationId)];
|
|
10952
|
+
}
|
|
10953
|
+
state._sendInFlight = Object.keys(state.runningConversations || {}).length > 0;
|
|
10954
|
+
setWorking(state._sendInFlight);
|
|
10955
|
+
updateSubmitButtonState();
|
|
10956
|
+
if (window.renderInputStack) window.renderInputStack();
|
|
10957
|
+
return true;
|
|
10958
|
+
}
|
|
10959
|
+
|
|
10816
10960
|
function setSubmitButtonVisual(icon, label, running, stopMode) {
|
|
10817
10961
|
var btn = els['submit-btn'] || document.getElementById('submit-btn');
|
|
10818
10962
|
if (!btn) return;
|
|
@@ -10923,18 +11067,33 @@ window.stopCurrentConversation = async function() {
|
|
|
10923
11067
|
if (!conversationId || !runtime) return false;
|
|
10924
11068
|
var target = runtime.target || currentConversationTarget(conversationId);
|
|
10925
11069
|
var runId = String(runtime.runId || '');
|
|
11070
|
+
var provisional = runtime.provisional === true;
|
|
10926
11071
|
if (String(runtime.status || '') === 'force_restarting') return false;
|
|
11072
|
+
if (provisional) markProvisionalStop(target, runId);
|
|
10927
11073
|
var force = String(runtime.status || '') === 'stopping';
|
|
10928
11074
|
if (typeof pauseQueueForTarget === 'function') pauseQueueForTarget(target);
|
|
10929
11075
|
setConversationRuntimeState(target, force ? 'force_restarting' : 'stopping', runId);
|
|
10930
11076
|
updateSubmitButtonState();
|
|
10931
11077
|
renderConversations();
|
|
11078
|
+
if (provisional) {
|
|
11079
|
+
// The backend may not have allocated a utility runtime yet. Keep the
|
|
11080
|
+
// exact local cancellation marker and let sendMessage consume it, or let
|
|
11081
|
+
// the first backend start event issue a stop with the real run id. An
|
|
11082
|
+
// empty-run stop request here races runtime allocation and can return
|
|
11083
|
+
// not_running before the prompt reaches the worker.
|
|
11084
|
+
if (window.renderInputStack) window.renderInputStack();
|
|
11085
|
+
return true;
|
|
11086
|
+
}
|
|
10932
11087
|
try {
|
|
10933
11088
|
var result;
|
|
10934
11089
|
// The target runtime supervisor owns the checkpoint + cooperative-stop
|
|
10935
11090
|
// transaction. A separate awaited checkpoint can itself be starved by a
|
|
10936
11091
|
// blocked worker and would prevent the supervisor from recording the first
|
|
10937
11092
|
// stop, making a second-click hard restart impossible.
|
|
11093
|
+
// A provisional renderer run has no backend run id yet. Omitting the
|
|
11094
|
+
// expected id lets a concurrently-created backend run be stopped, while
|
|
11095
|
+
// the pending marker above cancels a request that has not reached the
|
|
11096
|
+
// runtime at all.
|
|
10938
11097
|
if (api.stopConversation) result = await api.stopConversation({ target: target, runId: runId, force: force });
|
|
10939
11098
|
else if (api.abortConversation) result = await api.abortConversation(currentConversationTarget(conversationId));
|
|
10940
11099
|
var resultAction = String(result && result.action || '');
|
|
@@ -10945,6 +11104,9 @@ window.stopCurrentConversation = async function() {
|
|
|
10945
11104
|
await refreshConversationRuntimeAfterStopRace(target, runId, result);
|
|
10946
11105
|
return true;
|
|
10947
11106
|
}
|
|
11107
|
+
if (provisional && state.pendingProvisionalStops) {
|
|
11108
|
+
delete state.pendingProvisionalStops[runtimeKeyFor(target.workspaceId, target.conversationId)];
|
|
11109
|
+
}
|
|
10948
11110
|
var latestAfterStop = state.conversationRuntimeStates && state.conversationRuntimeStates[runtimeKeyFor(target.workspaceId, target.conversationId)];
|
|
10949
11111
|
if (!force && latestAfterStop && String(latestAfterStop.runId || '') === runId && String(latestAfterStop.status || '') !== 'stopping') {
|
|
10950
11112
|
// A second click or a terminal worker event won the race while the first
|
|
@@ -11837,6 +11999,13 @@ window.endQueueDrag = function(event) {
|
|
|
11837
11999
|
if (event.currentTarget) event.currentTarget.classList.remove('dragging');
|
|
11838
12000
|
};
|
|
11839
12001
|
|
|
12002
|
+
function recoverableGuideRejection(receipt) {
|
|
12003
|
+
if (!receipt || String(receipt.status || '').toLowerCase() !== 'rejected') return false;
|
|
12004
|
+
var reason = String(receipt.reason || receipt.error || '').trim();
|
|
12005
|
+
return reason === 'Target conversation is not running'
|
|
12006
|
+
|| reason === 'Guide runId does not match the active run';
|
|
12007
|
+
}
|
|
12008
|
+
|
|
11840
12009
|
window.sendMessage = async function(modeOverride, queuedText, opts) {
|
|
11841
12010
|
if (!window.requireWorkspace('send')) return;
|
|
11842
12011
|
opts = opts || {};
|
|
@@ -11960,6 +12129,28 @@ window.sendMessage = async function(modeOverride, queuedText, opts) {
|
|
|
11960
12129
|
var conversationRunning = !!activeRuntime;
|
|
11961
12130
|
markConversationTracked(lockedConversationId, state.conversationTrackMs || 300000, lockedTarget.workspaceId);
|
|
11962
12131
|
|
|
12132
|
+
var activeSendCall = !!(state.activeSendCallsByTarget && state.activeSendCallsByTarget[lockedRuntimeKey]);
|
|
12133
|
+
if ((conversationRunning || activeSendCall) && api.getState) {
|
|
12134
|
+
var reconciledLocalRuntime = await reconcileStaleLocalRuntimeBeforeSend(lockedTarget);
|
|
12135
|
+
if (reconciledLocalRuntime) {
|
|
12136
|
+
activeRuntime = runningConversationRecord(lockedConversationId, lockedTarget.workspaceId);
|
|
12137
|
+
conversationRunning = !!activeRuntime;
|
|
12138
|
+
if (!conversationRunning && effectiveInputMode === 'guide') effectiveInputMode = 'next';
|
|
12139
|
+
}
|
|
12140
|
+
}
|
|
12141
|
+
|
|
12142
|
+
if (opts.retryAfterGuideRejection && conversationRunning && api.getState) {
|
|
12143
|
+
try {
|
|
12144
|
+
var freshRuntimeState = await api.getState(lockedTarget);
|
|
12145
|
+
var freshStatus = String(freshRuntimeState && freshRuntimeState.status || '').toLowerCase();
|
|
12146
|
+
if (['idle', 'completed', 'interrupted', 'force_interrupted', 'error'].indexOf(freshStatus) >= 0) {
|
|
12147
|
+
setConversationRuntimeState(lockedTarget, 'idle', '');
|
|
12148
|
+
activeRuntime = null;
|
|
12149
|
+
conversationRunning = false;
|
|
12150
|
+
}
|
|
12151
|
+
} catch (_) {}
|
|
12152
|
+
}
|
|
12153
|
+
|
|
11963
12154
|
if (effectiveInputMode === 'next' && !opts.fromQueue && conversationRunning) {
|
|
11964
12155
|
if (!state.nextQueue) state.nextQueue = [];
|
|
11965
12156
|
if (!state.nextQueueRequests) state.nextQueueRequests = [];
|
|
@@ -12041,6 +12232,57 @@ window.sendMessage = async function(modeOverride, queuedText, opts) {
|
|
|
12041
12232
|
throw new Error(currentLang() === 'zh' ? '未收到有效的 Guide 回执' : 'A valid Guide acknowledgement was not received');
|
|
12042
12233
|
}
|
|
12043
12234
|
var status = normalizeGuideUiStatus(receipt.status);
|
|
12235
|
+
if (recoverableGuideRejection(receipt)) {
|
|
12236
|
+
// Renderer runtime state can lag the backend's finalization event by
|
|
12237
|
+
// one IPC turn. The Guide was explicitly rejected, so replaying the
|
|
12238
|
+
// exact payload as a fresh Build is safe and prevents a normal user
|
|
12239
|
+
// send from disappearing behind a stale Guide route.
|
|
12240
|
+
var fallbackRequest = {
|
|
12241
|
+
text: typeof requestMessage === 'string' ? requestMessage : String(requestMessage.text || ''),
|
|
12242
|
+
images: typeof requestMessage === 'object' && Array.isArray(requestMessage.images)
|
|
12243
|
+
? requestMessage.images.map(function(image) { return Object.assign({}, image); })
|
|
12244
|
+
: []
|
|
12245
|
+
};
|
|
12246
|
+
recordGuideUiMessage({
|
|
12247
|
+
clientMessageId: clientMessageId,
|
|
12248
|
+
target: receipt.target || lockedTarget,
|
|
12249
|
+
runId: receipt.runId || envelope.runId,
|
|
12250
|
+
status: 'deferred',
|
|
12251
|
+
content: displayText,
|
|
12252
|
+
attachments: optimisticAttachments,
|
|
12253
|
+
createdAt: receipt.createdAt || envelope.createdAt,
|
|
12254
|
+
updatedAt: receipt.updatedAt,
|
|
12255
|
+
reason: currentLang() === 'zh'
|
|
12256
|
+
? '运行已结束,输入已作为新的 Build 继续。'
|
|
12257
|
+
: 'The run ended before Guide delivery; the input continued as a new Build.'
|
|
12258
|
+
}, lockedTarget);
|
|
12259
|
+
applyAgentWorkEventToRun({
|
|
12260
|
+
id: 'guide-' + clientMessageId + '-deferred',
|
|
12261
|
+
type: 'guide_deferred',
|
|
12262
|
+
content: displayText,
|
|
12263
|
+
workspaceId: lockedTarget.workspaceId,
|
|
12264
|
+
conversationId: lockedTarget.conversationId,
|
|
12265
|
+
runId: receipt.runId || envelope.runId,
|
|
12266
|
+
sequence: receipt.sequence || 0,
|
|
12267
|
+
guideStatus: 'deferred',
|
|
12268
|
+
clientMessageId: clientMessageId,
|
|
12269
|
+
guide: guideMessagesForTarget(lockedTarget)[clientMessageId]
|
|
12270
|
+
});
|
|
12271
|
+
return Promise.resolve(window.sendMessage('next', undefined, {
|
|
12272
|
+
requestedMode: requestedMode,
|
|
12273
|
+
goalObjective: opts.goalObjective,
|
|
12274
|
+
forceBuild: true,
|
|
12275
|
+
retryAfterGuideRejection: true,
|
|
12276
|
+
queuedRequest: fallbackRequest
|
|
12277
|
+
})).then(function(fallbackResult) {
|
|
12278
|
+
return {
|
|
12279
|
+
ok: !(fallbackResult && fallbackResult.ok === false),
|
|
12280
|
+
guideReceipt: receipt,
|
|
12281
|
+
recoveredAsBuild: true,
|
|
12282
|
+
fallback: fallbackResult
|
|
12283
|
+
};
|
|
12284
|
+
});
|
|
12285
|
+
}
|
|
12044
12286
|
var guideRecord = recordGuideUiMessage({
|
|
12045
12287
|
clientMessageId: clientMessageId,
|
|
12046
12288
|
target: receipt.target || lockedTarget,
|
|
@@ -12099,6 +12341,15 @@ window.sendMessage = async function(modeOverride, queuedText, opts) {
|
|
|
12099
12341
|
var executionMode = opts.goalDeclaration || requestedMode === 'goal' ? 'build'
|
|
12100
12342
|
: ((opts.forceBuild || idleBuildNextImmediate) && requestedMode === 'build' ? 'build' : requestedMode);
|
|
12101
12343
|
await syncConversationExecutionState(executionMode, effectiveInputMode);
|
|
12344
|
+
if (consumeProvisionalStop(lockedTarget, localRunId)) {
|
|
12345
|
+
// Stop/Escape won before the backend accepted the prompt. Do not send the
|
|
12346
|
+
// provider request after the user has already cancelled it.
|
|
12347
|
+
delete state.activeSendCallsByTarget[lockedRuntimeKey];
|
|
12348
|
+
setConversationRuntimeState(lockedTarget, 'interrupted', localRunId, { provisional: false });
|
|
12349
|
+
setWorking(!!runningConversationRecord(activeConversationId()));
|
|
12350
|
+
window.renderInputStack();
|
|
12351
|
+
return { status: 'interrupted', runId: localRunId, target: lockedTarget, cancelledBeforeStart: true };
|
|
12352
|
+
}
|
|
12102
12353
|
var renderOnViewedBranch = !queuedRuntimeBranch || queueBranchPathForTarget(lockedTarget, 'viewed') === queueBranchPathForTarget(lockedTarget, 'runtime');
|
|
12103
12354
|
if (isActiveConversationTarget(lockedTarget) && renderOnViewedBranch) {
|
|
12104
12355
|
if (opts.resumeGuideRunId && requestMessage.clientMessageId) {
|
|
@@ -12128,10 +12379,12 @@ window.sendMessage = async function(modeOverride, queuedText, opts) {
|
|
|
12128
12379
|
renderAutoRouteRatingControls();
|
|
12129
12380
|
}
|
|
12130
12381
|
var responseMsg = null;
|
|
12382
|
+
var sendFailure = '';
|
|
12131
12383
|
try {
|
|
12132
12384
|
var sendPromise = api.sendMessage(requestMessage, lockedTarget);
|
|
12133
12385
|
if (requestedMode === 'goal') activateSubmittedGoal(opts.goalObjective || rawText);
|
|
12134
12386
|
var r = await sendPromise;
|
|
12387
|
+
if (r && r.error) sendFailure = String(r.error);
|
|
12135
12388
|
if (isActiveConversationTarget(lockedTarget)) applyReturnedGoalState(r);
|
|
12136
12389
|
if (r && r.runId) {
|
|
12137
12390
|
var anchorStore = workRunAnchorIndexStore(lockedTarget);
|
|
@@ -12212,9 +12465,10 @@ window.sendMessage = async function(modeOverride, queuedText, opts) {
|
|
|
12212
12465
|
}
|
|
12213
12466
|
}
|
|
12214
12467
|
} catch(e) {
|
|
12468
|
+
sendFailure = e && e.message ? String(e.message) : String(e || 'Agent run failed.');
|
|
12215
12469
|
if (isActiveConversationTarget(lockedTarget)) {
|
|
12216
12470
|
responseMsg = conversationWorkUiState(lockedConversationId, lockedTarget.workspaceId).activeWorkflowMsg || addMsg('assistant', '', state.mode, state.model);
|
|
12217
|
-
updateMsg(responseMsg, formatChatError(
|
|
12471
|
+
updateMsg(responseMsg, formatChatError(sendFailure, 'Agent run failed.'), 'error', state.mode, state.model);
|
|
12218
12472
|
}
|
|
12219
12473
|
}
|
|
12220
12474
|
if (api.getState) {
|
|
@@ -12265,7 +12519,15 @@ window.sendMessage = async function(modeOverride, queuedText, opts) {
|
|
|
12265
12519
|
if (resumedModeResult != null) state._syncedMode = state.mode;
|
|
12266
12520
|
}
|
|
12267
12521
|
var finalRuntime = state.conversationRuntimeStates && state.conversationRuntimeStates[lockedRuntimeKey];
|
|
12268
|
-
|
|
12522
|
+
var finalRunMatches = !finalRuntime || !finalRuntime.runId || finalRuntime.runId === localRunId || (r && finalRuntime.runId === r.runId);
|
|
12523
|
+
if (sendFailure) {
|
|
12524
|
+
// The IPC contract returns terminal failures as { error } while the
|
|
12525
|
+
// target-scoped error work event may arrive on the next task. Never let
|
|
12526
|
+
// the renderer's provisional run fall through to completed in that gap.
|
|
12527
|
+
if (finalRunMatches) {
|
|
12528
|
+
setConversationRuntimeState(lockedTarget, 'error', (r && r.runId) || (finalRuntime && finalRuntime.runId) || localRunId, { provisional: false });
|
|
12529
|
+
}
|
|
12530
|
+
} else if (finalRunMatches) {
|
|
12269
12531
|
setConversationRuntimeState(lockedTarget, 'completed', (r && r.runId) || localRunId);
|
|
12270
12532
|
}
|
|
12271
12533
|
setWorking(!!runningConversationRecord(activeConversationId()));
|
|
@@ -12683,6 +12945,7 @@ window.switchRightTab = function(tab) {
|
|
|
12683
12945
|
if (tab === 'subagent') window.renderSubagentList();
|
|
12684
12946
|
if (tab === 'browser') {
|
|
12685
12947
|
cancelBrowserGuestIdleDestroy();
|
|
12948
|
+
window.refreshComputerUseState(currentConversationTarget());
|
|
12686
12949
|
window.ensureBrowserPanel({ activate: true }).catch(function(error) {
|
|
12687
12950
|
console.error('[Browser] guest initialization failed:', error && error.message ? error.message : String(error));
|
|
12688
12951
|
});
|
|
@@ -13167,7 +13430,7 @@ window.terminalSend = function() {
|
|
|
13167
13430
|
var startedPane = document.querySelector('.terminal-pane[data-session="' + resp.sessionId + '"]');
|
|
13168
13431
|
var startedInput = startedPane && startedPane.querySelector('.terminal-input');
|
|
13169
13432
|
if (startedInput && startedInput.value === cmd) startedInput.value = '';
|
|
13170
|
-
return api.terminalWrite(resp.sessionId, cmd + '\r
|
|
13433
|
+
return api.terminalWrite(resp.sessionId, cmd + '\r');
|
|
13171
13434
|
}).catch(function(err) {
|
|
13172
13435
|
input.disabled = false;
|
|
13173
13436
|
var currentPane = window.getActiveTerminalPane();
|
|
@@ -13176,7 +13439,7 @@ window.terminalSend = function() {
|
|
|
13176
13439
|
});
|
|
13177
13440
|
}
|
|
13178
13441
|
input.value = '';
|
|
13179
|
-
api.terminalWrite(sessionId, cmd + '\r
|
|
13442
|
+
api.terminalWrite(sessionId, cmd + '\r').catch(function(err) {
|
|
13180
13443
|
if (output) output.innerHTML += '\r\n<span style="color:#ff6666;">[' + esc(t('common.error')) + '] ' + esc(err.message) + '</span>';
|
|
13181
13444
|
});
|
|
13182
13445
|
};
|
|
@@ -15019,36 +15282,12 @@ renderArchiveSettings = function() {
|
|
|
15019
15282
|
};
|
|
15020
15283
|
|
|
15021
15284
|
window.archiveCurrent = function() {
|
|
15022
|
-
if (!api.archive) return;
|
|
15023
15285
|
var currentId = activeConversationId();
|
|
15024
|
-
if (currentId
|
|
15025
|
-
|
|
15026
|
-
|
|
15027
|
-
|
|
15028
|
-
|
|
15029
|
-
if (!receipt || !receipt.ok) throw new Error((receipt && receipt.error) || 'Archive failed');
|
|
15030
|
-
showUiNotice('[Archive] ' + t('archive.saved') + ': ' + receipt.fileName, 'success', 'archive-saved-' + receipt.conversationId);
|
|
15031
|
-
return Promise.all([api.listArchives ? api.listArchives('workspace') : [], api.getState ? api.getState() : null]);
|
|
15032
|
-
}).then(function(result) {
|
|
15033
|
-
var items = result[0] || [];
|
|
15034
|
-
var refreshed = result[1];
|
|
15035
|
-
state.workspaceArchives = (items || []).map(function(a) {
|
|
15036
|
-
return { id: a.id || a.name || String(a), name: a.name || String(a), firstLine: a.firstLine || '', date: a.date || '', scope: a.scope || 'workspace', workspace: a.workspace || state.currentWorkspace || '', restorable: !!a.restorable, conversationId: a.conversationId || '' };
|
|
15037
|
-
});
|
|
15038
|
-
state.allArchives = [];
|
|
15039
|
-
if (refreshed && refreshed.conversations) {
|
|
15040
|
-
state.activeBackendConversationId = refreshed.conversationId || state.activeBackendConversationId;
|
|
15041
|
-
applyBackendConversations(refreshed.conversations, state.activeBackendConversationId, refreshed.workspaceId || runtimeWorkspaceId(''));
|
|
15042
|
-
if (refreshed.chatMessages) renderChatMessages(refreshed.chatMessages);
|
|
15043
|
-
renderConversations();
|
|
15044
|
-
}
|
|
15045
|
-
window.renderRightArchives();
|
|
15046
|
-
var stab = document.getElementById('stab-archive');
|
|
15047
|
-
if (stab) stab.innerHTML = renderArchiveSettings();
|
|
15048
|
-
if (state.settingsActiveTab === 'archive') window.settingsTab('archive');
|
|
15049
|
-
}).catch(function(err) {
|
|
15050
|
-
addMsg('assistant', '[Archive] ' + t('workspace.saveFailed') + ': ' + (err.message || String(err)), 'error', '');
|
|
15051
|
-
});
|
|
15286
|
+
if (!currentId || !window.archiveConv) return;
|
|
15287
|
+
// Use the same optimistic path as the conversation-row action. This keeps
|
|
15288
|
+
// the active conversation immediately gone even while a running/queued
|
|
15289
|
+
// runtime is being hard-stopped by the main process.
|
|
15290
|
+
return window.archiveConv(currentId);
|
|
15052
15291
|
};
|
|
15053
15292
|
|
|
15054
15293
|
window.loadArchive = function(idx, scope) {
|
|
@@ -16088,11 +16327,17 @@ window.loadFileTree = async function(options) {
|
|
|
16088
16327
|
};
|
|
16089
16328
|
|
|
16090
16329
|
window.submitSelectedFlow = function() {
|
|
16330
|
+
if (!state._flowsLoaded && window.ensureFlowsLoaded) {
|
|
16331
|
+
return window.ensureFlowsLoaded().then(function() { return window.submitSelectedFlow(); });
|
|
16332
|
+
}
|
|
16091
16333
|
var selected = String(state.defaultFlow || (document.getElementById('flow-select') && document.getElementById('flow-select').value) || '');
|
|
16092
16334
|
var workIdx = state.flowWorks.findIndex(function(work) { return String(work && work.name || '') === selected; });
|
|
16093
16335
|
if (workIdx < 0) {
|
|
16094
|
-
|
|
16095
|
-
|
|
16336
|
+
var message = state.flowWorks.length
|
|
16337
|
+
? (currentLang() === 'zh' ? '请先选择一个 Flow。' : 'Select a Flow first.')
|
|
16338
|
+
: t('flow.noAvailable');
|
|
16339
|
+
showUiNotice(message, 'error', 'flow-select-required');
|
|
16340
|
+
return { ok: false, error: message };
|
|
16096
16341
|
}
|
|
16097
16342
|
return window.runFlowWork(workIdx);
|
|
16098
16343
|
};
|
|
@@ -16849,11 +17094,63 @@ var NEWMARK_BROWSER_PARTITION = 'persist:newmark-browser';
|
|
|
16849
17094
|
var NEWMARK_BROWSER_MIN_CREATE_DELAY_MS = 5000;
|
|
16850
17095
|
var NEWMARK_BROWSER_IDLE_DESTROY_MS = 60000;
|
|
16851
17096
|
var browserGuestCreatePromise = null;
|
|
17097
|
+
var browserGuestCreatePromises = {};
|
|
16852
17098
|
var browserGuestIdleTimer = null;
|
|
16853
17099
|
var browserRetainedUrl = 'about:blank';
|
|
17100
|
+
var browserRetainedUrls = {};
|
|
16854
17101
|
var browserGuestCreateCount = 0;
|
|
16855
17102
|
var browserGuestFirstCreatedAtMs = null;
|
|
16856
17103
|
|
|
17104
|
+
function computerUseTargetKey(target) {
|
|
17105
|
+
target = target || currentConversationTarget();
|
|
17106
|
+
return runtimeKeyFor(target.workspaceId, target.conversationId);
|
|
17107
|
+
}
|
|
17108
|
+
|
|
17109
|
+
function renderComputerUseToggle(value, target) {
|
|
17110
|
+
var button = document.getElementById('computer-use-toggle');
|
|
17111
|
+
if (!button) return;
|
|
17112
|
+
var enabled = value && value.enabled !== false;
|
|
17113
|
+
var occupied = !!(value && value.occupied);
|
|
17114
|
+
var owner = value && value.ownerLabel ? ' · ' + String(value.ownerLabel) : '';
|
|
17115
|
+
button.classList.toggle('active', enabled);
|
|
17116
|
+
button.setAttribute('aria-pressed', enabled ? 'true' : 'false');
|
|
17117
|
+
button.title = (enabled ? 'ComputerUse enabled for this conversation' : 'ComputerUse disabled for this conversation') + owner;
|
|
17118
|
+
button.textContent = enabled ? (occupied ? 'CU●' : 'CU') : 'CU×';
|
|
17119
|
+
button.dataset.computerUseRuntimeKey = computerUseTargetKey(target);
|
|
17120
|
+
}
|
|
17121
|
+
|
|
17122
|
+
window.refreshComputerUseState = function(target) {
|
|
17123
|
+
target = target || currentConversationTarget();
|
|
17124
|
+
var key = computerUseTargetKey(target);
|
|
17125
|
+
var cached = state.computerUseByTarget && state.computerUseByTarget[key];
|
|
17126
|
+
if (cached) renderComputerUseToggle(cached, target);
|
|
17127
|
+
if (!api.computerUseState) return Promise.resolve(cached || { enabled: true, occupied: false, runtimeKey: key });
|
|
17128
|
+
return api.computerUseState(target).then(function(value) {
|
|
17129
|
+
if (!state.computerUseByTarget) state.computerUseByTarget = {};
|
|
17130
|
+
state.computerUseByTarget[key] = value || { enabled: true, occupied: false, runtimeKey: key };
|
|
17131
|
+
if (isActiveConversationTarget(target)) renderComputerUseToggle(state.computerUseByTarget[key], target);
|
|
17132
|
+
return state.computerUseByTarget[key];
|
|
17133
|
+
}).catch(function() { return cached || { enabled: true, occupied: false, runtimeKey: key }; });
|
|
17134
|
+
};
|
|
17135
|
+
|
|
17136
|
+
window.toggleComputerUse = function() {
|
|
17137
|
+
var target = currentConversationTarget();
|
|
17138
|
+
var key = computerUseTargetKey(target);
|
|
17139
|
+
var current = state.computerUseByTarget && state.computerUseByTarget[key];
|
|
17140
|
+
var enabled = !(current && current.enabled !== false);
|
|
17141
|
+
if (!api.setComputerUseEnabled) return Promise.resolve(null);
|
|
17142
|
+
return api.setComputerUseEnabled(target, enabled).then(function(result) {
|
|
17143
|
+
if (result && result.ok === false) {
|
|
17144
|
+
showUiNotice(String(result.error || 'ComputerUse occupied'), 'error', 'computer-use-toggle-' + key);
|
|
17145
|
+
return result;
|
|
17146
|
+
}
|
|
17147
|
+
if (!state.computerUseByTarget) state.computerUseByTarget = {};
|
|
17148
|
+
state.computerUseByTarget[key] = result && result.state ? result.state : { enabled: enabled, occupied: false, runtimeKey: key };
|
|
17149
|
+
renderComputerUseToggle(state.computerUseByTarget[key], target);
|
|
17150
|
+
return result;
|
|
17151
|
+
});
|
|
17152
|
+
};
|
|
17153
|
+
|
|
16857
17154
|
window.browserGuestLifecycleSnapshot = function() {
|
|
16858
17155
|
return {
|
|
16859
17156
|
createCount: browserGuestCreateCount,
|
|
@@ -16870,15 +17167,60 @@ function browserUrlsEquivalent(left, right) {
|
|
|
16870
17167
|
return !!a && a === b;
|
|
16871
17168
|
}
|
|
16872
17169
|
|
|
16873
|
-
function
|
|
16874
|
-
|
|
16875
|
-
return
|
|
17170
|
+
function browserTargetKey(target) {
|
|
17171
|
+
target = target || currentConversationTarget();
|
|
17172
|
+
return runtimeKeyFor(target.workspaceId, target.conversationId);
|
|
17173
|
+
}
|
|
17174
|
+
|
|
17175
|
+
function browserPartitionForTarget(target) {
|
|
17176
|
+
var key = browserTargetKey(target).replace(/[^A-Za-z0-9_.-]/g, '_').slice(0, 150) || 'default';
|
|
17177
|
+
return NEWMARK_BROWSER_PARTITION + '-' + key;
|
|
17178
|
+
}
|
|
17179
|
+
|
|
17180
|
+
function browserGuestForTarget(target) {
|
|
17181
|
+
var key = browserTargetKey(target);
|
|
17182
|
+
var frame = els['browser-frame'] || document.getElementById('browser-frame');
|
|
17183
|
+
if (!frame) return null;
|
|
17184
|
+
var views = frame.querySelectorAll('webview[data-newmark-browser-runtime-key]');
|
|
17185
|
+
for (var i = 0; i < views.length; i++) {
|
|
17186
|
+
if (String(views[i].getAttribute('data-newmark-browser-runtime-key') || '') === key && views[i].isConnected) return views[i];
|
|
17187
|
+
}
|
|
17188
|
+
return null;
|
|
16876
17189
|
}
|
|
16877
17190
|
|
|
16878
|
-
function
|
|
17191
|
+
function setActiveBrowserGuest(view, target) {
|
|
17192
|
+
var key = browserTargetKey(target);
|
|
17193
|
+
var frame = els['browser-frame'] || document.getElementById('browser-frame');
|
|
17194
|
+
if (frame) {
|
|
17195
|
+
var views = frame.querySelectorAll('webview[data-newmark-browser-runtime-key]');
|
|
17196
|
+
for (var i = 0; i < views.length; i++) {
|
|
17197
|
+
var same = views[i] === view || String(views[i].getAttribute('data-newmark-browser-runtime-key') || '') === key;
|
|
17198
|
+
views[i].style.display = same ? '' : 'none';
|
|
17199
|
+
if (same) views[i].id = 'browser-webview';
|
|
17200
|
+
else if (views[i].id === 'browser-webview') views[i].id = 'browser-webview-' + String(views[i].getAttribute('data-newmark-browser-runtime-key') || 'guest').replace(/[^A-Za-z0-9_.-]/g, '_').slice(-80);
|
|
17201
|
+
}
|
|
17202
|
+
}
|
|
17203
|
+
els['browser-webview'] = view || null;
|
|
17204
|
+
if (view) view._newmarkBrowserTargetKey = key;
|
|
17205
|
+
}
|
|
17206
|
+
|
|
17207
|
+
function currentBrowserGuest(target) {
|
|
17208
|
+
var view = browserGuestForTarget(target || currentConversationTarget());
|
|
17209
|
+
if (view) return view;
|
|
17210
|
+
var active = document.getElementById('browser-webview');
|
|
17211
|
+
return active && active.isConnected ? active : null;
|
|
17212
|
+
}
|
|
17213
|
+
|
|
17214
|
+
function rememberBrowserUrl(url, force, target) {
|
|
17215
|
+
target = target || currentConversationTarget();
|
|
17216
|
+
var targetKey = browserTargetKey(target);
|
|
16879
17217
|
url = String(url || '').trim();
|
|
16880
|
-
|
|
16881
|
-
if (url
|
|
17218
|
+
var prior = browserRetainedUrls[targetKey] || 'about:blank';
|
|
17219
|
+
if (!force && url === 'about:blank' && prior !== 'about:blank') return;
|
|
17220
|
+
if (url) {
|
|
17221
|
+
browserRetainedUrls[targetKey] = url;
|
|
17222
|
+
if (isActiveConversationTarget(target)) browserRetainedUrl = url;
|
|
17223
|
+
}
|
|
16882
17224
|
}
|
|
16883
17225
|
|
|
16884
17226
|
function waitForBrowserCreationFloor() {
|
|
@@ -16887,8 +17229,9 @@ function waitForBrowserCreationFloor() {
|
|
|
16887
17229
|
return new Promise(function(resolve) { setTimeout(resolve, remaining); });
|
|
16888
17230
|
}
|
|
16889
17231
|
|
|
16890
|
-
function createBrowserGuestElement() {
|
|
16891
|
-
|
|
17232
|
+
function createBrowserGuestElement(target) {
|
|
17233
|
+
target = target || currentConversationTarget();
|
|
17234
|
+
var existing = browserGuestForTarget(target);
|
|
16892
17235
|
if (existing) return existing;
|
|
16893
17236
|
var frame = els['browser-frame'] || document.getElementById('browser-frame');
|
|
16894
17237
|
if (!frame) throw new Error('Built-in Browser frame is unavailable');
|
|
@@ -16898,8 +17241,14 @@ function createBrowserGuestElement() {
|
|
|
16898
17241
|
browserGuestCreateCount += 1;
|
|
16899
17242
|
if (browserGuestFirstCreatedAtMs === null) browserGuestFirstCreatedAtMs = Date.now() - newmarkRendererStartedAt;
|
|
16900
17243
|
view.id = 'browser-webview';
|
|
16901
|
-
view.setAttribute('
|
|
17244
|
+
view.setAttribute('data-newmark-browser-runtime-key', browserTargetKey(target));
|
|
17245
|
+
view.setAttribute('partition', browserPartitionForTarget(target));
|
|
16902
17246
|
frame.appendChild(view);
|
|
17247
|
+
if (isActiveConversationTarget(target)) setActiveBrowserGuest(view, target);
|
|
17248
|
+
else {
|
|
17249
|
+
view.style.display = 'none';
|
|
17250
|
+
view.id = 'browser-webview-' + browserTargetKey(target).replace(/[^A-Za-z0-9_.-]/g, '_').slice(-80);
|
|
17251
|
+
}
|
|
16903
17252
|
els['browser-webview'] = view;
|
|
16904
17253
|
return view;
|
|
16905
17254
|
}
|
|
@@ -16922,11 +17271,14 @@ function cancelBrowserGuestIdleDestroy() {
|
|
|
16922
17271
|
function destroyIdleBrowserGuest() {
|
|
16923
17272
|
browserGuestIdleTimer = null;
|
|
16924
17273
|
if (state.rightTab === 'browser') return;
|
|
16925
|
-
var view = currentBrowserGuest();
|
|
17274
|
+
var view = currentBrowserGuest(currentConversationTarget());
|
|
16926
17275
|
if (!view) return;
|
|
16927
|
-
|
|
17276
|
+
var target = { workspaceId: state.currentWorkspaceId || state.currentWorkspacePath || state.currentWorkspace || 'none', conversationId: activeConversationId() || 'default' };
|
|
17277
|
+
try { rememberBrowserUrl(view.getURL ? view.getURL() : '', false, target); } catch {}
|
|
16928
17278
|
if (view.parentNode) view.parentNode.removeChild(view);
|
|
16929
17279
|
if (els['browser-webview'] === view) els['browser-webview'] = null;
|
|
17280
|
+
var key = browserTargetKey(target);
|
|
17281
|
+
delete browserGuestCreatePromises[key];
|
|
16930
17282
|
browserGuestCreatePromise = null;
|
|
16931
17283
|
restoreBrowserPlaceholder();
|
|
16932
17284
|
}
|
|
@@ -16958,16 +17310,17 @@ function navigateBrowserView(view, url) {
|
|
|
16958
17310
|
}
|
|
16959
17311
|
|
|
16960
17312
|
window.navigateBrowser = function(url) {
|
|
17313
|
+
var target = currentConversationTarget();
|
|
16961
17314
|
url = String(url || '').trim();
|
|
16962
17315
|
if (!/^(?:https?:\/\/|about:blank$|newmark-preview:\/\/)/i.test(url)) {
|
|
16963
17316
|
url = 'https://' + url;
|
|
16964
17317
|
}
|
|
16965
17318
|
if (state.browserPreviewToken && url !== state.browserPreviewUrl) window.revokeBrowserPreviewCapability();
|
|
16966
|
-
rememberBrowserUrl(url, true);
|
|
17319
|
+
rememberBrowserUrl(url, true, target);
|
|
16967
17320
|
var input = els['browser-url'];
|
|
16968
17321
|
if (input) input.value = url;
|
|
16969
17322
|
cancelBrowserGuestIdleDestroy();
|
|
16970
|
-
return window.ensureBrowserPanel({ activate: false }).then(function(view) {
|
|
17323
|
+
return window.ensureBrowserPanel({ activate: false, target: target }).then(function(view) {
|
|
16971
17324
|
navigateBrowserView(view, url);
|
|
16972
17325
|
if (state.rightTab !== 'browser') scheduleBrowserGuestIdleDestroy();
|
|
16973
17326
|
return view;
|
|
@@ -16996,6 +17349,8 @@ window.browserReload = function() {
|
|
|
16996
17349
|
};
|
|
16997
17350
|
|
|
16998
17351
|
function initializeBrowserGuest(view) {
|
|
17352
|
+
var target = arguments.length > 1 && arguments[1] ? arguments[1] : currentConversationTarget();
|
|
17353
|
+
var targetKey = browserTargetKey(target);
|
|
16999
17354
|
if (!view._newmarkReadyPromise) {
|
|
17000
17355
|
view._newmarkDomReady = false;
|
|
17001
17356
|
view._newmarkReadySettled = false;
|
|
@@ -17011,12 +17366,12 @@ function initializeBrowserGuest(view) {
|
|
|
17011
17366
|
currentUrl = view.getURL ? view.getURL() : '';
|
|
17012
17367
|
if (input && currentUrl && currentUrl !== 'about:blank') input.value = currentUrl;
|
|
17013
17368
|
} catch {}
|
|
17014
|
-
rememberBrowserUrl(currentUrl);
|
|
17369
|
+
rememberBrowserUrl(currentUrl, false, target);
|
|
17015
17370
|
if (view._newmarkReadySettled) return;
|
|
17016
17371
|
var registration = Promise.resolve({ accepted: true });
|
|
17017
17372
|
try {
|
|
17018
17373
|
if (!api.browserRegisterGuest || !view.getWebContentsId) throw new Error('Built-in Browser registration API is unavailable');
|
|
17019
|
-
registration = api.browserRegisterGuest(view.getWebContentsId());
|
|
17374
|
+
registration = api.browserRegisterGuest(view.getWebContentsId(), target);
|
|
17020
17375
|
} catch (registrationError) {
|
|
17021
17376
|
registration = Promise.reject(registrationError);
|
|
17022
17377
|
}
|
|
@@ -17056,13 +17411,13 @@ function initializeBrowserGuest(view) {
|
|
|
17056
17411
|
view.addEventListener('did-navigate', function(e) {
|
|
17057
17412
|
var input = els['browser-url'];
|
|
17058
17413
|
if (input && e.url && e.url !== 'about:blank') input.value = e.url;
|
|
17059
|
-
rememberBrowserUrl(e.url);
|
|
17414
|
+
rememberBrowserUrl(e.url, false, target);
|
|
17060
17415
|
if (browserUrlsEquivalent(e.url, view._newmarkPendingUrl)) view._newmarkPendingUrl = '';
|
|
17061
17416
|
});
|
|
17062
17417
|
view.addEventListener('did-navigate-in-page', function(e) {
|
|
17063
17418
|
var input = els['browser-url'];
|
|
17064
17419
|
if (input && e.url && e.url !== 'about:blank') input.value = e.url;
|
|
17065
|
-
rememberBrowserUrl(e.url);
|
|
17420
|
+
rememberBrowserUrl(e.url, false, target);
|
|
17066
17421
|
if (browserUrlsEquivalent(e.url, view._newmarkPendingUrl)) view._newmarkPendingUrl = '';
|
|
17067
17422
|
});
|
|
17068
17423
|
view.addEventListener('did-fail-load', function(e) {
|
|
@@ -17076,27 +17431,38 @@ function initializeBrowserGuest(view) {
|
|
|
17076
17431
|
});
|
|
17077
17432
|
var current = '';
|
|
17078
17433
|
try { current = view.getURL ? view.getURL() : ''; } catch { current = ''; }
|
|
17079
|
-
if (!current && !view.getAttribute('src')) view.setAttribute('src', 'about:blank');
|
|
17434
|
+
if (!current && !view.getAttribute('src')) view.setAttribute('src', browserRetainedUrls[targetKey] || 'about:blank');
|
|
17080
17435
|
}
|
|
17081
17436
|
return view._newmarkReadyPromise;
|
|
17082
17437
|
}
|
|
17083
17438
|
|
|
17084
17439
|
window.ensureBrowserPanel = function(options) {
|
|
17085
17440
|
options = options || {};
|
|
17441
|
+
var target = options.target || currentConversationTarget();
|
|
17442
|
+
var targetKey = browserTargetKey(target);
|
|
17086
17443
|
cancelBrowserGuestIdleDestroy();
|
|
17087
|
-
if (!
|
|
17088
|
-
|
|
17089
|
-
|
|
17444
|
+
if (!browserGuestCreatePromises[targetKey]) {
|
|
17445
|
+
// Keep background Browser-Use demand off the startup hot path, but never
|
|
17446
|
+
// make a deliberate visible-tab click wait behind that memory guard. A
|
|
17447
|
+
// user explicitly opening Browser is an interaction deadline, not a
|
|
17448
|
+
// background prewarm request.
|
|
17449
|
+
var creationWait = options.activate === true
|
|
17450
|
+
? Promise.resolve()
|
|
17451
|
+
: waitForBrowserCreationFloor();
|
|
17452
|
+
browserGuestCreatePromises[targetKey] = creationWait.then(function() {
|
|
17453
|
+
return initializeBrowserGuest(createBrowserGuestElement(target), target);
|
|
17090
17454
|
}).catch(function(error) {
|
|
17091
|
-
|
|
17455
|
+
delete browserGuestCreatePromises[targetKey];
|
|
17092
17456
|
throw error;
|
|
17093
17457
|
});
|
|
17094
17458
|
}
|
|
17095
|
-
|
|
17459
|
+
browserGuestCreatePromise = browserGuestCreatePromises[targetKey];
|
|
17460
|
+
return browserGuestCreatePromises[targetKey].then(function(view) {
|
|
17461
|
+
if (isActiveConversationTarget(target)) setActiveBrowserGuest(view, target);
|
|
17096
17462
|
if (options.activate === true) {
|
|
17097
17463
|
var current = '';
|
|
17098
17464
|
try { current = view.getURL ? view.getURL() : ''; } catch { current = ''; }
|
|
17099
|
-
var targetUrl =
|
|
17465
|
+
var targetUrl = browserRetainedUrls[targetKey] || 'about:blank';
|
|
17100
17466
|
if (targetUrl !== 'about:blank' && (!current || current === 'about:blank') && !view._newmarkPendingUrl) {
|
|
17101
17467
|
navigateBrowserView(view, targetUrl);
|
|
17102
17468
|
}
|
|
@@ -17323,7 +17689,7 @@ function renderConversations() {
|
|
|
17323
17689
|
? '<span class="conv-runtime-badge ' + escAttr(String(runtimeState.status)) + '">' + esc(String(runtimeState.status)) + '</span>' : '';
|
|
17324
17690
|
div.innerHTML = '<span class="conv-summary" title="' + escAttr(String(conv.id || '')) + '">' + esc(displaySummary) + (conv.messageCount ? ' (' + esc(String(conv.messageCount)) + ')' : '') + '</span>' + runtimeBadge +
|
|
17325
17691
|
'<button class="conv-rename-btn" onclick="event.stopPropagation();window.editConversationName(' + i + ')" title="' + escAttr(t('conversation.rename')) + '">' + iconOnly('pencil', t('conversation.rename')) + '</button>' +
|
|
17326
|
-
'<button class="conv-archive-btn"
|
|
17692
|
+
'<button class="conv-archive-btn" onclick="event.stopPropagation();window.archiveConv(this.closest(".conv-item").getAttribute("data-conversation-id"))" title="' + escAttr(t('conversation.archive')) + '">' + iconOnly('archive', t('conversation.archive')) + '</button>' +
|
|
17327
17693
|
'<button class="conv-pin-btn' + (conv.pinned ? ' active' : '') + '" onclick="event.stopPropagation();window.toggleConversationPinned(' + i + ')" title="' + escAttr(conv.pinned ? t('conversation.unpin') : t('conversation.pin')) + '">' + iconOnly('pin', conv.pinned ? t('conversation.unpin') : t('conversation.pin')) + '</button>';
|
|
17328
17694
|
div.onclick = function(idx) { return function() { window.switchConversation(idx); }; }(i);
|
|
17329
17695
|
div.addEventListener('dragstart', function(event) {
|
|
@@ -17479,6 +17845,13 @@ window.switchConversation = function(idx) {
|
|
|
17479
17845
|
if (previousId) markConversationTracked(previousId, state.conversationTrackMs || 300000);
|
|
17480
17846
|
setActiveWorkspaceConversation(idx);
|
|
17481
17847
|
var activeId = activeConversationId();
|
|
17848
|
+
var activeBrowserTarget = currentConversationTarget(activeId);
|
|
17849
|
+
var activeBrowserView = browserGuestForTarget(activeBrowserTarget);
|
|
17850
|
+
if (activeBrowserView) setActiveBrowserGuest(activeBrowserView, activeBrowserTarget);
|
|
17851
|
+
else if (state.rightTab === 'browser') window.ensureBrowserPanel({ target: activeBrowserTarget, activate: true }).catch(function(error) {
|
|
17852
|
+
console.error('[Browser] conversation-bound guest initialization failed:', error && error.message ? error.message : String(error));
|
|
17853
|
+
});
|
|
17854
|
+
window.refreshComputerUseState(activeBrowserTarget);
|
|
17482
17855
|
state.backendQueue = backendQueueForTarget(currentConversationTarget(activeId));
|
|
17483
17856
|
holdForegroundConversation(activeId, 4500);
|
|
17484
17857
|
markConversationTracked(activeId, state.conversationTrackMs || 300000);
|
|
@@ -17518,8 +17891,13 @@ function refreshConversationArchivesAfterBatch() {
|
|
|
17518
17891
|
state.workspaceArchives = (items || []).map(function(a) {
|
|
17519
17892
|
return { id: a.id || a.name || String(a), name: a.name || String(a), firstLine: a.firstLine || '', date: a.date || '', scope: a.scope || 'workspace', workspace: a.workspace || state.currentWorkspace || '', restorable: !!a.restorable, conversationId: a.conversationId || '' };
|
|
17520
17893
|
});
|
|
17521
|
-
|
|
17894
|
+
var archiveSettingsOpen = typeof document !== 'undefined'
|
|
17895
|
+
&& state.settingsActiveTab === 'archive'
|
|
17896
|
+
&& !!document.getElementById('stab-archive');
|
|
17897
|
+
if (!archiveSettingsOpen) state.allArchives = [];
|
|
17522
17898
|
window.renderRightArchives();
|
|
17899
|
+
var stab = typeof document !== 'undefined' ? document.getElementById('stab-archive') : null;
|
|
17900
|
+
if (stab && archiveSettingsOpen) stab.innerHTML = renderArchiveSettings();
|
|
17523
17901
|
return items || [];
|
|
17524
17902
|
});
|
|
17525
17903
|
}
|
|
@@ -17554,10 +17932,6 @@ window.archiveConv = function(conversationId) {
|
|
|
17554
17932
|
var targetRuntime = currentConversationTarget(targetId);
|
|
17555
17933
|
var workspaceKey = currentWorkspaceKey();
|
|
17556
17934
|
var pendingKey = workspaceKey + '::' + targetId;
|
|
17557
|
-
if (runningConversationRecord(targetId)) {
|
|
17558
|
-
showUiNotice(currentLang() === 'zh' ? '运行中的对话不能归档。' : 'A running conversation cannot be archived.', 'error', 'archive-running-' + currentRuntimeKey(targetId));
|
|
17559
|
-
return;
|
|
17560
|
-
}
|
|
17561
17935
|
if (state.conversationArchivePending[pendingKey]) return;
|
|
17562
17936
|
var priorActiveId = String(((convs.find(function(item) { return item && item.active; }) || {}).id) || '');
|
|
17563
17937
|
var rollbackOrder = convs.map(function(item) { return String(item && item.id || 'default'); });
|
|
@@ -17597,39 +17971,37 @@ window.archiveConv = function(conversationId) {
|
|
|
17597
17971
|
archivePromise.then(function(receipt) {
|
|
17598
17972
|
if (!receipt || receipt.ok !== true) throw new Error((receipt && receipt.error) || 'Archive failed');
|
|
17599
17973
|
delete state.conversationArchivePending[pendingKey];
|
|
17974
|
+
var optimisticArchive = {
|
|
17975
|
+
id: receipt.fileName,
|
|
17976
|
+
name: receipt.fileName,
|
|
17977
|
+
firstLine: target.summary || '',
|
|
17978
|
+
date: new Date().toISOString(),
|
|
17979
|
+
scope: 'workspace',
|
|
17980
|
+
workspace: state.currentWorkspace || '',
|
|
17981
|
+
restorable: true,
|
|
17982
|
+
conversationId: receipt.conversationId || targetId,
|
|
17983
|
+
};
|
|
17984
|
+
state.workspaceArchives = (state.workspaceArchives || []).filter(function(item) {
|
|
17985
|
+
return String(item && item.id || item && item.name || '') !== String(optimisticArchive.id);
|
|
17986
|
+
});
|
|
17987
|
+
state.workspaceArchives.unshift(optimisticArchive);
|
|
17988
|
+
if (Array.isArray(state.allArchives) && state.allArchives.length) {
|
|
17989
|
+
state.allArchives = state.allArchives.filter(function(item) {
|
|
17990
|
+
return String(item && item.id || item && item.name || '') !== String(optimisticArchive.id);
|
|
17991
|
+
});
|
|
17992
|
+
state.allArchives.unshift(optimisticArchive);
|
|
17993
|
+
}
|
|
17994
|
+
window.renderRightArchives();
|
|
17995
|
+
var archiveSettings = typeof document !== 'undefined' ? document.getElementById('stab-archive') : null;
|
|
17996
|
+
if (archiveSettings && state.settingsActiveTab === 'archive') archiveSettings.innerHTML = renderArchiveSettings();
|
|
17600
17997
|
showUiNotice('[Archive] ' + t('archive.saved') + ': ' + receipt.fileName, 'success', 'archive-saved-' + targetId);
|
|
17601
17998
|
scheduleConversationArchiveRefresh(workspaceKey);
|
|
17602
17999
|
}).catch(function(err) {
|
|
17603
|
-
var pending = state.conversationArchivePending[pendingKey];
|
|
17604
18000
|
delete state.conversationArchivePending[pendingKey];
|
|
17605
|
-
|
|
17606
|
-
|
|
17607
|
-
|
|
17608
|
-
|
|
17609
|
-
var targetOrderIndex = orderIds.indexOf(targetId);
|
|
17610
|
-
for (var beforeIndex = targetOrderIndex - 1; beforeIndex >= 0 && restoreAt < 0; beforeIndex--) {
|
|
17611
|
-
var precedingIndex = workspaceConversations.findIndex(function(item) { return String(item && item.id || '') === orderIds[beforeIndex]; });
|
|
17612
|
-
if (precedingIndex >= 0) restoreAt = precedingIndex + 1;
|
|
17613
|
-
}
|
|
17614
|
-
for (var afterIndex = targetOrderIndex + 1; afterIndex < orderIds.length && restoreAt < 0; afterIndex++) {
|
|
17615
|
-
var followingIndex = workspaceConversations.findIndex(function(item) { return String(item && item.id || '') === orderIds[afterIndex]; });
|
|
17616
|
-
if (followingIndex >= 0) restoreAt = followingIndex;
|
|
17617
|
-
}
|
|
17618
|
-
if (restoreAt < 0) restoreAt = Math.min(Number(pending.index || 0), workspaceConversations.length);
|
|
17619
|
-
workspaceConversations.splice(restoreAt, 0, pending.target);
|
|
17620
|
-
if (pending.wasActive) {
|
|
17621
|
-
for (var j = 0; j < workspaceConversations.length; j++) workspaceConversations[j].active = String(workspaceConversations[j].id || '') === targetId;
|
|
17622
|
-
}
|
|
17623
|
-
}
|
|
17624
|
-
if (workspaceKey === currentWorkspaceKey()) {
|
|
17625
|
-
state.conversations = workspaceConversations;
|
|
17626
|
-
state.activeConversation = Math.max(0, workspaceConversations.findIndex(function(item) { return item && item.active; }));
|
|
17627
|
-
if (pending && pending.wasActive) {
|
|
17628
|
-
state.activeBackendConversationId = targetId;
|
|
17629
|
-
if (els['chat-area']) els['chat-area'].innerHTML = pending.chatHtml || '';
|
|
17630
|
-
}
|
|
17631
|
-
renderConversations();
|
|
17632
|
-
}
|
|
18001
|
+
// Keep the optimistic removal authoritative for this renderer session.
|
|
18002
|
+
// A failed IPC receipt is surfaced, but never resurrects the row under
|
|
18003
|
+
// the user's pointer; the next explicit workspace refresh is the only
|
|
18004
|
+
// path allowed to reconcile a failed destructive operation.
|
|
17633
18005
|
showUiNotice('[Archive] ' + t('workspace.saveFailed') + ': ' + (err.message || String(err)), 'error', 'archive-failed-' + targetId);
|
|
17634
18006
|
});
|
|
17635
18007
|
}
|
|
@@ -19404,18 +19776,26 @@ window.saveWsSetting = function(key, value) {
|
|
|
19404
19776
|
window.renderFlowSelector = function() {
|
|
19405
19777
|
var flowSel = document.getElementById('flow-select');
|
|
19406
19778
|
if (!flowSel) return;
|
|
19779
|
+
var flows = Array.isArray(state.flowWorks) ? state.flowWorks : [];
|
|
19407
19780
|
var defaultFlow = state.defaultFlow || '';
|
|
19781
|
+
if (flows.length === 0) {
|
|
19782
|
+
state.defaultFlow = '';
|
|
19783
|
+
flowSel.disabled = true;
|
|
19784
|
+
flowSel.innerHTML = '<option value="">' + esc(t('flow.noAvailable')) + '</option>';
|
|
19785
|
+
if (window.syncNewmarkSelect) window.syncNewmarkSelect(flowSel);
|
|
19786
|
+
return;
|
|
19787
|
+
}
|
|
19788
|
+
flowSel.disabled = false;
|
|
19408
19789
|
var html = '';
|
|
19409
|
-
for (var fi = 0; fi <
|
|
19410
|
-
var selected =
|
|
19411
|
-
html += '<option value="' + esc(
|
|
19790
|
+
for (var fi = 0; fi < flows.length; fi++) {
|
|
19791
|
+
var selected = flows[fi].name === defaultFlow ? ' selected' : '';
|
|
19792
|
+
html += '<option value="' + esc(flows[fi].name) + '"' + selected + '>' + esc(flows[fi].name) + '</option>';
|
|
19412
19793
|
}
|
|
19413
19794
|
flowSel.innerHTML = html;
|
|
19414
|
-
if (
|
|
19415
|
-
|
|
19416
|
-
state.defaultFlow = flowSel.value;
|
|
19417
|
-
}
|
|
19795
|
+
if (defaultFlow) flowSel.value = defaultFlow;
|
|
19796
|
+
state.defaultFlow = flowSel.value;
|
|
19418
19797
|
flowSel.onchange = function() { state.defaultFlow = this.value; api.saveConfig({defaultFlow: state.defaultFlow}); };
|
|
19798
|
+
if (window.syncNewmarkSelect) window.syncNewmarkSelect(flowSel);
|
|
19419
19799
|
};
|
|
19420
19800
|
|
|
19421
19801
|
window.loadFlows = function(options) {
|
|
@@ -19680,9 +20060,10 @@ function schedulePostStartupUiRendering() {
|
|
|
19680
20060
|
cacheEls();
|
|
19681
20061
|
setupAgentWorkEvents();
|
|
19682
20062
|
if (api.onBrowserEnsureGuest) {
|
|
19683
|
-
api.onBrowserEnsureGuest(function() {
|
|
20063
|
+
api.onBrowserEnsureGuest(function(target) {
|
|
19684
20064
|
cancelBrowserGuestIdleDestroy();
|
|
19685
|
-
|
|
20065
|
+
var requestedTarget = target || currentConversationTarget();
|
|
20066
|
+
window.ensureBrowserPanel({ target: requestedTarget, activate: isActiveConversationTarget(requestedTarget) }).catch(function(error) {
|
|
19686
20067
|
console.error('[Browser] guest initialization failed:', error && error.message ? error.message : String(error));
|
|
19687
20068
|
});
|
|
19688
20069
|
});
|
|
@@ -19814,6 +20195,48 @@ function schedulePostStartupUiRendering() {
|
|
|
19814
20195
|
updateWorkspaceGate();
|
|
19815
20196
|
|
|
19816
20197
|
// === Event Listeners ===
|
|
20198
|
+
function escapeBelongsToFocusedControl(event) {
|
|
20199
|
+
var target = event && event.target;
|
|
20200
|
+
if (!target || target === document || target === document.body || target === document.documentElement) return false;
|
|
20201
|
+
if (els.prompt && (target === els.prompt || (els.prompt.contains && els.prompt.contains(target)))) return false;
|
|
20202
|
+
if (target.closest && target.closest('.modal.active, .sub-win.open, .newmark-select-shell.open')) return true;
|
|
20203
|
+
return !!(target.matches && target.matches('input, textarea, select, [contenteditable="true"]'));
|
|
20204
|
+
}
|
|
20205
|
+
if (api.onWorkspaceChanged) {
|
|
20206
|
+
var workspaceRefreshTimer = null;
|
|
20207
|
+
api.onWorkspaceChanged(function() {
|
|
20208
|
+
if (workspaceRefreshTimer) clearTimeout(workspaceRefreshTimer);
|
|
20209
|
+
workspaceRefreshTimer = setTimeout(function() {
|
|
20210
|
+
workspaceRefreshTimer = null;
|
|
20211
|
+
if (window.refreshWorkspaceState) window.refreshWorkspaceState().catch(function(){});
|
|
20212
|
+
if (document.getElementById('right-archive-list')) window.refreshRightArchives();
|
|
20213
|
+
}, 120);
|
|
20214
|
+
});
|
|
20215
|
+
}
|
|
20216
|
+
|
|
20217
|
+
function stopRunningFromEscape(event) {
|
|
20218
|
+
if (!event || event.key !== 'Escape' || event.defaultPrevented || escapeBelongsToFocusedControl(event)) return false;
|
|
20219
|
+
if (currentFlowRunning() && flowTakeoverMatchesCurrent() && !promptHasText()) {
|
|
20220
|
+
event.preventDefault();
|
|
20221
|
+
window.stopFlowRun();
|
|
20222
|
+
return true;
|
|
20223
|
+
}
|
|
20224
|
+
if (isCurrentConversationRunning() && !promptHasText()) {
|
|
20225
|
+
event.preventDefault();
|
|
20226
|
+
window.stopCurrentConversation();
|
|
20227
|
+
return true;
|
|
20228
|
+
}
|
|
20229
|
+
return false;
|
|
20230
|
+
}
|
|
20231
|
+
|
|
20232
|
+
// The prompt handler below covers the normal focused-input path. Keep a
|
|
20233
|
+
// document-level fallback so a physical Escape still stops a running target
|
|
20234
|
+
// after focus moved to the chat, title bar, button, or another non-editor
|
|
20235
|
+
// surface. Modal/editor/select Escape behavior remains owned by that UI.
|
|
20236
|
+
document.addEventListener('keydown', function(event) {
|
|
20237
|
+
if (stopRunningFromEscape(event)) event.stopPropagation();
|
|
20238
|
+
});
|
|
20239
|
+
|
|
19817
20240
|
if (els.prompt) {
|
|
19818
20241
|
els.prompt.addEventListener('keydown', function(e) {
|
|
19819
20242
|
if (e.key === 'Escape' && currentFlowRunning() && flowTakeoverMatchesCurrent() && !promptHasText()) {
|