newmark-agent 0.3.11 → 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 +2 -0
- package/dist/cli-help.js +25 -1
- package/dist/conversation-utility-host.bundle.cjs +357 -84
- package/dist/core/agent.d.ts +18 -3
- package/dist/core/agent.js +214 -30
- package/dist/core/agentKernelRunner.js +53 -8
- package/dist/core/config.d.ts +7 -2
- package/dist/core/config.js +24 -6
- package/dist/core/conversationKernel.js +1 -1
- 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/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 +40 -11
- package/dist/llm/provider.d.ts +8 -5
- package/dist/llm/provider.js +85 -33
- package/dist/main.js +167 -45
- package/dist/preload.js +6 -0
- 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/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 +250 -84
- package/dist/wsl-agent-host.bundle.cjs +357 -84
- package/package.json +14 -5
package/dist/ui/index.html
CHANGED
|
@@ -6063,6 +6063,7 @@ var NEWMARK_I18N = {
|
|
|
6063
6063
|
'flow.nextDisabled': 'Flow only accepts Guide input; Next is disabled.',
|
|
6064
6064
|
'flow.saved': 'Flow saved',
|
|
6065
6065
|
'flow.emptyRun': 'Flow is empty and cannot run.',
|
|
6066
|
+
'flow.noAvailable': 'No workflows are available to run.',
|
|
6066
6067
|
'flow.started': 'Flow started',
|
|
6067
6068
|
'plugins.title': 'Plugins',
|
|
6068
6069
|
'plugins.mcp': 'MCP Management',
|
|
@@ -6628,6 +6629,7 @@ var NEWMARK_I18N = {
|
|
|
6628
6629
|
'flow.nextDisabled': 'Flow 仅允许 Guide 输入,已禁用 Next。',
|
|
6629
6630
|
'flow.saved': 'Flow 已保存',
|
|
6630
6631
|
'flow.emptyRun': 'Flow 为空,无法运行。',
|
|
6632
|
+
'flow.noAvailable': '当前没有可运行的工作流。',
|
|
6631
6633
|
'flow.started': 'Flow 已启动',
|
|
6632
6634
|
'plugins.title': '插件',
|
|
6633
6635
|
'plugins.mcp': 'MCP 管理',
|
|
@@ -8329,15 +8331,26 @@ function addWorkReview(diffs) {
|
|
|
8329
8331
|
return review;
|
|
8330
8332
|
}
|
|
8331
8333
|
|
|
8332
|
-
|
|
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) {
|
|
8333
8338
|
if (!div) return;
|
|
8334
8339
|
if (role) div.className = 'chat-msg ' + role;
|
|
8335
8340
|
text = redactSensitiveText(text);
|
|
8336
8341
|
div._newmarkMessageText = String(text || '');
|
|
8337
8342
|
var body = div.querySelector('.msg-body');
|
|
8338
|
-
|
|
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
|
+
}
|
|
8339
8352
|
var meta = div.querySelector('.meta');
|
|
8340
|
-
if (meta && (mode || model)) {
|
|
8353
|
+
if (meta && (mode || model) && !streaming) {
|
|
8341
8354
|
var roleLabel = div.classList.contains('user')
|
|
8342
8355
|
? (String(mode || '').toLowerCase() === 'flow-user-input' ? t('flow.userInput') : t('message.user'))
|
|
8343
8356
|
: (div.classList.contains('workflow') ? t('message.workflow') : (div.classList.contains('system') ? t('message.system') : t('message.agent')));
|
|
@@ -8746,6 +8759,60 @@ function setConversationRuntimeState(target, status, runId, extra) {
|
|
|
8746
8759
|
return next;
|
|
8747
8760
|
}
|
|
8748
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
|
+
|
|
8749
8816
|
function formatWorkDuration(milliseconds) {
|
|
8750
8817
|
var seconds = Math.max(0, Math.floor(Number(milliseconds || 0) / 1000));
|
|
8751
8818
|
var hours = Math.floor(seconds / 3600);
|
|
@@ -9476,7 +9543,14 @@ function renderWorkRunEvents(run, includeGuides) {
|
|
|
9476
9543
|
var responseLabel = type === 'response' && terminalInterrupted
|
|
9477
9544
|
? (currentLang() === 'zh' ? '未完成回复片段\n' : 'Incomplete response fragment\n') + workEventLabel(event)
|
|
9478
9545
|
: workEventLabel(event);
|
|
9479
|
-
|
|
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>';
|
|
9480
9554
|
}
|
|
9481
9555
|
if (type.indexOf('guide') === 0 || event.guide) return renderWorkRunGuideMessage(event);
|
|
9482
9556
|
if (type === 'tool_group') return renderWorkToolGroup(event, eventIndex);
|
|
@@ -10223,7 +10297,13 @@ function applyStreamingWorkflowText(ui, eventConversationId, mode, model) {
|
|
|
10223
10297
|
}
|
|
10224
10298
|
return;
|
|
10225
10299
|
}
|
|
10226
|
-
|
|
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 });
|
|
10227
10307
|
}
|
|
10228
10308
|
|
|
10229
10309
|
function scheduleStreamingWorkflowTextFlush(ui, eventConversationId, eventWorkspaceId) {
|
|
@@ -10254,6 +10334,7 @@ function renderAgentWorkEvent(event) {
|
|
|
10254
10334
|
ui.activeWorkflowText = '';
|
|
10255
10335
|
ui.lastCompletedWorkflow = null;
|
|
10256
10336
|
ui._streamFlushPending = false;
|
|
10337
|
+
ui._lastLargeStreamRenderAt = 0;
|
|
10257
10338
|
} else if (type === 'text') {
|
|
10258
10339
|
ui.activeWorkflowText = (ui.activeWorkflowText || '') + content;
|
|
10259
10340
|
if (workRun) return;
|
|
@@ -10264,6 +10345,7 @@ function renderAgentWorkEvent(event) {
|
|
|
10264
10345
|
ui.activeWorkflowText = '';
|
|
10265
10346
|
ui.activeWorkflowMsg = null;
|
|
10266
10347
|
ui._streamFlushPending = false;
|
|
10348
|
+
ui._lastLargeStreamRenderAt = 0;
|
|
10267
10349
|
} else if (type === 'final_response' && workRun) {
|
|
10268
10350
|
ui.activeWorkflowText = '';
|
|
10269
10351
|
ui.activeWorkflowMsg = null;
|
|
@@ -10297,6 +10379,7 @@ function renderAgentWorkEvent(event) {
|
|
|
10297
10379
|
}
|
|
10298
10380
|
ui.activeWorkflowMsg = null;
|
|
10299
10381
|
ui.activeWorkflowText = '';
|
|
10382
|
+
ui._lastLargeStreamRenderAt = 0;
|
|
10300
10383
|
finishToolBatch(eventConversationId);
|
|
10301
10384
|
} else if (type === 'error') {
|
|
10302
10385
|
flushStreamingWorkflowTextNow(ui, eventConversationId, event.mode || state.mode, event.model || state.model);
|
|
@@ -10366,7 +10449,14 @@ function appendAgentWorkEvent(event) {
|
|
|
10366
10449
|
var visibleRuntimeBranch = active && isViewingRuntimeConversationBranch(target);
|
|
10367
10450
|
if (active) markConversationTracked(id, state.conversationTrackMs || 300000, workspaceId);
|
|
10368
10451
|
if (active || isConversationTracked(id, workspaceId)) cacheAgentWorkEvent(event);
|
|
10369
|
-
|
|
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 || '' });
|
|
10370
10460
|
else if (event.status && ['running', 'stopping', 'force_restarting'].indexOf(String(event.status)) >= 0) setConversationRuntimeState(target, event.status, event.runId || '');
|
|
10371
10461
|
else if (event.status && ['completed', 'interrupted', 'force_interrupted', 'error'].indexOf(String(event.status)) >= 0) {
|
|
10372
10462
|
setConversationRuntimeState(target, event.status, event.runId || '');
|
|
@@ -10977,18 +11067,33 @@ window.stopCurrentConversation = async function() {
|
|
|
10977
11067
|
if (!conversationId || !runtime) return false;
|
|
10978
11068
|
var target = runtime.target || currentConversationTarget(conversationId);
|
|
10979
11069
|
var runId = String(runtime.runId || '');
|
|
11070
|
+
var provisional = runtime.provisional === true;
|
|
10980
11071
|
if (String(runtime.status || '') === 'force_restarting') return false;
|
|
11072
|
+
if (provisional) markProvisionalStop(target, runId);
|
|
10981
11073
|
var force = String(runtime.status || '') === 'stopping';
|
|
10982
11074
|
if (typeof pauseQueueForTarget === 'function') pauseQueueForTarget(target);
|
|
10983
11075
|
setConversationRuntimeState(target, force ? 'force_restarting' : 'stopping', runId);
|
|
10984
11076
|
updateSubmitButtonState();
|
|
10985
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
|
+
}
|
|
10986
11087
|
try {
|
|
10987
11088
|
var result;
|
|
10988
11089
|
// The target runtime supervisor owns the checkpoint + cooperative-stop
|
|
10989
11090
|
// transaction. A separate awaited checkpoint can itself be starved by a
|
|
10990
11091
|
// blocked worker and would prevent the supervisor from recording the first
|
|
10991
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.
|
|
10992
11097
|
if (api.stopConversation) result = await api.stopConversation({ target: target, runId: runId, force: force });
|
|
10993
11098
|
else if (api.abortConversation) result = await api.abortConversation(currentConversationTarget(conversationId));
|
|
10994
11099
|
var resultAction = String(result && result.action || '');
|
|
@@ -10999,6 +11104,9 @@ window.stopCurrentConversation = async function() {
|
|
|
10999
11104
|
await refreshConversationRuntimeAfterStopRace(target, runId, result);
|
|
11000
11105
|
return true;
|
|
11001
11106
|
}
|
|
11107
|
+
if (provisional && state.pendingProvisionalStops) {
|
|
11108
|
+
delete state.pendingProvisionalStops[runtimeKeyFor(target.workspaceId, target.conversationId)];
|
|
11109
|
+
}
|
|
11002
11110
|
var latestAfterStop = state.conversationRuntimeStates && state.conversationRuntimeStates[runtimeKeyFor(target.workspaceId, target.conversationId)];
|
|
11003
11111
|
if (!force && latestAfterStop && String(latestAfterStop.runId || '') === runId && String(latestAfterStop.status || '') !== 'stopping') {
|
|
11004
11112
|
// A second click or a terminal worker event won the race while the first
|
|
@@ -12233,6 +12341,15 @@ window.sendMessage = async function(modeOverride, queuedText, opts) {
|
|
|
12233
12341
|
var executionMode = opts.goalDeclaration || requestedMode === 'goal' ? 'build'
|
|
12234
12342
|
: ((opts.forceBuild || idleBuildNextImmediate) && requestedMode === 'build' ? 'build' : requestedMode);
|
|
12235
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
|
+
}
|
|
12236
12353
|
var renderOnViewedBranch = !queuedRuntimeBranch || queueBranchPathForTarget(lockedTarget, 'viewed') === queueBranchPathForTarget(lockedTarget, 'runtime');
|
|
12237
12354
|
if (isActiveConversationTarget(lockedTarget) && renderOnViewedBranch) {
|
|
12238
12355
|
if (opts.resumeGuideRunId && requestMessage.clientMessageId) {
|
|
@@ -12262,10 +12379,12 @@ window.sendMessage = async function(modeOverride, queuedText, opts) {
|
|
|
12262
12379
|
renderAutoRouteRatingControls();
|
|
12263
12380
|
}
|
|
12264
12381
|
var responseMsg = null;
|
|
12382
|
+
var sendFailure = '';
|
|
12265
12383
|
try {
|
|
12266
12384
|
var sendPromise = api.sendMessage(requestMessage, lockedTarget);
|
|
12267
12385
|
if (requestedMode === 'goal') activateSubmittedGoal(opts.goalObjective || rawText);
|
|
12268
12386
|
var r = await sendPromise;
|
|
12387
|
+
if (r && r.error) sendFailure = String(r.error);
|
|
12269
12388
|
if (isActiveConversationTarget(lockedTarget)) applyReturnedGoalState(r);
|
|
12270
12389
|
if (r && r.runId) {
|
|
12271
12390
|
var anchorStore = workRunAnchorIndexStore(lockedTarget);
|
|
@@ -12346,9 +12465,10 @@ window.sendMessage = async function(modeOverride, queuedText, opts) {
|
|
|
12346
12465
|
}
|
|
12347
12466
|
}
|
|
12348
12467
|
} catch(e) {
|
|
12468
|
+
sendFailure = e && e.message ? String(e.message) : String(e || 'Agent run failed.');
|
|
12349
12469
|
if (isActiveConversationTarget(lockedTarget)) {
|
|
12350
12470
|
responseMsg = conversationWorkUiState(lockedConversationId, lockedTarget.workspaceId).activeWorkflowMsg || addMsg('assistant', '', state.mode, state.model);
|
|
12351
|
-
updateMsg(responseMsg, formatChatError(
|
|
12471
|
+
updateMsg(responseMsg, formatChatError(sendFailure, 'Agent run failed.'), 'error', state.mode, state.model);
|
|
12352
12472
|
}
|
|
12353
12473
|
}
|
|
12354
12474
|
if (api.getState) {
|
|
@@ -12399,7 +12519,15 @@ window.sendMessage = async function(modeOverride, queuedText, opts) {
|
|
|
12399
12519
|
if (resumedModeResult != null) state._syncedMode = state.mode;
|
|
12400
12520
|
}
|
|
12401
12521
|
var finalRuntime = state.conversationRuntimeStates && state.conversationRuntimeStates[lockedRuntimeKey];
|
|
12402
|
-
|
|
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) {
|
|
12403
12531
|
setConversationRuntimeState(lockedTarget, 'completed', (r && r.runId) || localRunId);
|
|
12404
12532
|
}
|
|
12405
12533
|
setWorking(!!runningConversationRecord(activeConversationId()));
|
|
@@ -13302,7 +13430,7 @@ window.terminalSend = function() {
|
|
|
13302
13430
|
var startedPane = document.querySelector('.terminal-pane[data-session="' + resp.sessionId + '"]');
|
|
13303
13431
|
var startedInput = startedPane && startedPane.querySelector('.terminal-input');
|
|
13304
13432
|
if (startedInput && startedInput.value === cmd) startedInput.value = '';
|
|
13305
|
-
return api.terminalWrite(resp.sessionId, cmd + '\r
|
|
13433
|
+
return api.terminalWrite(resp.sessionId, cmd + '\r');
|
|
13306
13434
|
}).catch(function(err) {
|
|
13307
13435
|
input.disabled = false;
|
|
13308
13436
|
var currentPane = window.getActiveTerminalPane();
|
|
@@ -13311,7 +13439,7 @@ window.terminalSend = function() {
|
|
|
13311
13439
|
});
|
|
13312
13440
|
}
|
|
13313
13441
|
input.value = '';
|
|
13314
|
-
api.terminalWrite(sessionId, cmd + '\r
|
|
13442
|
+
api.terminalWrite(sessionId, cmd + '\r').catch(function(err) {
|
|
13315
13443
|
if (output) output.innerHTML += '\r\n<span style="color:#ff6666;">[' + esc(t('common.error')) + '] ' + esc(err.message) + '</span>';
|
|
13316
13444
|
});
|
|
13317
13445
|
};
|
|
@@ -15154,36 +15282,12 @@ renderArchiveSettings = function() {
|
|
|
15154
15282
|
};
|
|
15155
15283
|
|
|
15156
15284
|
window.archiveCurrent = function() {
|
|
15157
|
-
if (!api.archive) return;
|
|
15158
15285
|
var currentId = activeConversationId();
|
|
15159
|
-
if (currentId
|
|
15160
|
-
|
|
15161
|
-
|
|
15162
|
-
|
|
15163
|
-
|
|
15164
|
-
if (!receipt || !receipt.ok) throw new Error((receipt && receipt.error) || 'Archive failed');
|
|
15165
|
-
showUiNotice('[Archive] ' + t('archive.saved') + ': ' + receipt.fileName, 'success', 'archive-saved-' + receipt.conversationId);
|
|
15166
|
-
return Promise.all([api.listArchives ? api.listArchives('workspace') : [], api.getState ? api.getState() : null]);
|
|
15167
|
-
}).then(function(result) {
|
|
15168
|
-
var items = result[0] || [];
|
|
15169
|
-
var refreshed = result[1];
|
|
15170
|
-
state.workspaceArchives = (items || []).map(function(a) {
|
|
15171
|
-
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 || '' };
|
|
15172
|
-
});
|
|
15173
|
-
state.allArchives = [];
|
|
15174
|
-
if (refreshed && refreshed.conversations) {
|
|
15175
|
-
state.activeBackendConversationId = refreshed.conversationId || state.activeBackendConversationId;
|
|
15176
|
-
applyBackendConversations(refreshed.conversations, state.activeBackendConversationId, refreshed.workspaceId || runtimeWorkspaceId(''));
|
|
15177
|
-
if (refreshed.chatMessages) renderChatMessages(refreshed.chatMessages);
|
|
15178
|
-
renderConversations();
|
|
15179
|
-
}
|
|
15180
|
-
window.renderRightArchives();
|
|
15181
|
-
var stab = document.getElementById('stab-archive');
|
|
15182
|
-
if (stab) stab.innerHTML = renderArchiveSettings();
|
|
15183
|
-
if (state.settingsActiveTab === 'archive') window.settingsTab('archive');
|
|
15184
|
-
}).catch(function(err) {
|
|
15185
|
-
addMsg('assistant', '[Archive] ' + t('workspace.saveFailed') + ': ' + (err.message || String(err)), 'error', '');
|
|
15186
|
-
});
|
|
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);
|
|
15187
15291
|
};
|
|
15188
15292
|
|
|
15189
15293
|
window.loadArchive = function(idx, scope) {
|
|
@@ -16223,11 +16327,17 @@ window.loadFileTree = async function(options) {
|
|
|
16223
16327
|
};
|
|
16224
16328
|
|
|
16225
16329
|
window.submitSelectedFlow = function() {
|
|
16330
|
+
if (!state._flowsLoaded && window.ensureFlowsLoaded) {
|
|
16331
|
+
return window.ensureFlowsLoaded().then(function() { return window.submitSelectedFlow(); });
|
|
16332
|
+
}
|
|
16226
16333
|
var selected = String(state.defaultFlow || (document.getElementById('flow-select') && document.getElementById('flow-select').value) || '');
|
|
16227
16334
|
var workIdx = state.flowWorks.findIndex(function(work) { return String(work && work.name || '') === selected; });
|
|
16228
16335
|
if (workIdx < 0) {
|
|
16229
|
-
|
|
16230
|
-
|
|
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 };
|
|
16231
16341
|
}
|
|
16232
16342
|
return window.runFlowWork(workIdx);
|
|
16233
16343
|
};
|
|
@@ -17332,7 +17442,14 @@ window.ensureBrowserPanel = function(options) {
|
|
|
17332
17442
|
var targetKey = browserTargetKey(target);
|
|
17333
17443
|
cancelBrowserGuestIdleDestroy();
|
|
17334
17444
|
if (!browserGuestCreatePromises[targetKey]) {
|
|
17335
|
-
|
|
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() {
|
|
17336
17453
|
return initializeBrowserGuest(createBrowserGuestElement(target), target);
|
|
17337
17454
|
}).catch(function(error) {
|
|
17338
17455
|
delete browserGuestCreatePromises[targetKey];
|
|
@@ -17572,7 +17689,7 @@ function renderConversations() {
|
|
|
17572
17689
|
? '<span class="conv-runtime-badge ' + escAttr(String(runtimeState.status)) + '">' + esc(String(runtimeState.status)) + '</span>' : '';
|
|
17573
17690
|
div.innerHTML = '<span class="conv-summary" title="' + escAttr(String(conv.id || '')) + '">' + esc(displaySummary) + (conv.messageCount ? ' (' + esc(String(conv.messageCount)) + ')' : '') + '</span>' + runtimeBadge +
|
|
17574
17691
|
'<button class="conv-rename-btn" onclick="event.stopPropagation();window.editConversationName(' + i + ')" title="' + escAttr(t('conversation.rename')) + '">' + iconOnly('pencil', t('conversation.rename')) + '</button>' +
|
|
17575
|
-
'<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>' +
|
|
17576
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>';
|
|
17577
17694
|
div.onclick = function(idx) { return function() { window.switchConversation(idx); }; }(i);
|
|
17578
17695
|
div.addEventListener('dragstart', function(event) {
|
|
@@ -17774,8 +17891,13 @@ function refreshConversationArchivesAfterBatch() {
|
|
|
17774
17891
|
state.workspaceArchives = (items || []).map(function(a) {
|
|
17775
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 || '' };
|
|
17776
17893
|
});
|
|
17777
|
-
|
|
17894
|
+
var archiveSettingsOpen = typeof document !== 'undefined'
|
|
17895
|
+
&& state.settingsActiveTab === 'archive'
|
|
17896
|
+
&& !!document.getElementById('stab-archive');
|
|
17897
|
+
if (!archiveSettingsOpen) state.allArchives = [];
|
|
17778
17898
|
window.renderRightArchives();
|
|
17899
|
+
var stab = typeof document !== 'undefined' ? document.getElementById('stab-archive') : null;
|
|
17900
|
+
if (stab && archiveSettingsOpen) stab.innerHTML = renderArchiveSettings();
|
|
17779
17901
|
return items || [];
|
|
17780
17902
|
});
|
|
17781
17903
|
}
|
|
@@ -17810,10 +17932,6 @@ window.archiveConv = function(conversationId) {
|
|
|
17810
17932
|
var targetRuntime = currentConversationTarget(targetId);
|
|
17811
17933
|
var workspaceKey = currentWorkspaceKey();
|
|
17812
17934
|
var pendingKey = workspaceKey + '::' + targetId;
|
|
17813
|
-
if (runningConversationRecord(targetId)) {
|
|
17814
|
-
showUiNotice(currentLang() === 'zh' ? '运行中的对话不能归档。' : 'A running conversation cannot be archived.', 'error', 'archive-running-' + currentRuntimeKey(targetId));
|
|
17815
|
-
return;
|
|
17816
|
-
}
|
|
17817
17935
|
if (state.conversationArchivePending[pendingKey]) return;
|
|
17818
17936
|
var priorActiveId = String(((convs.find(function(item) { return item && item.active; }) || {}).id) || '');
|
|
17819
17937
|
var rollbackOrder = convs.map(function(item) { return String(item && item.id || 'default'); });
|
|
@@ -17853,39 +17971,37 @@ window.archiveConv = function(conversationId) {
|
|
|
17853
17971
|
archivePromise.then(function(receipt) {
|
|
17854
17972
|
if (!receipt || receipt.ok !== true) throw new Error((receipt && receipt.error) || 'Archive failed');
|
|
17855
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();
|
|
17856
17997
|
showUiNotice('[Archive] ' + t('archive.saved') + ': ' + receipt.fileName, 'success', 'archive-saved-' + targetId);
|
|
17857
17998
|
scheduleConversationArchiveRefresh(workspaceKey);
|
|
17858
17999
|
}).catch(function(err) {
|
|
17859
|
-
var pending = state.conversationArchivePending[pendingKey];
|
|
17860
18000
|
delete state.conversationArchivePending[pendingKey];
|
|
17861
|
-
|
|
17862
|
-
|
|
17863
|
-
|
|
17864
|
-
|
|
17865
|
-
var targetOrderIndex = orderIds.indexOf(targetId);
|
|
17866
|
-
for (var beforeIndex = targetOrderIndex - 1; beforeIndex >= 0 && restoreAt < 0; beforeIndex--) {
|
|
17867
|
-
var precedingIndex = workspaceConversations.findIndex(function(item) { return String(item && item.id || '') === orderIds[beforeIndex]; });
|
|
17868
|
-
if (precedingIndex >= 0) restoreAt = precedingIndex + 1;
|
|
17869
|
-
}
|
|
17870
|
-
for (var afterIndex = targetOrderIndex + 1; afterIndex < orderIds.length && restoreAt < 0; afterIndex++) {
|
|
17871
|
-
var followingIndex = workspaceConversations.findIndex(function(item) { return String(item && item.id || '') === orderIds[afterIndex]; });
|
|
17872
|
-
if (followingIndex >= 0) restoreAt = followingIndex;
|
|
17873
|
-
}
|
|
17874
|
-
if (restoreAt < 0) restoreAt = Math.min(Number(pending.index || 0), workspaceConversations.length);
|
|
17875
|
-
workspaceConversations.splice(restoreAt, 0, pending.target);
|
|
17876
|
-
if (pending.wasActive) {
|
|
17877
|
-
for (var j = 0; j < workspaceConversations.length; j++) workspaceConversations[j].active = String(workspaceConversations[j].id || '') === targetId;
|
|
17878
|
-
}
|
|
17879
|
-
}
|
|
17880
|
-
if (workspaceKey === currentWorkspaceKey()) {
|
|
17881
|
-
state.conversations = workspaceConversations;
|
|
17882
|
-
state.activeConversation = Math.max(0, workspaceConversations.findIndex(function(item) { return item && item.active; }));
|
|
17883
|
-
if (pending && pending.wasActive) {
|
|
17884
|
-
state.activeBackendConversationId = targetId;
|
|
17885
|
-
if (els['chat-area']) els['chat-area'].innerHTML = pending.chatHtml || '';
|
|
17886
|
-
}
|
|
17887
|
-
renderConversations();
|
|
17888
|
-
}
|
|
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.
|
|
17889
18005
|
showUiNotice('[Archive] ' + t('workspace.saveFailed') + ': ' + (err.message || String(err)), 'error', 'archive-failed-' + targetId);
|
|
17890
18006
|
});
|
|
17891
18007
|
}
|
|
@@ -19660,18 +19776,26 @@ window.saveWsSetting = function(key, value) {
|
|
|
19660
19776
|
window.renderFlowSelector = function() {
|
|
19661
19777
|
var flowSel = document.getElementById('flow-select');
|
|
19662
19778
|
if (!flowSel) return;
|
|
19779
|
+
var flows = Array.isArray(state.flowWorks) ? state.flowWorks : [];
|
|
19663
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;
|
|
19664
19789
|
var html = '';
|
|
19665
|
-
for (var fi = 0; fi <
|
|
19666
|
-
var selected =
|
|
19667
|
-
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>';
|
|
19668
19793
|
}
|
|
19669
19794
|
flowSel.innerHTML = html;
|
|
19670
|
-
if (
|
|
19671
|
-
|
|
19672
|
-
state.defaultFlow = flowSel.value;
|
|
19673
|
-
}
|
|
19795
|
+
if (defaultFlow) flowSel.value = defaultFlow;
|
|
19796
|
+
state.defaultFlow = flowSel.value;
|
|
19674
19797
|
flowSel.onchange = function() { state.defaultFlow = this.value; api.saveConfig({defaultFlow: state.defaultFlow}); };
|
|
19798
|
+
if (window.syncNewmarkSelect) window.syncNewmarkSelect(flowSel);
|
|
19675
19799
|
};
|
|
19676
19800
|
|
|
19677
19801
|
window.loadFlows = function(options) {
|
|
@@ -20071,6 +20195,48 @@ function schedulePostStartupUiRendering() {
|
|
|
20071
20195
|
updateWorkspaceGate();
|
|
20072
20196
|
|
|
20073
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
|
+
|
|
20074
20240
|
if (els.prompt) {
|
|
20075
20241
|
els.prompt.addEventListener('keydown', function(e) {
|
|
20076
20242
|
if (e.key === 'Escape' && currentFlowRunning() && flowTakeoverMatchesCurrent() && !promptHasText()) {
|