newmark-agent 0.6.3 → 0.6.7
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 +13 -3
- package/dist/cli-commands.d.ts +1 -1
- package/dist/cli-commands.js +53 -2
- package/dist/cli-discovery.js +3 -0
- package/dist/conversation-utility-host.bundle.cjs +3046 -1038
- package/dist/conversation-utility-host.js +94 -7
- package/dist/core/agent.d.ts +149 -12
- package/dist/core/agent.js +843 -176
- package/dist/core/agentKernelRunner.d.ts +7 -1
- package/dist/core/agentKernelRunner.js +171 -22
- package/dist/core/autoRouter.d.ts +49 -44
- package/dist/core/autoRouter.js +117 -302
- package/dist/core/branchIdentity.d.ts +29 -0
- package/dist/core/branchIdentity.js +54 -0
- package/dist/core/config.js +27 -11
- package/dist/core/continuation/contracts.d.ts +1 -1
- package/dist/core/continuation/store.d.ts +119 -1
- package/dist/core/continuation/store.js +337 -19
- package/dist/core/conversationKernel.d.ts +62 -8
- package/dist/core/conversationKernel.js +361 -47
- package/dist/core/conversationStateDocument.d.ts +18 -0
- package/dist/core/conversationStateDocument.js +88 -0
- package/dist/core/conversationVisualBudget.d.ts +74 -0
- package/dist/core/conversationVisualBudget.js +153 -0
- package/dist/core/electronUtilityAgentClient.d.ts +3 -2
- package/dist/core/electronUtilityAgentClient.js +32 -8
- package/dist/core/electronUtilityRuntimePool.d.ts +28 -3
- package/dist/core/electronUtilityRuntimePool.js +93 -24
- package/dist/core/hostRuntimeHooks.d.ts +23 -0
- package/dist/core/hostRuntimeHooks.js +17 -0
- package/dist/core/jevDecision.d.ts +144 -0
- package/dist/core/jevDecision.js +226 -0
- package/dist/core/memoryProbe.d.ts +24 -0
- package/dist/core/memoryProbe.js +104 -0
- package/dist/core/mobilePairing.d.ts +7 -1
- package/dist/core/mobilePairing.js +9 -1
- package/dist/core/performanceDiagnostics.d.ts +1 -1
- package/dist/core/routeDecisionValidator.d.ts +86 -0
- package/dist/core/routeDecisionValidator.js +249 -0
- package/dist/core/routeEligibility.d.ts +52 -0
- package/dist/core/routeEligibility.js +108 -0
- package/dist/core/runtimeMemoryBudget.d.ts +6 -0
- package/dist/core/runtimeMemoryBudget.js +12 -0
- package/dist/core/utilityAgentProtocol.d.ts +8 -10
- package/dist/core/utilityHostToolRouter.js +2 -0
- package/dist/core/visualDownscale.d.ts +6 -0
- package/dist/core/visualDownscale.js +137 -0
- package/dist/core/wslAgentClient.d.ts +1 -2
- package/dist/core/wslAgentClient.js +0 -8
- package/dist/core/wslAgentProtocol.d.ts +1 -10
- package/dist/core/wslAgentRuntimePool.d.ts +1 -3
- package/dist/core/wslAgentRuntimePool.js +17 -22
- package/dist/llm/provider.d.ts +3 -1
- package/dist/llm/provider.js +53 -9
- package/dist/main.js +108 -31
- package/dist/preload.js +3 -1
- package/dist/server.js +30 -18
- package/dist/tools/computerUse.d.ts +4 -0
- package/dist/tools/computerUse.js +202 -4
- package/dist/tools/computerUsePowerShellHost.d.ts +6 -1
- package/dist/tools/computerUsePowerShellHost.js +105 -31
- package/dist/tools/index.js +4 -1
- package/dist/tui/src/adapters/core-runtime-adapter.js +13 -1
- package/dist/tui/src/i18n.js +12 -1
- package/dist/tui/src/render.js +9 -2
- package/dist/tui/src/settings-schema.js +19 -0
- package/dist/tui/src/state.js +69 -1
- package/dist/ui/index.html +994 -277
- package/dist/ui/lucide-sprite.svg +0 -8
- package/dist/wsl-agent-host.bundle.cjs +2851 -1013
- package/dist/wsl-agent-host.js +0 -3
- package/package.json +38 -9
package/dist/ui/index.html
CHANGED
|
@@ -185,7 +185,7 @@ try {
|
|
|
185
185
|
--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
|
|
186
186
|
--duration-fast: 150ms;
|
|
187
187
|
--duration-normal: 250ms;
|
|
188
|
-
--duration-slow:
|
|
188
|
+
--duration-slow: 300ms;
|
|
189
189
|
|
|
190
190
|
/* Stable Newmark semantic layers. Components depend on meaning, not on an
|
|
191
191
|
upstream developer-preview framework's private tokens or brand palette. */
|
|
@@ -208,6 +208,17 @@ try {
|
|
|
208
208
|
--right-width: 380px;
|
|
209
209
|
--bottom-height: 200px;
|
|
210
210
|
--topbar-height: 38px;
|
|
211
|
+
|
|
212
|
+
/* Component aliases. These names are referenced by existing component rules
|
|
213
|
+
but were never defined, which silently dropped fills, borders and danger
|
|
214
|
+
colors. Map them onto the theme tokens so both themes resolve them. */
|
|
215
|
+
--control-bg: var(--glass-bg-1);
|
|
216
|
+
--panel: var(--glass-bg-1);
|
|
217
|
+
--panel-2: var(--modal-surface);
|
|
218
|
+
--bg-panel: rgb(var(--glass-rgb-2));
|
|
219
|
+
--text-primary: var(--text);
|
|
220
|
+
--accent-border: color-mix(in srgb, var(--accent) 30%, transparent);
|
|
221
|
+
--danger: var(--nm-state-danger);
|
|
211
222
|
}
|
|
212
223
|
|
|
213
224
|
/* Light theme */
|
|
@@ -371,7 +382,7 @@ html, body {
|
|
|
371
382
|
50% { transform: scale(1.04); filter: saturate(1.25); }
|
|
372
383
|
}
|
|
373
384
|
|
|
374
|
-
::selection { background:
|
|
385
|
+
::selection { background: var(--editor-selection); color: var(--text-bright); }
|
|
375
386
|
|
|
376
387
|
/* Open-source icon system: lucide-static sprite is copied into dist/ui at build time. */
|
|
377
388
|
.nm-icon {
|
|
@@ -413,7 +424,10 @@ html, body {
|
|
|
413
424
|
white-space: nowrap;
|
|
414
425
|
border: 0;
|
|
415
426
|
}
|
|
416
|
-
|
|
427
|
+
/* Pointer focus stays quiet. The button reset goes through :where() so it keeps
|
|
428
|
+
(0,1,0) specificity and the keyboard :focus-visible ring below can win. */
|
|
429
|
+
:where(button):focus { outline: none; }
|
|
430
|
+
select:focus, input:focus, textarea:focus, [contenteditable]:focus { outline: none; }
|
|
417
431
|
/* Liquid-glass controls use one continuous capsule edge. Equal-width icon
|
|
418
432
|
buttons naturally become circles; wider controls become end-rounded pills. */
|
|
419
433
|
button { border-radius: var(--radius-full) !important; }
|
|
@@ -435,6 +449,7 @@ button { border-radius: var(--radius-full) !important; }
|
|
|
435
449
|
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.10); border-radius: var(--radius-full); }
|
|
436
450
|
::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.18); }
|
|
437
451
|
[data-theme="light"] ::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.12); }
|
|
452
|
+
[data-theme="light"] ::-webkit-scrollbar-thumb:hover { background: rgba(0,0,0,0.2); }
|
|
438
453
|
|
|
439
454
|
/* ============================================================
|
|
440
455
|
LAYER 1 - App Shell
|
|
@@ -444,7 +459,7 @@ button { border-radius: var(--radius-full) !important; }
|
|
|
444
459
|
flex-direction: column;
|
|
445
460
|
height: 100vh;
|
|
446
461
|
background:
|
|
447
|
-
radial-gradient(ellipse 80% 60% at 50% 0%,
|
|
462
|
+
radial-gradient(ellipse 80% 60% at 50% 0%, color-mix(in srgb, var(--accent) 6%, transparent) 0%, transparent 60%),
|
|
448
463
|
radial-gradient(ellipse 60% 50% at 80% 80%, rgba(56,212,160,0.04) 0%, transparent 50%),
|
|
449
464
|
var(--app-bg);
|
|
450
465
|
}
|
|
@@ -516,11 +531,11 @@ button { border-radius: var(--radius-full) !important; }
|
|
|
516
531
|
display: flex;
|
|
517
532
|
align-items: center;
|
|
518
533
|
justify-content: center;
|
|
519
|
-
transition: background var(--duration-fast) var(--ease-out-expo), color var(--duration-fast) var(--ease-out-expo);
|
|
534
|
+
transition: background var(--duration-fast) var(--ease-out-expo), color var(--duration-fast) var(--ease-out-expo), transform 90ms var(--ease-out-expo);
|
|
520
535
|
}
|
|
521
536
|
|
|
522
537
|
.top-btn:hover { background: var(--control-hover-bg); color: var(--text-bright); }
|
|
523
|
-
.top-btn:active { transform: scale(0.92)
|
|
538
|
+
.top-btn:active { transform: scale(0.92); }
|
|
524
539
|
.top-btn.close-btn:hover { background: rgba(255,68,68,0.2); color: var(--nm-state-danger); }
|
|
525
540
|
|
|
526
541
|
/* ============================================================
|
|
@@ -598,7 +613,7 @@ button { border-radius: var(--radius-full) !important; }
|
|
|
598
613
|
font-size: 0;
|
|
599
614
|
cursor: pointer;
|
|
600
615
|
color: var(--text-dim);
|
|
601
|
-
transition: background var(--duration-fast) var(--ease-out-expo), color var(--duration-fast) var(--ease-out-expo), transform var(--duration-fast) var(--ease-out-
|
|
616
|
+
transition: background var(--duration-fast) var(--ease-out-expo), color var(--duration-fast) var(--ease-out-expo), transform var(--duration-fast) var(--ease-out-expo);
|
|
602
617
|
flex-shrink: 0;
|
|
603
618
|
}
|
|
604
619
|
|
|
@@ -729,7 +744,7 @@ button.left-nav-icon { width: calc(100% - 12px); }
|
|
|
729
744
|
}
|
|
730
745
|
|
|
731
746
|
.left-nav-icon:hover { background: var(--control-hover-bg); color: var(--text-bright); }
|
|
732
|
-
.left-nav-icon:active { transform: scale(0.96)
|
|
747
|
+
.left-nav-icon:active { transform: scale(0.96); }
|
|
733
748
|
.left-nav-icon .icon { width: 24px; height: 20px; text-align: center; flex-shrink: 0; font-size: 0; }
|
|
734
749
|
.left-nav-icon span:not(.icon) {
|
|
735
750
|
display: inline-block;
|
|
@@ -939,7 +954,9 @@ button.left-ws-item {
|
|
|
939
954
|
color: var(--text-dim);
|
|
940
955
|
font-size: 11px;
|
|
941
956
|
cursor: pointer;
|
|
942
|
-
transition:
|
|
957
|
+
transition-property: color, background-color, border-color, filter, opacity, visibility, max-height, margin-top, margin-bottom, padding-top, padding-bottom;
|
|
958
|
+
transition-duration: var(--duration-fast);
|
|
959
|
+
transition-timing-function: var(--ease-out-expo);
|
|
943
960
|
flex-shrink: 0;
|
|
944
961
|
max-height: 30px;
|
|
945
962
|
opacity: 1;
|
|
@@ -949,7 +966,7 @@ button.left-ws-item {
|
|
|
949
966
|
#left-ws-add:hover {
|
|
950
967
|
border-color: var(--accent);
|
|
951
968
|
color: var(--accent);
|
|
952
|
-
background:
|
|
969
|
+
background: color-mix(in srgb, var(--accent) 6%, transparent);
|
|
953
970
|
}
|
|
954
971
|
|
|
955
972
|
#left.collapsed #left-ws-add {
|
|
@@ -990,7 +1007,7 @@ button.left-ws-item {
|
|
|
990
1007
|
}
|
|
991
1008
|
|
|
992
1009
|
.left-collapse-btn:hover { background: var(--control-hover-bg); color: var(--text-bright); }
|
|
993
|
-
.left-collapse-btn:active { transform: scale(0.92)
|
|
1010
|
+
.left-collapse-btn:active { transform: scale(0.92); }
|
|
994
1011
|
|
|
995
1012
|
/* ============================================================
|
|
996
1013
|
LEFT SECONDARY SIDEBAR
|
|
@@ -1041,10 +1058,11 @@ button.left-ws-item {
|
|
|
1041
1058
|
}
|
|
1042
1059
|
|
|
1043
1060
|
.sec-btn:hover { background: var(--control-hover-bg); border-color: var(--border-hover); }
|
|
1044
|
-
.sec-btn:active { transform: scale(0.96)
|
|
1061
|
+
.sec-btn:active { transform: scale(0.96); }
|
|
1045
1062
|
.sec-btn.primary { background: var(--accent); color: #fff; border-color: var(--accent); }
|
|
1046
1063
|
.sec-btn.primary:hover { filter: brightness(1.1); }
|
|
1047
1064
|
.sec-btn:focus-visible {
|
|
1065
|
+
outline: none;
|
|
1048
1066
|
border-color: var(--accent);
|
|
1049
1067
|
box-shadow: 0 0 0 3px var(--accent-glow);
|
|
1050
1068
|
}
|
|
@@ -1075,7 +1093,7 @@ button.left-ws-item {
|
|
|
1075
1093
|
}
|
|
1076
1094
|
|
|
1077
1095
|
.conv-item:hover { background: var(--glass-bg-2); border-color: var(--border-hover); }
|
|
1078
|
-
.conv-item.active { border-color: var(--accent); background:
|
|
1096
|
+
.conv-item.active { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 10%, transparent); }
|
|
1079
1097
|
.conv-item.marquee-border::before {
|
|
1080
1098
|
border-radius: var(--radius-full);
|
|
1081
1099
|
}
|
|
@@ -1378,13 +1396,13 @@ button.left-ws-item {
|
|
|
1378
1396
|
display: flex;
|
|
1379
1397
|
align-items: center;
|
|
1380
1398
|
justify-content: center;
|
|
1381
|
-
transition: background var(--duration-fast) var(--ease-out-expo), color var(--duration-fast) var(--ease-out-expo), border-color var(--duration-fast) var(--ease-out-expo), transform var(--duration-fast) var(--ease-out-
|
|
1399
|
+
transition: background var(--duration-fast) var(--ease-out-expo), color var(--duration-fast) var(--ease-out-expo), border-color var(--duration-fast) var(--ease-out-expo), transform var(--duration-fast) var(--ease-out-expo);
|
|
1382
1400
|
position: relative;
|
|
1383
1401
|
}
|
|
1384
1402
|
|
|
1385
1403
|
.tab-btn:hover { background: var(--control-hover-bg); color: var(--text-bright); }
|
|
1386
|
-
.tab-btn.active { background:
|
|
1387
|
-
.tab-btn:active { transform: scale(0.92)
|
|
1404
|
+
.tab-btn.active { background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--accent); border-color: color-mix(in srgb, var(--accent) 20%, transparent); }
|
|
1405
|
+
.tab-btn:active { transform: scale(0.92); }
|
|
1388
1406
|
.tab-btn .label { display: none; }
|
|
1389
1407
|
|
|
1390
1408
|
.tab-divider {
|
|
@@ -1438,8 +1456,8 @@ button.left-ws-item {
|
|
|
1438
1456
|
white-space: nowrap;
|
|
1439
1457
|
}
|
|
1440
1458
|
|
|
1441
|
-
.ft-item:hover { background:
|
|
1442
|
-
.ft-item.selected { background:
|
|
1459
|
+
.ft-item:hover { background: var(--control-hover-bg); color: var(--text); }
|
|
1460
|
+
.ft-item.selected { background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--text-bright); }
|
|
1443
1461
|
.ft-item .ft-icon { width: 16px; height: 16px; display: inline-flex; align-items: center; justify-content: center; text-align: center; flex-shrink: 0; font-size: 0; }
|
|
1444
1462
|
.ft-item .ft-name { overflow: hidden; text-overflow: ellipsis; }
|
|
1445
1463
|
.ft-item .ft-toggle { width: 14px; height: 16px; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; font-size: 0; color: var(--text-dim); transform: rotate(90deg); transition: transform var(--duration-fast) var(--ease-out-expo); }
|
|
@@ -1479,7 +1497,7 @@ button.left-ws-item {
|
|
|
1479
1497
|
}
|
|
1480
1498
|
|
|
1481
1499
|
.et-btn:hover { background: var(--control-hover-bg); color: var(--text-bright); }
|
|
1482
|
-
.et-btn.active { background:
|
|
1500
|
+
.et-btn.active { background:color-mix(in srgb, var(--accent) 16%, transparent); color:var(--accent2); border-color:color-mix(in srgb, var(--accent) 55%, transparent); }
|
|
1483
1501
|
.et-btn .nm-icon { width:15px; height:15px; }
|
|
1484
1502
|
.et-btn.primary { background: var(--accent); color: #fff; border-color: var(--accent); }
|
|
1485
1503
|
.et-btn.primary:hover { filter: brightness(1.1); }
|
|
@@ -1712,7 +1730,6 @@ button.left-ws-item {
|
|
|
1712
1730
|
line-height: 0;
|
|
1713
1731
|
}
|
|
1714
1732
|
.math-symbol { display: inline-block; }
|
|
1715
|
-
}
|
|
1716
1733
|
|
|
1717
1734
|
/* Subagent panel */
|
|
1718
1735
|
.subagent-item {
|
|
@@ -2117,7 +2134,7 @@ button.left-ws-item {
|
|
|
2117
2134
|
}
|
|
2118
2135
|
|
|
2119
2136
|
.shell-btn:hover { background: var(--control-hover-bg); color: var(--text-bright); }
|
|
2120
|
-
.shell-btn:active { transform: scale(0.95)
|
|
2137
|
+
.shell-btn:active { transform: scale(0.95); }
|
|
2121
2138
|
|
|
2122
2139
|
.shell-toggle-btn {
|
|
2123
2140
|
margin-left: auto;
|
|
@@ -2220,7 +2237,6 @@ button.left-ws-item {
|
|
|
2220
2237
|
.conversation-load-earlier:hover,
|
|
2221
2238
|
.conversation-load-earlier:focus-visible { background: var(--glass-bg-2); border-color: var(--border-hover); outline: none; }
|
|
2222
2239
|
.conversation-load-earlier:disabled { opacity: 0.6; cursor: default; }
|
|
2223
|
-
}
|
|
2224
2240
|
|
|
2225
2241
|
#chat-area:has(> .chat-msg) {
|
|
2226
2242
|
background-image:
|
|
@@ -2331,11 +2347,18 @@ button.left-ws-item {
|
|
|
2331
2347
|
line-height: 1.6;
|
|
2332
2348
|
word-wrap: break-word;
|
|
2333
2349
|
overflow-wrap: break-word;
|
|
2334
|
-
transform: translateZ(0);
|
|
2335
2350
|
animation: msg-in 250ms var(--ease-out-expo);
|
|
2336
2351
|
position: relative;
|
|
2337
2352
|
}
|
|
2338
2353
|
|
|
2354
|
+
/* Restored transcript rows are already present in the conversation. Avoid
|
|
2355
|
+
animating a large history batch or pinning every row to its own compositor
|
|
2356
|
+
layer; only newly arriving live messages use the entrance motion. */
|
|
2357
|
+
.chat-msg.history-static {
|
|
2358
|
+
animation: none;
|
|
2359
|
+
transform: none;
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2339
2362
|
.chat-msg::after {
|
|
2340
2363
|
content: "";
|
|
2341
2364
|
position: absolute;
|
|
@@ -2350,8 +2373,8 @@ button.left-ws-item {
|
|
|
2350
2373
|
}
|
|
2351
2374
|
|
|
2352
2375
|
@keyframes msg-in {
|
|
2353
|
-
from { opacity: 0; transform: translateY(8px)
|
|
2354
|
-
to { opacity: 1; transform: translateY(0)
|
|
2376
|
+
from { opacity: 0; transform: translateY(8px); }
|
|
2377
|
+
to { opacity: 1; transform: translateY(0); }
|
|
2355
2378
|
}
|
|
2356
2379
|
|
|
2357
2380
|
.chat-msg.user {
|
|
@@ -2467,7 +2490,6 @@ button.left-ws-item {
|
|
|
2467
2490
|
}
|
|
2468
2491
|
|
|
2469
2492
|
.msg-action-btn:hover { color: var(--text); background: var(--glass-bg-2); }
|
|
2470
|
-
.msg-action-btn.route-rating-selected { color: var(--accent); background: var(--glass-bg-2); }
|
|
2471
2493
|
.msg-action-btn:disabled { cursor: default; opacity: 0.62; }
|
|
2472
2494
|
|
|
2473
2495
|
.message-inline-editor { display: grid; gap: 8px; margin-top: 4px; }
|
|
@@ -2487,7 +2509,7 @@ button.left-ws-item {
|
|
|
2487
2509
|
.conversation-branch-pager { display:flex; justify-content:flex-end; align-items:center; gap:0; margin-top:7px; color:var(--text-dim); font-size:11px; }
|
|
2488
2510
|
.branch-page-arrow { cursor:pointer; color:var(--text-dim); padding:1px 3px; transition:color var(--duration-fast) var(--ease-out-expo), background var(--duration-fast) var(--ease-out-expo); border-radius:var(--radius-sm); }
|
|
2489
2511
|
.branch-page-arrow:hover { color:var(--text-bright); background:var(--control-hover-bg); }
|
|
2490
|
-
.branch-page-arrow:active { transform:scale(0.92)
|
|
2512
|
+
.branch-page-arrow:active { transform:scale(0.92); }
|
|
2491
2513
|
|
|
2492
2514
|
.chat-msg .msg-body .md-rendered {
|
|
2493
2515
|
padding: 0;
|
|
@@ -2919,8 +2941,8 @@ button.todo-item { background: transparent; }
|
|
|
2919
2941
|
margin: 8px 0;
|
|
2920
2942
|
padding: 10px 14px;
|
|
2921
2943
|
border-radius: var(--radius-md);
|
|
2922
|
-
background:
|
|
2923
|
-
border: 1px solid
|
|
2944
|
+
background: color-mix(in srgb, var(--accent) 8%, transparent);
|
|
2945
|
+
border: 1px solid color-mix(in srgb, var(--accent) 20%, transparent);
|
|
2924
2946
|
}
|
|
2925
2947
|
|
|
2926
2948
|
.option-question {
|
|
@@ -2945,9 +2967,9 @@ button.todo-item { background: transparent; }
|
|
|
2945
2967
|
transition: background var(--duration-fast) var(--ease-out-expo), border-color var(--duration-fast) var(--ease-out-expo);
|
|
2946
2968
|
}
|
|
2947
2969
|
|
|
2948
|
-
.option-btn:hover { background:
|
|
2949
|
-
.option-btn:active { transform: scale(0.96)
|
|
2950
|
-
.option-btn.selected { background:
|
|
2970
|
+
.option-btn:hover { background: color-mix(in srgb, var(--accent) 15%, transparent); border-color: var(--accent); color: var(--text-bright); }
|
|
2971
|
+
.option-btn:active { transform: scale(0.96); }
|
|
2972
|
+
.option-btn.selected { background:color-mix(in srgb, var(--accent) 24%, transparent); border-color:var(--accent); color:var(--text-bright); }
|
|
2951
2973
|
.option-btn:disabled { cursor:default; opacity:.55; }
|
|
2952
2974
|
.option-btn.selected:disabled { opacity:1; }
|
|
2953
2975
|
.option-desc {
|
|
@@ -2986,14 +3008,14 @@ button.todo-item { background: transparent; }
|
|
|
2986
3008
|
padding: 0 12px;
|
|
2987
3009
|
border: 1px solid var(--accent);
|
|
2988
3010
|
border-radius: var(--radius-md);
|
|
2989
|
-
background:
|
|
3011
|
+
background: color-mix(in srgb, var(--accent) 14%, transparent);
|
|
2990
3012
|
color: var(--text-bright);
|
|
2991
3013
|
font: 12px var(--font-ui);
|
|
2992
3014
|
cursor: pointer;
|
|
2993
3015
|
transition: background var(--duration-fast) var(--ease-out-expo);
|
|
2994
3016
|
}
|
|
2995
3017
|
|
|
2996
|
-
.option-manual-submit:hover { background:
|
|
3018
|
+
.option-manual-submit:hover { background: color-mix(in srgb, var(--accent) 24%, transparent); }
|
|
2997
3019
|
.option-manual-submit:disabled { opacity: .55; cursor: default; }
|
|
2998
3020
|
|
|
2999
3021
|
/* ============================================================
|
|
@@ -3043,7 +3065,7 @@ button.todo-item { background: transparent; }
|
|
|
3043
3065
|
border-radius: 8px;
|
|
3044
3066
|
box-shadow: 0 6px 18px rgba(0,0,0,0.12);
|
|
3045
3067
|
overflow: hidden;
|
|
3046
|
-
transform: translateY(0)
|
|
3068
|
+
transform: translateY(0);
|
|
3047
3069
|
animation: fade-in-up 180ms var(--ease-out-expo);
|
|
3048
3070
|
}
|
|
3049
3071
|
[data-theme="light"] .stack-card {
|
|
@@ -3115,6 +3137,7 @@ button.todo-item { background: transparent; }
|
|
|
3115
3137
|
.stack-icon-btn.warn { color: var(--nm-state-warning); }
|
|
3116
3138
|
.stack-icon-btn.danger { color: var(--nm-state-danger); }
|
|
3117
3139
|
.stack-icon-btn:focus-visible {
|
|
3140
|
+
outline: none;
|
|
3118
3141
|
border-color: var(--accent);
|
|
3119
3142
|
box-shadow: 0 0 0 3px var(--accent-glow);
|
|
3120
3143
|
}
|
|
@@ -3127,12 +3150,12 @@ button.todo-item { background: transparent; }
|
|
|
3127
3150
|
#goal-bar {
|
|
3128
3151
|
border-bottom: none;
|
|
3129
3152
|
border-radius: 8px 8px 0 0;
|
|
3130
|
-
border-color:
|
|
3131
|
-
background: linear-gradient(90deg,
|
|
3153
|
+
border-color: color-mix(in srgb, var(--accent) 24%, transparent);
|
|
3154
|
+
background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 10%, transparent), rgba(24,24,24,0.94) 34%);
|
|
3132
3155
|
box-shadow: none;
|
|
3133
3156
|
}
|
|
3134
3157
|
[data-theme="light"] #goal-bar {
|
|
3135
|
-
background: linear-gradient(90deg,
|
|
3158
|
+
background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 13%, transparent), rgba(255,255,255,0.82) 36%, rgba(255,255,255,0.72) 100%);
|
|
3136
3159
|
border-color: rgba(74,104,223,0.2);
|
|
3137
3160
|
}
|
|
3138
3161
|
[data-theme="light"] #goal-bar.goal-paused {
|
|
@@ -3140,7 +3163,7 @@ button.todo-item { background: transparent; }
|
|
|
3140
3163
|
}
|
|
3141
3164
|
|
|
3142
3165
|
#goal-bar .stack-row { grid-template-columns: auto minmax(0,1fr) auto; }
|
|
3143
|
-
#goal-bar .stack-row::before { content:""; width:6px; height:6px; border-radius:50%; background:var(--accent); box-shadow:0 0 0 3px
|
|
3166
|
+
#goal-bar .stack-row::before { content:""; width:6px; height:6px; border-radius:50%; background:var(--accent); box-shadow:0 0 0 3px color-mix(in srgb, var(--accent) 12%, transparent); }
|
|
3144
3167
|
#goal-bar.goal-paused .stack-row::before { background:#f4c95d; box-shadow:0 0 0 3px rgba(244,201,93,.12); }
|
|
3145
3168
|
|
|
3146
3169
|
#goal-bar .goal-text {
|
|
@@ -3222,7 +3245,7 @@ button.todo-item { background: transparent; }
|
|
|
3222
3245
|
transition: background var(--duration-fast) var(--ease-out-expo), opacity var(--duration-fast) var(--ease-out-expo);
|
|
3223
3246
|
}
|
|
3224
3247
|
|
|
3225
|
-
.todo-item:hover { background:
|
|
3248
|
+
.todo-item:hover { background: var(--control-hover-bg); }
|
|
3226
3249
|
|
|
3227
3250
|
.todo-check {
|
|
3228
3251
|
width: 14px;
|
|
@@ -3373,6 +3396,19 @@ button.todo-item { background: transparent; }
|
|
|
3373
3396
|
.queue-item.dragging { opacity: 0.58; transform: scale(0.99); }
|
|
3374
3397
|
.queue-item.queue-drag-displaced { transition: transform 120ms var(--ease-out-expo); }
|
|
3375
3398
|
|
|
3399
|
+
/* dev-0.6.4: 该排队行绑定的是另一条分页,正在等待自己的运行分支。 */
|
|
3400
|
+
.queue-item[data-queue-branch-waiting="1"] { opacity: 0.68; }
|
|
3401
|
+
.queue-item[data-queue-branch-waiting="1"] .queue-edit { font-style: italic; }
|
|
3402
|
+
|
|
3403
|
+
/* dev-0.6.4: 该排队行被失败的前置 Build 阻断,恢复队列会重新挂载到已提交 frontier。 */
|
|
3404
|
+
.queue-item[data-queue-blocked="1"] .queue-edit { color: var(--danger, #e06c75); }
|
|
3405
|
+
.queue-item[data-queue-blocked="1"]::after {
|
|
3406
|
+
content: '!';
|
|
3407
|
+
color: var(--danger, #e06c75);
|
|
3408
|
+
font: 10px var(--font-mono);
|
|
3409
|
+
}
|
|
3410
|
+
|
|
3411
|
+
|
|
3376
3412
|
.queue-drag {
|
|
3377
3413
|
width: 22px;
|
|
3378
3414
|
height: 22px;
|
|
@@ -3796,6 +3832,18 @@ button.todo-item { background: transparent; }
|
|
|
3796
3832
|
position: relative;
|
|
3797
3833
|
z-index: 1;
|
|
3798
3834
|
}
|
|
3835
|
+
/* Carried content wrappers are the destination label itself, not a bare
|
|
3836
|
+
stacking child. They author their own inset box, and the fallback rule above
|
|
3837
|
+
outranks them (.liquid-selection-float > :not(...) is two class-level
|
|
3838
|
+
selectors against one), so their authored position has to be restated with
|
|
3839
|
+
the same selector weight and a later source order. Without it a pressed
|
|
3840
|
+
fixed control (left rail New row, rail collapse button) and a carried
|
|
3841
|
+
conversation row lose inset:0 / inset:9px, become shrink-to-fit flex items
|
|
3842
|
+
and paint left-flush inside the glass instead of staying centered. */
|
|
3843
|
+
.liquid-selection-float > .liquid-fixed-control-content,
|
|
3844
|
+
.liquid-selection-float > .conversation-drag-content {
|
|
3845
|
+
position: absolute;
|
|
3846
|
+
}
|
|
3799
3847
|
@keyframes model-menu-in { from { opacity: 0; transform: translateY(5px) scale(.985); } to { opacity: 1; transform: none; } }
|
|
3800
3848
|
|
|
3801
3849
|
.newmark-select-shell {
|
|
@@ -3904,6 +3952,9 @@ button.todo-item { background: transparent; }
|
|
|
3904
3952
|
|
|
3905
3953
|
.tool-select:hover { border-color: var(--border-hover); }
|
|
3906
3954
|
.tool-select:focus { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-glow); }
|
|
3955
|
+
/* The enhanced native <select> is still a Tab stop but is transparent; mirror its
|
|
3956
|
+
keyboard focus on the proxy button that immediately follows it. */
|
|
3957
|
+
.newmark-select-native:focus-visible + .newmark-select-button { border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-glow); }
|
|
3907
3958
|
|
|
3908
3959
|
.context-token-wrap {
|
|
3909
3960
|
position: relative;
|
|
@@ -3967,6 +4018,20 @@ button.todo-item { background: transparent; }
|
|
|
3967
4018
|
opacity: 1;
|
|
3968
4019
|
transform: scale(1.08);
|
|
3969
4020
|
}
|
|
4021
|
+
/* The ring's donut mask clips its own outline/box-shadow, so the keyboard ring is
|
|
4022
|
+
painted by the wrapper: 2px wide, 2px outside the 16px ring (global indicator). */
|
|
4023
|
+
.context-token-wrap:has(> #context-token-ring:focus-visible)::before {
|
|
4024
|
+
content: "";
|
|
4025
|
+
position: absolute;
|
|
4026
|
+
left: 50%;
|
|
4027
|
+
top: 50%;
|
|
4028
|
+
width: 20px;
|
|
4029
|
+
height: 20px;
|
|
4030
|
+
margin: -10px 0 0 -10px;
|
|
4031
|
+
border-radius: 50%;
|
|
4032
|
+
box-shadow: 0 0 0 2px var(--accent);
|
|
4033
|
+
pointer-events: none;
|
|
4034
|
+
}
|
|
3970
4035
|
|
|
3971
4036
|
#context-token-tooltip {
|
|
3972
4037
|
display: none;
|
|
@@ -4013,9 +4078,9 @@ button.todo-item { background: transparent; }
|
|
|
4013
4078
|
transition: background var(--duration-fast) var(--ease-out-expo), color var(--duration-fast) var(--ease-out-expo);
|
|
4014
4079
|
}
|
|
4015
4080
|
|
|
4016
|
-
.mode-toggle-btn:hover { background:
|
|
4017
|
-
.mode-toggle-btn.active { background:
|
|
4018
|
-
.mode-toggle-btn:active { transform: scale(0.95)
|
|
4081
|
+
.mode-toggle-btn:hover { background: var(--control-hover-bg); color: var(--text); }
|
|
4082
|
+
.mode-toggle-btn.active { background: color-mix(in srgb, var(--accent) 15%, transparent); color: var(--accent); border-radius: var(--radius-full); }
|
|
4083
|
+
.mode-toggle-btn:active { transform: scale(0.95); }
|
|
4019
4084
|
|
|
4020
4085
|
.workspace-type-pager {
|
|
4021
4086
|
display: grid;
|
|
@@ -4046,11 +4111,11 @@ button.todo-item { background: transparent; }
|
|
|
4046
4111
|
display: flex;
|
|
4047
4112
|
align-items: center;
|
|
4048
4113
|
justify-content: center;
|
|
4049
|
-
transition: transform var(--duration-fast) var(--ease-out-
|
|
4114
|
+
transition: transform var(--duration-fast) var(--ease-out-expo), box-shadow var(--duration-fast) var(--ease-out-expo);
|
|
4050
4115
|
box-shadow: 0 2px 12px var(--accent-glow);
|
|
4051
4116
|
}
|
|
4052
4117
|
|
|
4053
|
-
#submit-btn:hover { transform: scale(1.08); box-shadow: 0 4px 20px
|
|
4118
|
+
#submit-btn:hover { transform: scale(1.08); box-shadow: 0 4px 20px color-mix(in srgb, var(--accent) 50%, transparent); }
|
|
4054
4119
|
#submit-btn:active { transform: scale(0.92); transition: transform 80ms var(--ease-out-expo); }
|
|
4055
4120
|
#submit-btn::after { display: none !important; }
|
|
4056
4121
|
#submit-btn:active { transform: none; scale: 1; }
|
|
@@ -4176,13 +4241,13 @@ button.todo-item { background: transparent; }
|
|
|
4176
4241
|
|
|
4177
4242
|
.working-glow {
|
|
4178
4243
|
animation: none;
|
|
4179
|
-
box-shadow: 0 0 16px var(--accent-glow), 0 0 40px
|
|
4180
|
-
border-color:
|
|
4244
|
+
box-shadow: 0 0 16px var(--accent-glow), 0 0 40px color-mix(in srgb, var(--accent) 10%, transparent);
|
|
4245
|
+
border-color: color-mix(in srgb, var(--accent) 40%, transparent) !important;
|
|
4181
4246
|
}
|
|
4182
4247
|
|
|
4183
4248
|
@keyframes working-border-glow {
|
|
4184
|
-
0%, 100% { box-shadow: 0 0 12px var(--accent-glow), 0 0 30px
|
|
4185
|
-
50% { box-shadow: 0 0 24px
|
|
4249
|
+
0%, 100% { box-shadow: 0 0 12px var(--accent-glow), 0 0 30px color-mix(in srgb, var(--accent) 8%, transparent); }
|
|
4250
|
+
50% { box-shadow: 0 0 24px color-mix(in srgb, var(--accent) 50%, transparent), 0 0 60px color-mix(in srgb, var(--accent) 15%, transparent); }
|
|
4186
4251
|
}
|
|
4187
4252
|
|
|
4188
4253
|
/* ============================================================
|
|
@@ -4443,8 +4508,8 @@ button.todo-item { background: transparent; }
|
|
|
4443
4508
|
transition: background var(--duration-fast) var(--ease-out-expo), color var(--duration-fast) var(--ease-out-expo), border-color var(--duration-fast) var(--ease-out-expo);
|
|
4444
4509
|
}
|
|
4445
4510
|
|
|
4446
|
-
.stab-btn:hover { background:
|
|
4447
|
-
.stab-btn.active { background:
|
|
4511
|
+
.stab-btn:hover { background: var(--control-hover-bg); color: var(--text); }
|
|
4512
|
+
.stab-btn.active { background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--accent); border-color: color-mix(in srgb, var(--accent) 25%, transparent); }
|
|
4448
4513
|
.stab-btn:focus-visible { outline:none; border-color:var(--accent); box-shadow:0 0 0 3px var(--accent-glow); color:var(--text-bright); }
|
|
4449
4514
|
|
|
4450
4515
|
.plugin-tabs { overflow-x:auto; scrollbar-width:thin; }
|
|
@@ -4760,6 +4825,10 @@ textarea.mcp-input { min-height:76px; resize:vertical; font-family:var(--font-mo
|
|
|
4760
4825
|
box-shadow: 0 2px 6px rgba(0,0,0,.3);
|
|
4761
4826
|
transition: width 120ms var(--ease-out-expo), height 120ms var(--ease-out-expo), transform 120ms var(--ease-out-expo), box-shadow 120ms var(--ease-out-expo);
|
|
4762
4827
|
}
|
|
4828
|
+
.setting-control input[type="range"]:focus-visible::-webkit-slider-thumb,
|
|
4829
|
+
#settings-glass-opacity:focus-visible::-webkit-slider-thumb {
|
|
4830
|
+
box-shadow: 0 0 0 2px var(--bg), 0 0 0 4px var(--accent), 0 2px 6px rgba(0,0,0,.3);
|
|
4831
|
+
}
|
|
4763
4832
|
#settings-glass-opacity.liquid-range-dragging::-webkit-slider-thumb {
|
|
4764
4833
|
width: 26px;
|
|
4765
4834
|
height: 20px;
|
|
@@ -5097,9 +5166,9 @@ textarea.mcp-input { min-height:76px; resize:vertical; font-family:var(--font-mo
|
|
|
5097
5166
|
.memory-lab-node.selected {
|
|
5098
5167
|
min-height: 52px;
|
|
5099
5168
|
border-color: var(--accent);
|
|
5100
|
-
background:
|
|
5169
|
+
background: color-mix(in srgb, var(--accent) 16%, transparent);
|
|
5101
5170
|
color: var(--text-bright);
|
|
5102
|
-
box-shadow: 0 0 0 1px
|
|
5171
|
+
box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 8%, transparent);
|
|
5103
5172
|
}
|
|
5104
5173
|
.memory-lab-node-label {
|
|
5105
5174
|
min-width: 0;
|
|
@@ -5303,8 +5372,8 @@ textarea.mcp-input { min-height:76px; resize:vertical; font-family:var(--font-mo
|
|
|
5303
5372
|
}
|
|
5304
5373
|
.memory-lab-overview-node.focus {
|
|
5305
5374
|
border-color: var(--accent);
|
|
5306
|
-
background:
|
|
5307
|
-
box-shadow: 0 0 0 5px
|
|
5375
|
+
background: color-mix(in srgb, var(--accent) 20%, transparent);
|
|
5376
|
+
box-shadow: 0 0 0 5px color-mix(in srgb, var(--accent) 11%, transparent), 0 12px 28px rgba(0,0,0,0.24);
|
|
5308
5377
|
}
|
|
5309
5378
|
.memory-lab-overview-node.dim { opacity: 0.18; }
|
|
5310
5379
|
.memory-lab-overview-stage.zoom-dots .memory-lab-overview-node {
|
|
@@ -5409,9 +5478,9 @@ html.newmark-memory-overview-viewer body {
|
|
|
5409
5478
|
}
|
|
5410
5479
|
[data-theme="light"] .memory-lab-node.selected,
|
|
5411
5480
|
[data-theme="light"] .memory-lab-overview-node.focus {
|
|
5412
|
-
background:
|
|
5481
|
+
background: color-mix(in srgb, var(--accent) 10%, transparent);
|
|
5413
5482
|
color: var(--text-bright);
|
|
5414
|
-
box-shadow: 0 0 0 3px
|
|
5483
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 8%, transparent);
|
|
5415
5484
|
}
|
|
5416
5485
|
[data-theme="light"] .memory-lab-overview-grid {
|
|
5417
5486
|
opacity: 0.34;
|
|
@@ -5429,15 +5498,15 @@ html.newmark-memory-overview-viewer body {
|
|
|
5429
5498
|
.model-chip {
|
|
5430
5499
|
display:flex; align-items:center; gap:12px; box-sizing:border-box;
|
|
5431
5500
|
width:100%; min-width:0; margin:6px 0; padding:10px 12px;
|
|
5432
|
-
border-radius:var(--radius-sm); background:
|
|
5433
|
-
border:1px solid
|
|
5501
|
+
border-radius:var(--radius-sm); background:color-mix(in srgb, var(--accent) 10%, transparent);
|
|
5502
|
+
border:1px solid color-mix(in srgb, var(--accent) 20%, transparent); font-size:11px; color:var(--text-accent);
|
|
5434
5503
|
}
|
|
5435
5504
|
.model-chip-info { flex:1; min-width:0; text-align:left; overflow-wrap:anywhere; }
|
|
5436
5505
|
.model-chip-actions { display:flex; flex:0 0 auto; align-items:center; gap:6px; margin-left:auto; }
|
|
5437
5506
|
.model-response-warning { color:var(--text-dim); font-size:inherit; margin-left:4px; font-weight:400; }
|
|
5438
5507
|
.model-enable-switch { width:38px; height:22px; padding:2px; border-radius:999px; border:1px solid var(--glass-border-1); background:var(--glass-bg-1); cursor:pointer; flex:none; }
|
|
5439
5508
|
.model-enable-thumb { display:block; width:16px; height:16px; border-radius:50%; background:rgba(255,255,255,.55); transform:translateX(0); transition:transform 180ms var(--ease-out-expo); }
|
|
5440
|
-
.model-enable-switch[aria-checked="true"] { background:
|
|
5509
|
+
.model-enable-switch[aria-checked="true"] { background:color-mix(in srgb, var(--accent) 35%, transparent); }
|
|
5441
5510
|
.model-enable-switch[aria-checked="true"] .model-enable-thumb { transform:translateX(16px); }
|
|
5442
5511
|
.model-enable-switch:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }
|
|
5443
5512
|
.model-enable-switch:disabled { opacity:.6; }
|
|
@@ -5511,7 +5580,7 @@ html.newmark-memory-overview-viewer body {
|
|
|
5511
5580
|
padding: 8px 20px;
|
|
5512
5581
|
border: 1px solid var(--accent);
|
|
5513
5582
|
border-radius: var(--radius-md);
|
|
5514
|
-
background:
|
|
5583
|
+
background: color-mix(in srgb, var(--accent) 10%, transparent);
|
|
5515
5584
|
color: var(--accent);
|
|
5516
5585
|
font-size: 12px;
|
|
5517
5586
|
font-family: var(--font-ui);
|
|
@@ -5519,7 +5588,7 @@ html.newmark-memory-overview-viewer body {
|
|
|
5519
5588
|
transition: background var(--duration-fast) var(--ease-out-expo);
|
|
5520
5589
|
}
|
|
5521
5590
|
|
|
5522
|
-
.fuzzy-inject-btn:hover { background:
|
|
5591
|
+
.fuzzy-inject-btn:hover { background: color-mix(in srgb, var(--accent) 20%, transparent); }
|
|
5523
5592
|
|
|
5524
5593
|
/* Archive list */
|
|
5525
5594
|
.archive-item {
|
|
@@ -5716,14 +5785,14 @@ html.newmark-memory-overview-viewer body {
|
|
|
5716
5785
|
transition: background var(--duration-fast) var(--ease-out-expo), color var(--duration-fast) var(--ease-out-expo);
|
|
5717
5786
|
}
|
|
5718
5787
|
|
|
5719
|
-
.flow-new-btn:hover { background:
|
|
5788
|
+
.flow-new-btn:hover { background: color-mix(in srgb, var(--accent) 8%, transparent); color: var(--accent); border-color: var(--accent); }
|
|
5720
5789
|
|
|
5721
5790
|
/* ============================================================
|
|
5722
5791
|
ANIMATIONS & UTILITY
|
|
5723
5792
|
============================================================ */
|
|
5724
5793
|
@keyframes fade-in-up {
|
|
5725
|
-
from { opacity: 0; transform: translateY(12px)
|
|
5726
|
-
to { opacity: 1; transform: translateY(0)
|
|
5794
|
+
from { opacity: 0; transform: translateY(12px); }
|
|
5795
|
+
to { opacity: 1; transform: translateY(0); }
|
|
5727
5796
|
}
|
|
5728
5797
|
|
|
5729
5798
|
.hidden { display: none !important; }
|
|
@@ -5949,8 +6018,12 @@ html.newmark-memory-overview-viewer body {
|
|
|
5949
6018
|
.liquid-glass-popup,
|
|
5950
6019
|
.liquid-glass-popup.liquid-popup-exit,
|
|
5951
6020
|
.liquid-menu-color-block,
|
|
5952
|
-
.liquid-selection-float { transition-duration:
|
|
6021
|
+
.liquid-selection-float { transition-duration: 0.01ms !important; animation-duration: 0.01ms !important; }
|
|
5953
6022
|
.liquid-glass-popup::before { opacity: 0; }
|
|
6023
|
+
/* These two-class / id !important transitions outrank the rules above. */
|
|
6024
|
+
.liquid-selection-float.liquid-selection-landing,
|
|
6025
|
+
.conversation-selection-float.conversation-drag-following { transition-duration: 0.01ms !important; }
|
|
6026
|
+
#left.width-animating { transition-duration: 0.01ms !important; }
|
|
5954
6027
|
}
|
|
5955
6028
|
@media (max-width: 900px), (max-height: 680px) {
|
|
5956
6029
|
.liquid-glass-popup { --liquid-popup-blur: calc(var(--glass-blur-3) * 3); }
|
|
@@ -6045,7 +6118,7 @@ html.newmark-memory-overview-viewer body {
|
|
|
6045
6118
|
}
|
|
6046
6119
|
.liquid-glass-popup[data-liquid-popup-feedback='outward']::after { opacity: 0; }
|
|
6047
6120
|
@media (prefers-reduced-motion: reduce) {
|
|
6048
|
-
.liquid-popup-feedback-surface { transition-duration:
|
|
6121
|
+
.liquid-popup-feedback-surface { transition-duration: 0.01ms !important; }
|
|
6049
6122
|
}
|
|
6050
6123
|
.model-select-menu-option,
|
|
6051
6124
|
.newmark-select-option,
|
|
@@ -6155,10 +6228,12 @@ button::after {
|
|
|
6155
6228
|
border-radius: inherit;
|
|
6156
6229
|
opacity: 0;
|
|
6157
6230
|
background: var(--liquid-refraction);
|
|
6158
|
-
backdrop-filter: blur(calc(var(--glass-blur-3) * 4)) saturate(145%) brightness(1.04);
|
|
6159
|
-
-webkit-backdrop-filter: blur(calc(var(--glass-blur-3) * 4)) saturate(145%) brightness(1.04);
|
|
6160
6231
|
box-shadow: var(--liquid-top-highlight), var(--liquid-inner-shadow), 0 4px 14px rgba(0,0,0,0.18);
|
|
6161
|
-
|
|
6232
|
+
/* The backdrop blur is attached only while the plate can be seen. Chromium
|
|
6233
|
+
still paints and composites an opacity-0 layer that has a backdrop-filter,
|
|
6234
|
+
so a resting blur cost one backdrop layer per visible button. The delayed
|
|
6235
|
+
step keeps the blur through the 90ms release fade. */
|
|
6236
|
+
transition: opacity 90ms var(--ease-out-expo), backdrop-filter 0s linear 90ms;
|
|
6162
6237
|
}
|
|
6163
6238
|
button:hover:not(:disabled) {
|
|
6164
6239
|
filter: brightness(1.06);
|
|
@@ -6169,7 +6244,12 @@ button:active:not(:disabled) {
|
|
|
6169
6244
|
background-color: transparent !important;
|
|
6170
6245
|
box-shadow: none;
|
|
6171
6246
|
}
|
|
6172
|
-
button:active:not(:disabled)::after {
|
|
6247
|
+
button:active:not(:disabled)::after {
|
|
6248
|
+
opacity: 1;
|
|
6249
|
+
backdrop-filter: blur(calc(var(--glass-blur-3) * 4)) saturate(145%) brightness(1.04);
|
|
6250
|
+
-webkit-backdrop-filter: blur(calc(var(--glass-blur-3) * 4)) saturate(145%) brightness(1.04);
|
|
6251
|
+
transition: opacity 90ms var(--ease-out-expo);
|
|
6252
|
+
}
|
|
6173
6253
|
button:disabled { opacity: 0.5; }
|
|
6174
6254
|
|
|
6175
6255
|
/* Unified glass controls are rendered by a real top-layer float. Their local
|
|
@@ -6207,6 +6287,21 @@ button.conversation-work-run-head::after,
|
|
|
6207
6287
|
button.shell-block-header::after,
|
|
6208
6288
|
button.diff-block-header::after,
|
|
6209
6289
|
button.work-review-head::after { display: none !important; }
|
|
6290
|
+
/* Without a press plate, a held utility action keeps its own hover wash
|
|
6291
|
+
instead of dropping back to the bare row, and it does not take the
|
|
6292
|
+
plate's outward lift. */
|
|
6293
|
+
.top-btn:active:not(:disabled),
|
|
6294
|
+
.conv-more-btn:active:not(:disabled),
|
|
6295
|
+
.provider-icon-btn:active:not(:disabled),
|
|
6296
|
+
.work-review-toggle:active:not(:disabled) {
|
|
6297
|
+
scale: 1;
|
|
6298
|
+
background-color: var(--control-hover-bg) !important;
|
|
6299
|
+
}
|
|
6300
|
+
.msg-action-btn:active:not(:disabled) {
|
|
6301
|
+
scale: 1;
|
|
6302
|
+
background-color: var(--glass-bg-2) !important;
|
|
6303
|
+
}
|
|
6304
|
+
.top-btn.close-btn:active:not(:disabled) { background-color: rgba(255,68,68,0.2) !important; }
|
|
6210
6305
|
button.conversation-work-run-head:active:not(:disabled),
|
|
6211
6306
|
button.shell-block-header:active:not(:disabled),
|
|
6212
6307
|
button.diff-block-header:active:not(:disabled),
|
|
@@ -6707,14 +6802,6 @@ input[type="checkbox"].liquid-switch-dragging::before {
|
|
|
6707
6802
|
<path d="M6 4h1a2 2 0 0 1 2 2 2 2 0 0 1 2-2h1" />
|
|
6708
6803
|
<path d="M9 6v12" />
|
|
6709
6804
|
</symbol>
|
|
6710
|
-
<symbol id="thumbs-down" viewBox="0 0 24 24">
|
|
6711
|
-
<path d="M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22a3.13 3.13 0 0 1-3-3.88Z" />
|
|
6712
|
-
<path d="M17 14V2" />
|
|
6713
|
-
</symbol>
|
|
6714
|
-
<symbol id="thumbs-up" viewBox="0 0 24 24">
|
|
6715
|
-
<path d="M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z" />
|
|
6716
|
-
<path d="M7 10v12" />
|
|
6717
|
-
</symbol>
|
|
6718
6805
|
<symbol id="trash-2" viewBox="0 0 24 24">
|
|
6719
6806
|
<path d="M10 11v6" />
|
|
6720
6807
|
<path d="M14 11v6" />
|
|
@@ -7195,12 +7282,15 @@ var state = {
|
|
|
7195
7282
|
feedbackLevel: 'default',
|
|
7196
7283
|
autoSwitch: 'off',
|
|
7197
7284
|
autoSwitchScope: 'all',
|
|
7285
|
+
/**
|
|
7286
|
+
* Auto decision model ("JEV") slot as reported by the backend. `locked`
|
|
7287
|
+
* means Auto is off, so the decision model can neither be edited nor move a
|
|
7288
|
+
* model; every control in the JEV card is disabled while it is true.
|
|
7289
|
+
*/
|
|
7290
|
+
jevDecisionModel: null,
|
|
7198
7291
|
openAIApiMode: 'chat_stream',
|
|
7199
7292
|
fallbackOnUnavailable: 'off',
|
|
7200
7293
|
switchTendency: 'balanced',
|
|
7201
|
-
autoRouteRatingAvailable: false,
|
|
7202
|
-
autoRouteRatingRouteId: '',
|
|
7203
|
-
autoRouteRatingScore: 0,
|
|
7204
7294
|
workspaces: [],
|
|
7205
7295
|
currentWorkspace: '',
|
|
7206
7296
|
currentWorkspaceId: '',
|
|
@@ -7211,6 +7301,12 @@ var state = {
|
|
|
7211
7301
|
pendingOptions: [],
|
|
7212
7302
|
pendingOptionAnswers: {},
|
|
7213
7303
|
pendingOptionBatchKey: '',
|
|
7304
|
+
pendingOptionsByTarget: {},
|
|
7305
|
+
// Runtime key of the conversation whose transcript is currently in #chat-area.
|
|
7306
|
+
// The pending-option block is appended to that transcript, so an async
|
|
7307
|
+
// snapshot may only (re)paint it while it still owns the on-screen area.
|
|
7308
|
+
renderedChatTargetKey: '',
|
|
7309
|
+
pendingOptionAnswersByTarget: {},
|
|
7214
7310
|
promptAttachments: [],
|
|
7215
7311
|
nextQueue: [],
|
|
7216
7312
|
nextQueueRequests: [],
|
|
@@ -7221,6 +7317,7 @@ var state = {
|
|
|
7221
7317
|
backendQueue: { steering: [], followUp: [] },
|
|
7222
7318
|
backendQueuesByTarget: {},
|
|
7223
7319
|
queueItemsByTarget: {},
|
|
7320
|
+
pendingDrainRefreshByTarget: {},
|
|
7224
7321
|
defaultFlow: '',
|
|
7225
7322
|
settingsActiveTab: 'general',
|
|
7226
7323
|
globalAgentPrompt: '',
|
|
@@ -7575,7 +7672,8 @@ var NEWMARK_I18N = {
|
|
|
7575
7672
|
'model.githubModelsImported': 'Imported {count} models.',
|
|
7576
7673
|
'model.githubModelsWarning': 'Warning: {warning}',
|
|
7577
7674
|
'model.autoDetect': 'Auto detect',
|
|
7578
|
-
'model.modelName': '
|
|
7675
|
+
'model.modelName': 'API model identifier',
|
|
7676
|
+
'model.displayName': 'Display name',
|
|
7579
7677
|
'model.provider': 'Provider',
|
|
7580
7678
|
'model.contextSize': 'Context size',
|
|
7581
7679
|
'model.vision': 'Vision',
|
|
@@ -7597,15 +7695,21 @@ var NEWMARK_I18N = {
|
|
|
7597
7695
|
'model.openAIChat': 'Chat Completions',
|
|
7598
7696
|
'model.openAIResponses': 'Responses API',
|
|
7599
7697
|
'model.fallback': 'Fallback unavailable model',
|
|
7600
|
-
'model.preference': '
|
|
7698
|
+
'model.preference': 'Model decision tendency',
|
|
7699
|
+
'model.preferenceDesc': 'The current eligible model makes each Auto choice. This setting guides how readily it favors a different eligible model; it does not select a model or set a numeric cutoff.',
|
|
7601
7700
|
'model.costSaving': 'Cost saving',
|
|
7602
7701
|
'model.performance': 'Quality',
|
|
7603
|
-
'model.
|
|
7604
|
-
'model.
|
|
7605
|
-
'model.
|
|
7606
|
-
'model.
|
|
7607
|
-
'model.
|
|
7608
|
-
'model.
|
|
7702
|
+
'model.conservativeTendency': 'Conservative',
|
|
7703
|
+
'model.balancedTendency': 'Balanced',
|
|
7704
|
+
'model.aggressiveTendency': 'Aggressive',
|
|
7705
|
+
'model.jevTitle': 'Auto decision policy',
|
|
7706
|
+
'model.jevDesc': 'Uses the current eligible conversation model with native thinking disabled and strict JSON Schema output. Invalid output, unsupported controls, or timeout stops Auto routing.',
|
|
7707
|
+
'model.jevEnabled': 'Current-model decision enabled',
|
|
7708
|
+
'model.jevCurrentModelStatus': 'Current eligible model · thinking off · strict JSON Schema',
|
|
7709
|
+
'model.jevTimeout': 'Decision timeout (ms)',
|
|
7710
|
+
'model.jevLocked': 'Auto is off: current-model decision settings are locked and no model may change.',
|
|
7711
|
+
'model.jevSaved': 'Auto decision policy updated.',
|
|
7712
|
+
'model.jevRefused': 'The Auto decision policy was not changed.',
|
|
7609
7713
|
'model.validationTitle': 'Model validation',
|
|
7610
7714
|
'model.validating': 'Validating model latency and capabilities...',
|
|
7611
7715
|
'model.validationProgress': 'Validation progress',
|
|
@@ -8318,7 +8422,8 @@ var NEWMARK_I18N = {
|
|
|
8318
8422
|
'model.githubModelsImported': '已导入 {count} 个模型。',
|
|
8319
8423
|
'model.githubModelsWarning': '警告:{warning}',
|
|
8320
8424
|
'model.autoDetect': '自动检测',
|
|
8321
|
-
'model.modelName': '
|
|
8425
|
+
'model.modelName': '调用模型标识',
|
|
8426
|
+
'model.displayName': '显示名称',
|
|
8322
8427
|
'model.provider': '供应商',
|
|
8323
8428
|
'model.contextSize': '上下文规模',
|
|
8324
8429
|
'model.vision': '视觉',
|
|
@@ -8340,15 +8445,21 @@ var NEWMARK_I18N = {
|
|
|
8340
8445
|
'model.openAIChat': 'Chat Completions',
|
|
8341
8446
|
'model.openAIResponses': 'Responses API',
|
|
8342
8447
|
'model.fallback': '模型不可用时回退',
|
|
8343
|
-
'model.preference': '
|
|
8448
|
+
'model.preference': '模型决策倾向',
|
|
8449
|
+
'model.preferenceDesc': '每次 Auto 选择均由当前符合条件的模型决策。此设置只影响模型的决策倾向,不替模型选择部署,也不设数值阈值。',
|
|
8344
8450
|
'model.costSaving': '节省优先',
|
|
8345
8451
|
'model.performance': '质量优先',
|
|
8346
|
-
'model.
|
|
8347
|
-
'model.
|
|
8348
|
-
'model.
|
|
8349
|
-
'model.
|
|
8350
|
-
'model.
|
|
8351
|
-
'model.
|
|
8452
|
+
'model.conservativeTendency': '保守',
|
|
8453
|
+
'model.balancedTendency': '均衡',
|
|
8454
|
+
'model.aggressiveTendency': '激进',
|
|
8455
|
+
'model.jevTitle': 'Auto 决策策略',
|
|
8456
|
+
'model.jevDesc': '仅使用当前符合条件的对话模型,并原生关闭思考、要求严格 JSON Schema 输出。输出无效、关闭思考不受支持或请求超时都会停止 Auto 路由。',
|
|
8457
|
+
'model.jevEnabled': '启用当前模型决策',
|
|
8458
|
+
'model.jevCurrentModelStatus': '当前符合条件的模型 · 已关闭思考 · 严格 JSON Schema',
|
|
8459
|
+
'model.jevTimeout': '决策超时(毫秒)',
|
|
8460
|
+
'model.jevLocked': 'Auto 已关闭:当前模型决策设置已锁定,任何模型都不会变动。',
|
|
8461
|
+
'model.jevSaved': '已更新 Auto 决策策略。',
|
|
8462
|
+
'model.jevRefused': 'Auto 决策策略未做任何修改。',
|
|
8352
8463
|
'model.validationTitle': '模型校验',
|
|
8353
8464
|
'model.validating': '正在校验模型延迟和能力...',
|
|
8354
8465
|
'model.validationProgress': '校验进度',
|
|
@@ -9636,87 +9747,6 @@ window.copyMessageText = async function(button) {
|
|
|
9636
9747
|
var branchApiOverride = null;
|
|
9637
9748
|
window.__setBranchApiForTest = function(nextApi) { branchApiOverride = nextApi || null; };
|
|
9638
9749
|
|
|
9639
|
-
function applyAutoRouteRatingState(snapshot) {
|
|
9640
|
-
snapshot = snapshot || {};
|
|
9641
|
-
var decision = snapshot.routeDecision && typeof snapshot.routeDecision === 'object' ? snapshot.routeDecision : null;
|
|
9642
|
-
var routeId = decision ? String(decision.routeId || '') : '';
|
|
9643
|
-
var selection = decision && decision.requestedSelection;
|
|
9644
|
-
var available = !!routeId && !!decision.resolvedDeployment && selection && selection.kind === 'auto'
|
|
9645
|
-
&& snapshot.autoRouteRatingAvailable !== false;
|
|
9646
|
-
if (routeId !== state.autoRouteRatingRouteId) state.autoRouteRatingScore = 0;
|
|
9647
|
-
state.autoRouteRatingAvailable = !!available;
|
|
9648
|
-
state.autoRouteRatingRouteId = available ? routeId : '';
|
|
9649
|
-
if (!available) state.autoRouteRatingScore = 0;
|
|
9650
|
-
}
|
|
9651
|
-
|
|
9652
|
-
function autoRouteRatingLabel(score) {
|
|
9653
|
-
if (currentLang() === 'zh') return score > 0 ? '此 Auto 路由有帮助' : '此 Auto 路由没有帮助';
|
|
9654
|
-
return score > 0 ? 'This Auto route was helpful' : 'This Auto route was not helpful';
|
|
9655
|
-
}
|
|
9656
|
-
|
|
9657
|
-
function attachAutoRouteRatingControls(message) {
|
|
9658
|
-
if (!message || !api.rateAutoRoute || !state.autoRouteRatingAvailable || !state.autoRouteRatingRouteId) return;
|
|
9659
|
-
var actions = message.querySelector('.msg-actions');
|
|
9660
|
-
if (!actions) return;
|
|
9661
|
-
var old = actions.querySelectorAll('.route-rating-btn');
|
|
9662
|
-
for (var i = 0; i < old.length; i++) old[i].remove();
|
|
9663
|
-
[1, -1].forEach(function(score) {
|
|
9664
|
-
var label = autoRouteRatingLabel(score);
|
|
9665
|
-
var selected = Number(state.autoRouteRatingScore || 0) === score;
|
|
9666
|
-
var button = document.createElement('button');
|
|
9667
|
-
button.type = 'button';
|
|
9668
|
-
button.className = 'msg-action-btn route-rating-btn' + (selected ? ' route-rating-selected' : '');
|
|
9669
|
-
button.title = label;
|
|
9670
|
-
button.setAttribute('aria-label', label);
|
|
9671
|
-
button.setAttribute('aria-pressed', selected ? 'true' : 'false');
|
|
9672
|
-
button.setAttribute('data-route-id', state.autoRouteRatingRouteId);
|
|
9673
|
-
button.innerHTML = iconSvg(score > 0 ? 'thumbs-up' : 'thumbs-down', label, 'tiny');
|
|
9674
|
-
button.disabled = !!state.autoRouteRatingScore;
|
|
9675
|
-
button.addEventListener('click', function() { window.rateAutoRoute(button, score); });
|
|
9676
|
-
actions.appendChild(button);
|
|
9677
|
-
});
|
|
9678
|
-
}
|
|
9679
|
-
|
|
9680
|
-
function renderAutoRouteRatingControls() {
|
|
9681
|
-
if (!els['chat-area']) return;
|
|
9682
|
-
var existing = els['chat-area'].querySelectorAll('.route-rating-btn');
|
|
9683
|
-
for (var i = 0; i < existing.length; i++) existing[i].remove();
|
|
9684
|
-
if (!state.autoRouteRatingAvailable) return;
|
|
9685
|
-
var messages = els['chat-area'].querySelectorAll('.chat-msg.assistant:not(.work-run-message)');
|
|
9686
|
-
attachAutoRouteRatingControls(messages.length ? messages[messages.length - 1] : null);
|
|
9687
|
-
}
|
|
9688
|
-
|
|
9689
|
-
window.rateAutoRoute = async function(button, score) {
|
|
9690
|
-
if (!api.rateAutoRoute || !button) return;
|
|
9691
|
-
var message = button.closest ? button.closest('.chat-msg') : null;
|
|
9692
|
-
var buttons = message ? message.querySelectorAll('.route-rating-btn') : [];
|
|
9693
|
-
for (var i = 0; i < buttons.length; i++) buttons[i].disabled = true;
|
|
9694
|
-
var routeId = String(button.getAttribute('data-route-id') || '');
|
|
9695
|
-
try {
|
|
9696
|
-
var result = await api.rateAutoRoute({
|
|
9697
|
-
target: currentConversationTarget(),
|
|
9698
|
-
score: score,
|
|
9699
|
-
routeId: routeId
|
|
9700
|
-
});
|
|
9701
|
-
if (result && (result.ok || result.reason === 'already_rated')) {
|
|
9702
|
-
state.autoRouteRatingScore = Number(result.score) === -1 || Number(result.score) === 1 ? Number(result.score) : score;
|
|
9703
|
-
renderAutoRouteRatingControls();
|
|
9704
|
-
showUiNotice(currentLang() === 'zh' ? '已记录 Auto 路由反馈。' : 'Auto route feedback recorded.', 'info', 'auto-route-rating-' + routeId);
|
|
9705
|
-
return;
|
|
9706
|
-
}
|
|
9707
|
-
for (var j = 0; j < buttons.length; j++) buttons[j].disabled = false;
|
|
9708
|
-
var reason = result && result.reason ? String(result.reason) : 'no_active_auto_route';
|
|
9709
|
-
showUiNotice(
|
|
9710
|
-
currentLang() === 'zh' ? '无法记录 Auto 路由反馈:' + reason : 'Unable to record Auto route feedback: ' + reason,
|
|
9711
|
-
'error',
|
|
9712
|
-
'auto-route-rating-' + routeId
|
|
9713
|
-
);
|
|
9714
|
-
} catch (error) {
|
|
9715
|
-
for (var k = 0; k < buttons.length; k++) buttons[k].disabled = false;
|
|
9716
|
-
showUiNotice(error && error.message ? error.message : String(error), 'error', 'auto-route-rating-' + routeId);
|
|
9717
|
-
}
|
|
9718
|
-
};
|
|
9719
|
-
|
|
9720
9750
|
window.editUserMessage = function(button, messageIndex) {
|
|
9721
9751
|
var message = button && button.closest ? button.closest('.chat-msg') : null;
|
|
9722
9752
|
if (!message || message.querySelector('.message-inline-editor')) return;
|
|
@@ -9954,8 +9984,12 @@ window.submitUserMessageEdit = async function(message, messageIndex, editedText,
|
|
|
9954
9984
|
if (!isActiveConversationTarget(target)) return;
|
|
9955
9985
|
clearAgentWorkEvents(conversationId);
|
|
9956
9986
|
resetConversationWorkUi(conversationId, false);
|
|
9957
|
-
|
|
9958
|
-
|
|
9987
|
+
// dev-0.6.4: 生成分页时绝不按新分支的清空队列覆盖本地投影。排队行是用户
|
|
9988
|
+
// 已经提交的输入,属于它受理时捕获的那条分支;旧逻辑在这里把队列整体
|
|
9989
|
+
// 清零,于是分页之后排队内容从界面上消失。内核的执行投影仍然只跟随当前
|
|
9990
|
+
// 激活分支(新分支绝不接管另一页的排队输入),刷新由切回该分页时的
|
|
9991
|
+
// 权威快照完成;这里只保留可见性并标记「等待所属分页」。
|
|
9992
|
+
if (result && Array.isArray(result.queueItems)) setQueueItemsForTarget(result.queueItems, target);
|
|
9959
9993
|
state.conversationBranches = Array.isArray(result.branches) ? result.branches : [];
|
|
9960
9994
|
state.conversationBranchGroups = Array.isArray(result.branchGroups) ? result.branchGroups : [];
|
|
9961
9995
|
state.activeConversationBranchId = String(result.activeBranchId || '');
|
|
@@ -10165,7 +10199,8 @@ function appendConversationImageAttachments(container, input) {
|
|
|
10165
10199
|
}
|
|
10166
10200
|
|
|
10167
10201
|
function addMsg(role, text, mode, model, messageIndex, messageMeta) {
|
|
10168
|
-
var
|
|
10202
|
+
var skipAutoScroll = !!(messageMeta && messageMeta.skipAutoScroll);
|
|
10203
|
+
var chatWasAtBottom = !skipAutoScroll && !!(els['chat-area'] && shouldAutoScroll(els['chat-area']));
|
|
10169
10204
|
text = redactSensitiveText(text);
|
|
10170
10205
|
var owningRunId = messageMeta && String(messageMeta.runId || '');
|
|
10171
10206
|
if (owningRunId && role === 'assistant' && els['chat-area']) {
|
|
@@ -10196,6 +10231,7 @@ function addMsg(role, text, mode, model, messageIndex, messageMeta) {
|
|
|
10196
10231
|
}
|
|
10197
10232
|
var div = document.createElement('div');
|
|
10198
10233
|
div.className = 'chat-msg ' + role;
|
|
10234
|
+
if (messageMeta && messageMeta.skipEntryAnimation) div.classList.add('history-static');
|
|
10199
10235
|
var metaLabel = role === 'user'
|
|
10200
10236
|
? (String(mode || '').toLowerCase() === 'flow-user-input' ? t('flow.userInput') : t('message.user'))
|
|
10201
10237
|
: (role === 'workflow' ? t('message.workflow') : (role === 'system' ? t('message.system') : t('message.agent')));
|
|
@@ -10212,7 +10248,11 @@ function addMsg(role, text, mode, model, messageIndex, messageMeta) {
|
|
|
10212
10248
|
if (model) metaInfo += (metaInfo ? ' | ' : '') + t('message.model') + ': ' + model;
|
|
10213
10249
|
div.innerHTML = '<div class="meta"><span class="msg-role">' + metaLabel + '</span><span> | ' + esc(displayTimestamp) + '</span>' +
|
|
10214
10250
|
(metaInfo ? '<span class="meta-extra"> | ' + esc(metaInfo) + '</span>' : '') + messageActionsHtml(role, text, messageIndex) + '</div><div class="msg-body">' + renderMessageContent(text) + '</div>';
|
|
10251
|
+
var msgActions = role === 'user' ? div.querySelector('.msg-actions') : null;
|
|
10252
|
+
div._newmarkEditUserButton = msgActions && msgActions.children.length > 1 ? msgActions.children[1] : null;
|
|
10215
10253
|
div._newmarkMessageText = String(text || '');
|
|
10254
|
+
div._newmarkMessageMode = String(mode || '');
|
|
10255
|
+
div._newmarkMessageModel = String(model || '');
|
|
10216
10256
|
div._newmarkMessageAttachments = appendConversationImageAttachments(div.querySelector('.msg-body'), messageMeta && messageMeta.attachments);
|
|
10217
10257
|
div._newmarkDisplayImages = role === 'assistant' && owningRunId
|
|
10218
10258
|
? appendWorkDisplayImages(div.querySelector('.msg-body'), owningRunId, messageMeta && messageMeta.target)
|
|
@@ -10235,7 +10275,7 @@ function addMsg(role, text, mode, model, messageIndex, messageMeta) {
|
|
|
10235
10275
|
else els['chat-area'].appendChild(div);
|
|
10236
10276
|
} else els['chat-area'].appendChild(div);
|
|
10237
10277
|
if (chatWasAtBottom) _chatShouldAutoScroll = true;
|
|
10238
|
-
autoScrollIfAtBottom();
|
|
10278
|
+
if (!skipAutoScroll) autoScrollIfAtBottom();
|
|
10239
10279
|
return div;
|
|
10240
10280
|
}
|
|
10241
10281
|
|
|
@@ -10453,6 +10493,8 @@ function updateMsg(div, text, role, mode, model, options) {
|
|
|
10453
10493
|
if (preserveRunFinalResponse) div.classList.add('run-final-response');
|
|
10454
10494
|
text = redactSensitiveText(text);
|
|
10455
10495
|
div._newmarkMessageText = String(text || '');
|
|
10496
|
+
if (mode !== undefined && mode !== null) div._newmarkMessageMode = String(mode || '');
|
|
10497
|
+
if (model !== undefined && model !== null) div._newmarkMessageModel = String(model || '');
|
|
10456
10498
|
var body = div.querySelector('.msg-body');
|
|
10457
10499
|
var streaming = !!(options && options.streaming);
|
|
10458
10500
|
if (body) {
|
|
@@ -10654,6 +10696,47 @@ function runtimeKeyFor(workspaceId, conversationId) {
|
|
|
10654
10696
|
return state.runtimeKeyAliases && state.runtimeKeyAliases[base] || base;
|
|
10655
10697
|
}
|
|
10656
10698
|
|
|
10699
|
+
// dev-0.6.4: 全对话 id(分支身份)。用户分页编辑产生的分支与实验性「分支交流」
|
|
10700
|
+
// 产生的分支共用这一条规则:`<conversationId>::branch:<branchNodeId>`。
|
|
10701
|
+
// 供应商会话序列 id 与所有分支级缓存都必须使用它;兄弟分支绝不共享消息、
|
|
10702
|
+
// Build 列表或折叠历史。
|
|
10703
|
+
function conversationBranchIdentity(conversationId, branchNodeId) {
|
|
10704
|
+
var conversation = String(conversationId || 'default') || 'default';
|
|
10705
|
+
var branch = String(branchNodeId || '');
|
|
10706
|
+
return branch ? conversation + '::branch:' + branch : conversation;
|
|
10707
|
+
}
|
|
10708
|
+
|
|
10709
|
+
// 记录每个会话最近一次「被浏览」的分支节点,供后台会话的缓存键使用。
|
|
10710
|
+
function rememberViewedBranchNodeId(target, branchNodeId) {
|
|
10711
|
+
var owner = normalizeQueuedConversationTarget(target);
|
|
10712
|
+
if (!owner) return '';
|
|
10713
|
+
if (!state.viewedBranchNodeIdsByTarget) state.viewedBranchNodeIdsByTarget = {};
|
|
10714
|
+
var key = runtimeKeyFor(owner.workspaceId, owner.conversationId);
|
|
10715
|
+
var branch = String(branchNodeId || '');
|
|
10716
|
+
if (branch) state.viewedBranchNodeIdsByTarget[key] = branch;
|
|
10717
|
+
return String(state.viewedBranchNodeIdsByTarget[key] || '');
|
|
10718
|
+
}
|
|
10719
|
+
|
|
10720
|
+
function viewedBranchNodeIdFor(target) {
|
|
10721
|
+
var owner = normalizeQueuedConversationTarget(target || currentConversationTarget());
|
|
10722
|
+
if (!owner) return '';
|
|
10723
|
+
if (isActiveConversationTarget(owner)) {
|
|
10724
|
+
var path = Array.isArray(state.viewedConversationBranchNodePath) ? state.viewedConversationBranchNodePath : [];
|
|
10725
|
+
var leaf = String(path[path.length - 1] || state.activeConversationBranchId || '');
|
|
10726
|
+
if (leaf) return rememberViewedBranchNodeId(owner, leaf);
|
|
10727
|
+
}
|
|
10728
|
+
if (!state.viewedBranchNodeIdsByTarget) return '';
|
|
10729
|
+
return String(state.viewedBranchNodeIdsByTarget[runtimeKeyFor(owner.workspaceId, owner.conversationId)] || '');
|
|
10730
|
+
}
|
|
10731
|
+
|
|
10732
|
+
// 分支级缓存键:同一会话的两个分页绝不共用一份缓存。
|
|
10733
|
+
function branchConversationViewKey(target) {
|
|
10734
|
+
var owner = normalizeQueuedConversationTarget(target || currentConversationTarget());
|
|
10735
|
+
if (!owner) return '';
|
|
10736
|
+
var branch = viewedBranchNodeIdFor(owner) || 'root';
|
|
10737
|
+
return runtimeKeyFor(owner.workspaceId, owner.conversationId) + '::branch::' + branch;
|
|
10738
|
+
}
|
|
10739
|
+
|
|
10657
10740
|
function registerRuntimeKey(target, runtimeKey) {
|
|
10658
10741
|
runtimeKey = String(runtimeKey || '');
|
|
10659
10742
|
if (!target || !runtimeKey) return runtimeKeyFor(target && target.workspaceId, target && target.conversationId);
|
|
@@ -10811,6 +10894,11 @@ function renderPendingGuideMessages(target, persistedGuideIds) {
|
|
|
10811
10894
|
var messages = guideMessagesForTarget(target);
|
|
10812
10895
|
var pending = Object.keys(messages).map(function(id) { return messages[id]; }).filter(function(item) {
|
|
10813
10896
|
if (!item || persistedGuideIds[item.clientMessageId]) return false;
|
|
10897
|
+
// dev-0.6.4: Guide 只有被内核接收后才出现在时间线上,并且显示在接收位置。
|
|
10898
|
+
// 乐观/待确认(awaitingAck)与 deferred/rejected 的 Guide 都不进入对话区。
|
|
10899
|
+
if (item.awaitingAck) return false;
|
|
10900
|
+
var guideStatus = String(item.status || '').toLowerCase();
|
|
10901
|
+
if (['accepted', 'applied'].indexOf(guideStatus) < 0) return false;
|
|
10814
10902
|
// A Guide already owned by its work run is rendered by the Build block
|
|
10815
10903
|
// (expanded events or collapsed guide list), never as a separate trailing
|
|
10816
10904
|
// message. Without this guard, collapse/expand rebuilds would re-add the
|
|
@@ -10829,26 +10917,6 @@ function renderPendingGuideMessages(target, persistedGuideIds) {
|
|
|
10829
10917
|
element.remove();
|
|
10830
10918
|
element = null;
|
|
10831
10919
|
}
|
|
10832
|
-
if (item.awaitingAck && item.runId) {
|
|
10833
|
-
var runElement = findWorkRunElement({ runId: item.runId });
|
|
10834
|
-
var runBody = runElement && runElement.querySelector('.conversation-work-run-body');
|
|
10835
|
-
if (runBody) {
|
|
10836
|
-
var pendingStack = runBody.querySelector(':scope > .work-run-pending-guides');
|
|
10837
|
-
if (!pendingStack) {
|
|
10838
|
-
pendingStack = document.createElement('div');
|
|
10839
|
-
pendingStack.className = 'work-run-pending-guides';
|
|
10840
|
-
runBody.appendChild(pendingStack);
|
|
10841
|
-
}
|
|
10842
|
-
if (!element || !pendingStack.contains(element)) {
|
|
10843
|
-
var holder = document.createElement('div');
|
|
10844
|
-
holder.innerHTML = renderWorkRunGuideMessage(Object.assign({}, item, { status: 'accepted' }));
|
|
10845
|
-
element = holder.firstElementChild;
|
|
10846
|
-
if (element) pendingStack.appendChild(element);
|
|
10847
|
-
}
|
|
10848
|
-
if (element) applyGuideMessageMeta(element, item);
|
|
10849
|
-
return;
|
|
10850
|
-
}
|
|
10851
|
-
}
|
|
10852
10920
|
if (element) applyGuideMessageMeta(element, item);
|
|
10853
10921
|
else addMsg('user', item.content || 'Guide', 'guide', item.model || state.model, undefined, {
|
|
10854
10922
|
clientMessageId: item.clientMessageId,
|
|
@@ -10915,6 +10983,7 @@ function setConversationRuntimeState(target, status, runId, extra) {
|
|
|
10915
10983
|
if (!state.runningConversations) state.runningConversations = {};
|
|
10916
10984
|
if (['running', 'stopping', 'force_restarting'].indexOf(next.status) >= 0) state.runningConversations[key] = next;
|
|
10917
10985
|
else delete state.runningConversations[key];
|
|
10986
|
+
if (typeof ensureRuntimeReconcileWatchdog === 'function') ensureRuntimeReconcileWatchdog();
|
|
10918
10987
|
var branchIds = conversationBranchIdsForTarget(target);
|
|
10919
10988
|
var runs = workRunsForBranch(target, branchIds.runtime || branchIds.viewed);
|
|
10920
10989
|
var matchingRun = next.runId && runs.find(function(item) { return String(item.runId || '') === next.runId; });
|
|
@@ -11258,13 +11327,15 @@ function workRunBeforeElement(run) {
|
|
|
11258
11327
|
|
|
11259
11328
|
function workRunsForTarget(target) {
|
|
11260
11329
|
target = target || currentConversationTarget();
|
|
11261
|
-
|
|
11262
|
-
|
|
11263
|
-
|
|
11330
|
+
// 读取「当前浏览的分支节点」自己的 Build 列表。`workRunsByTarget` 只是
|
|
11331
|
+
// 兼容性的展示镜像,按会话共享会让另一个分页的运行结果覆盖本页(新发送的
|
|
11332
|
+
// 消息看起来消失),因此渲染路径一律走按分支节点存储的账本。
|
|
11333
|
+
var viewedBranchId = viewedBranchNodeIdFor(target);
|
|
11334
|
+
if (!viewedBranchId) {
|
|
11264
11335
|
var branchIds = conversationBranchIdsForTarget(target);
|
|
11265
|
-
|
|
11336
|
+
viewedBranchId = branchIds.viewed;
|
|
11266
11337
|
}
|
|
11267
|
-
return
|
|
11338
|
+
return workRunsForBranch(target, viewedBranchId);
|
|
11268
11339
|
}
|
|
11269
11340
|
|
|
11270
11341
|
function syncWorkRunsSnapshot(runs, target, branchId) {
|
|
@@ -11642,9 +11713,12 @@ function renderWorkRunGuideMessage(event) {
|
|
|
11642
11713
|
var guideId = String(guide.guideId || event.guideId || '');
|
|
11643
11714
|
var messageIndex = Number(state.guideMessageIndexByClientId && state.guideMessageIndexByClientId[clientMessageId]);
|
|
11644
11715
|
var indexed = Number.isFinite(messageIndex) && messageIndex >= 0;
|
|
11716
|
+
// dev-0.6.4: 时间线里的 Guide 记录不再提供编辑入口。Guide 是当前 Build 的
|
|
11717
|
+
// 历史干预记录,改写它既不会重放 Build,也会让分支归属变得含糊;这里只保留
|
|
11718
|
+
// 复制动作,消息索引属性仍然写回,供锚点/诊断使用。
|
|
11645
11719
|
return '<div class="chat-msg user work-run-guide-message" data-client-message-id="' + escAttr(clientMessageId) + '" data-guide-id="' + escAttr(guideId) + '" data-run-id="' + escAttr(String(guide.runId || event.runId || '')) + '"' + (indexed ? ' data-message-index="' + messageIndex + '"' : '') + '>' +
|
|
11646
11720
|
'<div class="meta"><span class="msg-role">' + esc(t('message.user')) + '</span>' + (time ? '<span> | ' + esc(time) + '</span>' : '') +
|
|
11647
|
-
'<span class="guide-receipt-status ' + escAttr(status) + '">Guide · ' + esc(guideUiStatusLabel(status)) + '</span>' + messageActionsHtml('user', content,
|
|
11721
|
+
'<span class="guide-receipt-status ' + escAttr(status) + '">Guide · ' + esc(guideUiStatusLabel(status)) + '</span>' + messageActionsHtml('user', content, -1) + '</div>' +
|
|
11648
11722
|
'<div class="msg-body">' + renderMessageContent(content) + '</div></div>';
|
|
11649
11723
|
}
|
|
11650
11724
|
|
|
@@ -11929,7 +12003,10 @@ function updateConversationWorkRunElement(run, element) {
|
|
|
11929
12003
|
// 重建/增量更新前后保持聊天视窗滚动位置:贴底时跟随最新,未贴底时
|
|
11930
12004
|
// 精确恢复 scrollTop,避免内容高度变化把视窗“强制拖向最新”。
|
|
11931
12005
|
var chatEl = els['chat-area'];
|
|
11932
|
-
|
|
12006
|
+
// The passive scroll listener keeps this sticky-bottom bit current. Re-read
|
|
12007
|
+
// scrollHeight here would force layout immediately before the DOM patch on
|
|
12008
|
+
// every incremental update, then the restoration below would force it again.
|
|
12009
|
+
var wasAtBottom = !!(chatEl && _chatShouldAutoScroll);
|
|
11933
12010
|
var scrollTopBefore = chatEl ? chatEl.scrollTop : 0;
|
|
11934
12011
|
var activityOpenStates = {};
|
|
11935
12012
|
var priorActivities = element.querySelectorAll ? element.querySelectorAll('details.conversation-work-activity[data-activity-key], details[data-work-detail-key]') : [];
|
|
@@ -12173,6 +12250,12 @@ function applyAgentWorkEventToRun(event) {
|
|
|
12173
12250
|
if (run.userToggled !== true) run.expanded = run.status === 'error';
|
|
12174
12251
|
}
|
|
12175
12252
|
}
|
|
12253
|
+
// dev-0.6.4 hotfix: 排队出队的回合结束时,把权威转录(助手回复、最终用户行)
|
|
12254
|
+
// 刷进对话区。正常发送路径有自己的回执刷新;这里只针对后台出队的运行,
|
|
12255
|
+
// 且由 scheduleDrainedTurnTranscriptRefresh 做去抖与“无发送在飞”保护。
|
|
12256
|
+
if (['done', 'error', 'interrupted', 'force_interrupted'].indexOf(type) >= 0) {
|
|
12257
|
+
scheduleDrainedTurnTranscriptRefresh(target, []);
|
|
12258
|
+
}
|
|
12176
12259
|
if (isActiveConversationTarget(target) && (!branchIds.viewed || !eventBranchId || branchIds.viewed === eventBranchId)) {
|
|
12177
12260
|
state.workRunsByTarget[runtimeKeyFor(target.workspaceId, target.conversationId)] = runs;
|
|
12178
12261
|
var existingElement = findWorkRunElement(run);
|
|
@@ -12455,7 +12538,8 @@ function renderPersistedToolMessage(message) {
|
|
|
12455
12538
|
|
|
12456
12539
|
function conversationMessageCache(target) {
|
|
12457
12540
|
var scopedTarget = target || currentConversationTarget();
|
|
12458
|
-
|
|
12541
|
+
// 分支级缓存:分页切换时另一个页面的消息快照绝不能覆盖当前页。
|
|
12542
|
+
var key = branchConversationViewKey(scopedTarget);
|
|
12459
12543
|
if (!state.conversationMessagesByTarget) state.conversationMessagesByTarget = {};
|
|
12460
12544
|
return Array.isArray(state.conversationMessagesByTarget[key])
|
|
12461
12545
|
? state.conversationMessagesByTarget[key].map(function(message) { return Object.assign({}, message); })
|
|
@@ -12464,10 +12548,22 @@ function conversationMessageCache(target) {
|
|
|
12464
12548
|
|
|
12465
12549
|
function cacheConversationMessages(messages, target) {
|
|
12466
12550
|
var scopedTarget = target || currentConversationTarget();
|
|
12467
|
-
var key =
|
|
12551
|
+
var key = branchConversationViewKey(scopedTarget);
|
|
12468
12552
|
var cloned = Array.isArray(messages) ? messages.map(function(message) { return Object.assign({}, message); }) : [];
|
|
12469
12553
|
if (!state.conversationMessagesByTarget) state.conversationMessagesByTarget = {};
|
|
12554
|
+
// Disk remains authoritative. Limit retained branch snapshots, not history.
|
|
12555
|
+
delete state.conversationMessagesByTarget[key];
|
|
12470
12556
|
state.conversationMessagesByTarget[key] = cloned;
|
|
12557
|
+
var cacheKeys = Object.keys(state.conversationMessagesByTarget);
|
|
12558
|
+
var retainedChars = 0;
|
|
12559
|
+
for (var i = cacheKeys.length - 1; i >= 0; i--) {
|
|
12560
|
+
var cachedKey = cacheKeys[i];
|
|
12561
|
+
var rows = state.conversationMessagesByTarget[cachedKey];
|
|
12562
|
+
var chars = rows.reduce(function(sum, row) { return sum + String(row.content || '').length; }, 0);
|
|
12563
|
+
if (cacheKeys.length - i > 6 || retainedChars + chars > 4 * 1024 * 1024) {
|
|
12564
|
+
delete state.conversationMessagesByTarget[cachedKey];
|
|
12565
|
+
} else retainedChars += chars;
|
|
12566
|
+
}
|
|
12471
12567
|
return cloned.map(function(message) { return Object.assign({}, message); });
|
|
12472
12568
|
}
|
|
12473
12569
|
|
|
@@ -12558,6 +12654,9 @@ function renderChatMessagesUnsafe(messages, target) {
|
|
|
12558
12654
|
var chatWasAtBottom = shouldAutoScroll(els['chat-area']);
|
|
12559
12655
|
els['chat-area'].innerHTML = '';
|
|
12560
12656
|
if (chatWasAtBottom) _chatShouldAutoScroll = true;
|
|
12657
|
+
// This target now owns the transcript on screen; an async snapshot for it may
|
|
12658
|
+
// repaint its held option block, and a snapshot for any other target may not.
|
|
12659
|
+
state.renderedChatTargetKey = runtimeKeyFor(renderTarget.workspaceId, renderTarget.conversationId);
|
|
12561
12660
|
var renderConversationId = renderTarget.conversationId;
|
|
12562
12661
|
var guideMessages = guideMessagesForTarget(renderTarget);
|
|
12563
12662
|
var persistedGuideIds = {};
|
|
@@ -12574,6 +12673,10 @@ function renderChatMessagesUnsafe(messages, target) {
|
|
|
12574
12673
|
});
|
|
12575
12674
|
var persistedRunsById = {};
|
|
12576
12675
|
var persistedRunIndexes = {};
|
|
12676
|
+
// dev-0.6.6: the transcript loop below asks "is this message owned by a
|
|
12677
|
+
// persisted work run?" once per message. A linear scan there is O(rounds²)
|
|
12678
|
+
// for a long conversation, so resolve it through a lookup table instead.
|
|
12679
|
+
var persistedRunIdSet = {};
|
|
12577
12680
|
var renderedRunIds = {};
|
|
12578
12681
|
var recoveredAssistantRunIds = {};
|
|
12579
12682
|
var messageRunRoles = {};
|
|
@@ -12583,6 +12686,7 @@ function renderChatMessagesUnsafe(messages, target) {
|
|
|
12583
12686
|
if (runId) {
|
|
12584
12687
|
persistedRunsById[runId] = run;
|
|
12585
12688
|
persistedRunIndexes[runId] = runIndex;
|
|
12689
|
+
persistedRunIdSet[runId] = true;
|
|
12586
12690
|
}
|
|
12587
12691
|
});
|
|
12588
12692
|
normalizedMessages.forEach(function(message, messageIndex) {
|
|
@@ -12643,7 +12747,7 @@ function renderChatMessagesUnsafe(messages, target) {
|
|
|
12643
12747
|
finishToolBatch(renderConversationId);
|
|
12644
12748
|
var clientMessageId = String(m.clientMessageId || '');
|
|
12645
12749
|
if (clientMessageId) persistedGuideIds[clientMessageId] = true;
|
|
12646
|
-
if (clientMessageId && m.runId &&
|
|
12750
|
+
if (clientMessageId && m.runId && persistedRunIdSet[String(m.runId || '')]) {
|
|
12647
12751
|
delete guideMessages[clientMessageId];
|
|
12648
12752
|
continue;
|
|
12649
12753
|
}
|
|
@@ -12662,7 +12766,9 @@ function renderChatMessagesUnsafe(messages, target) {
|
|
|
12662
12766
|
guideId: m.guideId || '',
|
|
12663
12767
|
attachments: m.attachments || [],
|
|
12664
12768
|
target: renderTarget,
|
|
12665
|
-
timestamp: m.timestamp || ''
|
|
12769
|
+
timestamp: m.timestamp || '',
|
|
12770
|
+
skipAutoScroll: true,
|
|
12771
|
+
skipEntryAnimation: true
|
|
12666
12772
|
});
|
|
12667
12773
|
if (associatedRun && String(m.role || '') === 'user') renderOwnedWorkRun(associatedRun, false);
|
|
12668
12774
|
if (clientMessageId) delete guideMessages[clientMessageId];
|
|
@@ -12675,7 +12781,6 @@ function renderChatMessagesUnsafe(messages, target) {
|
|
|
12675
12781
|
});
|
|
12676
12782
|
if (pendingWorkReview) addWorkReview(pendingWorkReview.diffs || pendingWorkReview, pendingWorkReview.runId || '');
|
|
12677
12783
|
renderConversationBranchPagers();
|
|
12678
|
-
renderAutoRouteRatingControls();
|
|
12679
12784
|
if (typeof renderPendingOptionsInChat === 'function') renderPendingOptionsInChat(state.pendingOptions || []);
|
|
12680
12785
|
}
|
|
12681
12786
|
|
|
@@ -12684,6 +12789,153 @@ function renderChatMessagesUnsafe(messages, target) {
|
|
|
12684
12789
|
// the preceding window via conversation:loadEarlier and prepends it, so a
|
|
12685
12790
|
// long-running conversation can be browsed without ever transferring the whole
|
|
12686
12791
|
// transcript in one snapshot.
|
|
12792
|
+
function canPrependHistoryMessages(messages, existingMessages, target, liveArea, previousStart, pageStart) {
|
|
12793
|
+
if (!Array.isArray(messages) || !messages.length || !Array.isArray(existingMessages) || !existingMessages.length || !liveArea) return false;
|
|
12794
|
+
if (!Number.isFinite(Number(previousStart)) || !Number.isFinite(Number(pageStart))
|
|
12795
|
+
|| Number(pageStart) < 0 || Number(pageStart) + messages.length !== Number(previousStart)) return false;
|
|
12796
|
+
var targetKey = runtimeKeyFor(target.workspaceId, target.conversationId);
|
|
12797
|
+
if (state.renderedChatTargetKey !== targetKey) return false;
|
|
12798
|
+
var persistedRuns = workRunsForTarget(target).slice();
|
|
12799
|
+
var persistedRunIds = Object.create(null);
|
|
12800
|
+
var stableRunStatuses = ['completed', 'interrupted', 'force_interrupted', 'error'];
|
|
12801
|
+
for (var runIndex = 0; runIndex < persistedRuns.length; runIndex++) {
|
|
12802
|
+
var persistedRun = persistedRuns[runIndex] || {};
|
|
12803
|
+
var persistedRunId = String(persistedRun.runId || '');
|
|
12804
|
+
if (!persistedRunId || stableRunStatuses.indexOf(String(persistedRun.status || '')) < 0) return false;
|
|
12805
|
+
persistedRunIds[persistedRunId] = true;
|
|
12806
|
+
}
|
|
12807
|
+
if ((state.conversationBranchGroups || []).length || (state.conversationBranches || []).length > 1
|
|
12808
|
+
|| state.activeConversationBranchId || (state.viewedConversationBranchNodePath || []).length) return false;
|
|
12809
|
+
var workUi = state.agentWorkUiByConversation && state.agentWorkUiByConversation[targetKey];
|
|
12810
|
+
if (workUi && (workUi.pendingWorkReview || (workUi.workReviews || []).length)) return false;
|
|
12811
|
+
if (pendingOptionsForTarget(target).length) return false;
|
|
12812
|
+
|
|
12813
|
+
function isPlainHistoryMessage(message, allowPersistedRun) {
|
|
12814
|
+
if (!message || (message.role !== 'user' && message.role !== 'assistant')) return false;
|
|
12815
|
+
var messageMode = String(message.mode || '').trim().toLowerCase();
|
|
12816
|
+
if (messageMode && messageMode !== 'chat' && messageMode !== 'build') return false;
|
|
12817
|
+
if (message.clientMessageId || message.guideId || isHiddenWorkflowMessage(message)) return false;
|
|
12818
|
+
if (Array.isArray(message.attachments) && message.attachments.length) return false;
|
|
12819
|
+
var runId = String(message.runId || '');
|
|
12820
|
+
if (runId && persistedRunIds[runId] && !allowPersistedRun) return false;
|
|
12821
|
+
return true;
|
|
12822
|
+
}
|
|
12823
|
+
if (!messages.every(function(message) { return isPlainHistoryMessage(message, false); })
|
|
12824
|
+
|| !existingMessages.every(function(message) { return isPlainHistoryMessage(message, true); })) return false;
|
|
12825
|
+
|
|
12826
|
+
var contentNodes = Array.prototype.filter.call(liveArea.children, function(node) {
|
|
12827
|
+
return !node.classList.contains('conversation-load-earlier');
|
|
12828
|
+
});
|
|
12829
|
+
var messageNodes = contentNodes.filter(function(node) {
|
|
12830
|
+
return node.classList.contains('chat-msg')
|
|
12831
|
+
&& !node.classList.contains('work-run-message')
|
|
12832
|
+
&& node.hasAttribute('data-message-index');
|
|
12833
|
+
});
|
|
12834
|
+
if (messageNodes.length !== existingMessages.length) return false;
|
|
12835
|
+
var auxiliaryNodes = contentNodes.filter(function(node) {
|
|
12836
|
+
return !node.classList.contains('chat-msg')
|
|
12837
|
+
|| node.classList.contains('work-run-message')
|
|
12838
|
+
|| !node.hasAttribute('data-message-index');
|
|
12839
|
+
});
|
|
12840
|
+
if (!auxiliaryNodes.every(function(node) {
|
|
12841
|
+
if (!node.classList.contains('chat-msg')) return false;
|
|
12842
|
+
if (node.classList.contains('work-run-message')) {
|
|
12843
|
+
var runElement = node.querySelector('.conversation-work-run[data-run-id]');
|
|
12844
|
+
return !!runElement && !!persistedRunIds[String(runElement.getAttribute('data-run-id') || '')];
|
|
12845
|
+
}
|
|
12846
|
+
return node.classList.contains('run-final-response')
|
|
12847
|
+
&& !!persistedRunIds[String(node.getAttribute('data-run-id') || '')];
|
|
12848
|
+
})) return false;
|
|
12849
|
+
var assistantRunIds = Object.create(null);
|
|
12850
|
+
var orderedMessages = messages.concat(existingMessages);
|
|
12851
|
+
for (var i = 0; i < orderedMessages.length; i++) {
|
|
12852
|
+
var message = orderedMessages[i];
|
|
12853
|
+
var runId = String(message && message.runId || '');
|
|
12854
|
+
if (message && message.role === 'assistant' && runId) {
|
|
12855
|
+
if (assistantRunIds[runId]) return false;
|
|
12856
|
+
assistantRunIds[runId] = true;
|
|
12857
|
+
}
|
|
12858
|
+
}
|
|
12859
|
+
return messageNodes.every(function(node, index) {
|
|
12860
|
+
var message = existingMessages[index] || {};
|
|
12861
|
+
var expectedId = String(message.messageId || '');
|
|
12862
|
+
var nodeTarget = node._newmarkMessageTarget;
|
|
12863
|
+
return node.classList.contains('chat-msg')
|
|
12864
|
+
&& node.classList.contains(String(message.role || ''))
|
|
12865
|
+
&& Number(node.getAttribute('data-message-index')) === index
|
|
12866
|
+
&& String(node.getAttribute('data-message-id') || '') === expectedId
|
|
12867
|
+
&& String(node.getAttribute('data-run-id') || '') === String(message.runId || '')
|
|
12868
|
+
&& String(node._newmarkMessageMode || '').trim().toLowerCase() === String(message.mode || '').trim().toLowerCase()
|
|
12869
|
+
&& String(node._newmarkMessageModel || '').trim() === String(message.model || '').trim()
|
|
12870
|
+
&& String(node._newmarkMessageText || '') === String(message.content || '')
|
|
12871
|
+
&& !!nodeTarget && nodeTarget.workspaceId === String(target.workspaceId || '')
|
|
12872
|
+
&& nodeTarget.conversationId === String(target.conversationId || 'default');
|
|
12873
|
+
});
|
|
12874
|
+
}
|
|
12875
|
+
|
|
12876
|
+
function prependHistoryMessagesIncrementally(messages, existingMessages, target, previousStart, pageStart) {
|
|
12877
|
+
var liveArea = els['chat-area'];
|
|
12878
|
+
if (!canPrependHistoryMessages(messages, existingMessages, target, liveArea, previousStart, pageStart)) return false;
|
|
12879
|
+
|
|
12880
|
+
var contentNodes = Array.prototype.filter.call(liveArea.children, function(node) {
|
|
12881
|
+
return !node.classList.contains('conversation-load-earlier');
|
|
12882
|
+
});
|
|
12883
|
+
var anchor = contentNodes[0] || null;
|
|
12884
|
+
var anchorTop = anchor ? anchor.getBoundingClientRect().top : 0;
|
|
12885
|
+
var wasAtBottom = shouldAutoScroll(liveArea);
|
|
12886
|
+
var previousAutoScroll = _chatShouldAutoScroll;
|
|
12887
|
+
var stagingArea = document.createElement('div');
|
|
12888
|
+
var priorArea = els['chat-area'];
|
|
12889
|
+
var renderFailed = false;
|
|
12890
|
+
try {
|
|
12891
|
+
els['chat-area'] = stagingArea;
|
|
12892
|
+
messages.forEach(function(message, index) {
|
|
12893
|
+
addMsg(message.role, message.content || '', message.mode || '', message.model || '', index, {
|
|
12894
|
+
runId: message.runId || '',
|
|
12895
|
+
messageId: message.messageId || '',
|
|
12896
|
+
target: target,
|
|
12897
|
+
timestamp: message.timestamp || '',
|
|
12898
|
+
skipAutoScroll: true,
|
|
12899
|
+
skipEntryAnimation: true
|
|
12900
|
+
});
|
|
12901
|
+
});
|
|
12902
|
+
} catch (error) {
|
|
12903
|
+
renderFailed = true;
|
|
12904
|
+
console.warn('[ui_history_prepend_failed]', error);
|
|
12905
|
+
} finally {
|
|
12906
|
+
els['chat-area'] = priorArea;
|
|
12907
|
+
_chatShouldAutoScroll = previousAutoScroll;
|
|
12908
|
+
}
|
|
12909
|
+
if (renderFailed || stagingArea.childNodes.length !== messages.length) return false;
|
|
12910
|
+
|
|
12911
|
+
var shiftedBy = messages.length;
|
|
12912
|
+
contentNodes.forEach(function(node) {
|
|
12913
|
+
if (!node.classList.contains('chat-msg') || !node.hasAttribute('data-message-index')) return;
|
|
12914
|
+
var nextIndex = Number(node.getAttribute('data-message-index')) + shiftedBy;
|
|
12915
|
+
node.setAttribute('data-message-index', String(nextIndex));
|
|
12916
|
+
var editButton = node._newmarkEditUserButton;
|
|
12917
|
+
if (editButton) editButton.setAttribute('onclick', 'window.editUserMessage(this,' + nextIndex + ')');
|
|
12918
|
+
});
|
|
12919
|
+
var fragment = document.createDocumentFragment();
|
|
12920
|
+
while (stagingArea.firstChild) fragment.appendChild(stagingArea.firstChild);
|
|
12921
|
+
liveArea.insertBefore(fragment, anchor);
|
|
12922
|
+
|
|
12923
|
+
var runAnchorIndexes = workRunAnchorIndexStore(target);
|
|
12924
|
+
Object.keys(runAnchorIndexes).forEach(function(runId) {
|
|
12925
|
+
var anchorIndex = Number(runAnchorIndexes[runId]);
|
|
12926
|
+
if (Number.isFinite(anchorIndex) && anchorIndex >= 0) runAnchorIndexes[runId] = anchorIndex + shiftedBy;
|
|
12927
|
+
});
|
|
12928
|
+
|
|
12929
|
+
var targetKey = runtimeKeyFor(target.workspaceId, target.conversationId);
|
|
12930
|
+
state.renderedChatMessages = cacheConversationMessages(messages.concat(existingMessages), target);
|
|
12931
|
+
state.renderedChatTargetKey = targetKey;
|
|
12932
|
+
_chatShouldAutoScroll = wasAtBottom;
|
|
12933
|
+
renderLoadEarlierButton();
|
|
12934
|
+
if (wasAtBottom) liveArea.scrollTop = liveArea.scrollHeight;
|
|
12935
|
+
else if (anchor) liveArea.scrollTop += anchor.getBoundingClientRect().top - anchorTop;
|
|
12936
|
+
return true;
|
|
12937
|
+
}
|
|
12938
|
+
|
|
12687
12939
|
function renderLoadEarlierButton() {
|
|
12688
12940
|
if (!els['chat-area']) return;
|
|
12689
12941
|
Array.prototype.forEach.call(els['chat-area'].querySelectorAll('.conversation-load-earlier'), function(button) { button.remove(); });
|
|
@@ -12714,9 +12966,12 @@ function renderLoadEarlierButton() {
|
|
|
12714
12966
|
var mergedMessages = result.chatMessages.concat(newer);
|
|
12715
12967
|
state.conversationLoadedBefore[loadKey] = Math.max(0, Number(result.windowStart) || 0);
|
|
12716
12968
|
var chatWasAtBottom = shouldAutoScroll(els['chat-area']);
|
|
12717
|
-
|
|
12718
|
-
if (!
|
|
12719
|
-
|
|
12969
|
+
var prepended = prependHistoryMessagesIncrementally(result.chatMessages, newer, loadTarget, loadBefore, result.windowStart);
|
|
12970
|
+
if (!prepended) {
|
|
12971
|
+
renderChatMessages(mergedMessages, loadTarget);
|
|
12972
|
+
if (!chatWasAtBottom && els['chat-area']) els['chat-area'].scrollTop = 0;
|
|
12973
|
+
renderLoadEarlierButton();
|
|
12974
|
+
}
|
|
12720
12975
|
}).catch(function() { button.disabled = false; });
|
|
12721
12976
|
});
|
|
12722
12977
|
els['chat-area'].insertBefore(button, els['chat-area'].firstChild);
|
|
@@ -12745,9 +13000,20 @@ function renderConversationBranchPagers() {
|
|
|
12745
13000
|
if (matched) anchor = candidate;
|
|
12746
13001
|
return matched;
|
|
12747
13002
|
});
|
|
12748
|
-
if (!anchor
|
|
13003
|
+
if (!anchor) {
|
|
13004
|
+
// dev-0.6.4: 分页锚点回退。Build 开头的用户消息可能以内联 Build/Guide
|
|
13005
|
+
// 卡片渲染,没有写回 data-message-id;旧逻辑此时直接放弃,导致「从 build
|
|
13006
|
+
// 开头编辑」创建的分页看不到分页条。现在按渲染索引,再按锚点文本回退。
|
|
12749
13007
|
var sourceMessageIndex = Math.max(0, Number(branchGroup.sourceMessageIndex === undefined ? activeBranch.sourceMessageIndex : branchGroup.sourceMessageIndex));
|
|
12750
13008
|
anchor = els['chat-area'].querySelector('.chat-msg.user[data-message-index="' + sourceMessageIndex + '"]');
|
|
13009
|
+
if (!anchor && String(activeBranch.sourceText || '').trim()) {
|
|
13010
|
+
var wantedAnchorText = String(activeBranch.sourceText || '').trim();
|
|
13011
|
+
Array.prototype.some.call(els['chat-area'].querySelectorAll('.chat-msg.user'), function(candidate) {
|
|
13012
|
+
var candidateText = String(candidate && candidate._newmarkMessageText || '').trim();
|
|
13013
|
+
if (candidateText && candidateText === wantedAnchorText) { anchor = candidate; return true; }
|
|
13014
|
+
return false;
|
|
13015
|
+
});
|
|
13016
|
+
}
|
|
12751
13017
|
}
|
|
12752
13018
|
if (anchor) {
|
|
12753
13019
|
var pager = document.createElement('div');
|
|
@@ -12852,6 +13118,8 @@ function scheduleStreamingWorkflowTextFlush(ui, eventConversationId, eventWorksp
|
|
|
12852
13118
|
requestAnimationFrame(function() {
|
|
12853
13119
|
var current = conversationWorkUiState(eventConversationId, eventWorkspaceId);
|
|
12854
13120
|
current._streamFlushPending = false;
|
|
13121
|
+
// A queued frame can outlive navigation or its UI state object.
|
|
13122
|
+
if (current !== ui || !isActiveConversationTarget({ workspaceId: eventWorkspaceId, conversationId: eventConversationId })) return;
|
|
12855
13123
|
applyStreamingWorkflowText(current, eventConversationId, current._streamMode || state.mode, current._streamModel || state.model);
|
|
12856
13124
|
});
|
|
12857
13125
|
}
|
|
@@ -13288,6 +13556,9 @@ function hydrateConversationBranchState(snapshot) {
|
|
|
13288
13556
|
if (Object.prototype.hasOwnProperty.call(s, 'branchGroupId')) state.activeConversationBranchGroupId = String(s.branchGroupId || '');
|
|
13289
13557
|
if (Object.prototype.hasOwnProperty.call(s, 'viewedBranchNodePath')) state.viewedConversationBranchNodePath = Array.isArray(s.viewedBranchNodePath) ? s.viewedBranchNodePath.map(String) : [];
|
|
13290
13558
|
if (Object.prototype.hasOwnProperty.call(s, 'runtimeBranchNodePath')) state.runtimeConversationBranchNodePath = Array.isArray(s.runtimeBranchNodePath) ? s.runtimeBranchNodePath.map(String) : [];
|
|
13559
|
+
// 分支级缓存必须知道「当前浏览的是哪个节点」,否则换页后另一页的快照会覆盖本页。
|
|
13560
|
+
var viewedLeaf = String((state.viewedConversationBranchNodePath || []).slice(-1)[0] || state.activeConversationBranchId || '');
|
|
13561
|
+
rememberViewedBranchNodeId(currentConversationTarget(s.conversationId || activeConversationId()), viewedLeaf);
|
|
13291
13562
|
rememberRuntimeConversationBranchPath(currentConversationTarget(s.conversationId || activeConversationId()));
|
|
13292
13563
|
}
|
|
13293
13564
|
|
|
@@ -13320,6 +13591,15 @@ function applyConversationModelSelection(snapshot) {
|
|
|
13320
13591
|
}
|
|
13321
13592
|
}
|
|
13322
13593
|
|
|
13594
|
+
/**
|
|
13595
|
+
* dev-0.6.6 长程内存回收:渲染端启动时清掉主进程记住的修订号(页面重载后内存里
|
|
13596
|
+
* 的转录已不存在,必须重新取全量)。之后每次真正画完一份转录都会 ack。
|
|
13597
|
+
*/
|
|
13598
|
+
(function clearTranscriptRevisionsOnBoot() {
|
|
13599
|
+
if (!api || !api.ackTranscript) return;
|
|
13600
|
+
try { api.ackTranscript(currentConversationTarget(), ''); } catch (e) {}
|
|
13601
|
+
})();
|
|
13602
|
+
|
|
13323
13603
|
function applyConversationSnapshot(s, requestedConversationId, options) {
|
|
13324
13604
|
if (!s) return;
|
|
13325
13605
|
if (s.computerUse) {
|
|
@@ -13339,23 +13619,34 @@ function applyConversationSnapshot(s, requestedConversationId, options) {
|
|
|
13339
13619
|
var preserveReadableHistory = !!(options && options.preserveReadableHistory
|
|
13340
13620
|
&& !snapshotHasReadableConversationHistory(s)
|
|
13341
13621
|
&& (!options.allowEmptyHistory || conversationHasReadableHistory(snapshotTarget)));
|
|
13622
|
+
// dev-0.6.6 长程内存回收:宿主可能只回传"转录未变"的瘦快照(仅当本渲染端此前
|
|
13623
|
+
// 通过 ackTranscript 声明过同一修订号)。瘦快照只对"当前正画着这条对话"有效;
|
|
13624
|
+
// 其它情况必须取一次全量,绝不能用别的对话的画面顶替。
|
|
13625
|
+
var transcriptUnchanged = !!(s && s.transcriptUnchanged === true);
|
|
13342
13626
|
if (s && s.runtimeKey) registerRuntimeKey(snapshotTarget, s.runtimeKey);
|
|
13627
|
+
if (transcriptUnchanged && !preserveReadableHistory) {
|
|
13628
|
+
var unchangedKey = runtimeKeyFor(snapshotTarget.workspaceId, snapshotTarget.conversationId);
|
|
13629
|
+
if (String(state.renderedChatTargetKey || '') !== unchangedKey && api.getState) {
|
|
13630
|
+
api.getState(snapshotTarget, { transcript: 'force' }).then(function(full) {
|
|
13631
|
+
if (full && isActiveConversationTarget(snapshotTarget)) applyConversationSnapshot(full, snapshotTarget.conversationId, options);
|
|
13632
|
+
}).catch(function() {});
|
|
13633
|
+
}
|
|
13634
|
+
}
|
|
13343
13635
|
hydrateConversationDraftSnapshot(s, snapshotTarget);
|
|
13344
|
-
hydrateConversationBranchState(s);
|
|
13636
|
+
if (!transcriptUnchanged) hydrateConversationBranchState(s);
|
|
13345
13637
|
var modelRevisionMatches = !options || options.modelRevision === undefined
|
|
13346
13638
|
|| Number(state.modelSelectionRevision || 0) === Number(options.modelRevision || 0);
|
|
13347
13639
|
if (modelRevisionMatches && isActiveConversationTarget(snapshotTarget)) {
|
|
13348
13640
|
applyConversationModelSelection(s);
|
|
13349
13641
|
}
|
|
13350
13642
|
rebindQueueToRuntimeBranch(snapshotTarget);
|
|
13351
|
-
if (s && Array.isArray(s.workRuns) && !preserveReadableHistory) {
|
|
13643
|
+
if (s && Array.isArray(s.workRuns) && !preserveReadableHistory && !transcriptUnchanged) {
|
|
13352
13644
|
var viewedBranchIdForSync = Array.isArray(s.viewedBranchNodePath) && s.viewedBranchNodePath.length
|
|
13353
13645
|
? String(s.viewedBranchNodePath[s.viewedBranchNodePath.length - 1])
|
|
13354
13646
|
: '';
|
|
13355
13647
|
syncWorkRunsSnapshot(s.workRuns, snapshotTarget, viewedBranchIdForSync || undefined);
|
|
13356
13648
|
}
|
|
13357
|
-
|
|
13358
|
-
if (s && Number.isFinite(Number(s.totalMessages))) {
|
|
13649
|
+
if (s && Number.isFinite(Number(s.totalMessages)) && Array.isArray(s.chatMessages)) {
|
|
13359
13650
|
if (!state.conversationLoadedBefore) state.conversationLoadedBefore = {};
|
|
13360
13651
|
var windowKey = runtimeKeyFor(snapshotTarget.workspaceId, s.conversationId || requestedConversationId || 'default');
|
|
13361
13652
|
state.conversationLoadedBefore[windowKey] = Math.max(0, Number(s.totalMessages) - (Array.isArray(s.chatMessages) ? s.chatMessages.length : 0));
|
|
@@ -13367,9 +13658,16 @@ function applyConversationSnapshot(s, requestedConversationId, options) {
|
|
|
13367
13658
|
window.renderContextWindow();
|
|
13368
13659
|
}
|
|
13369
13660
|
if (state.rightTab === 'status') window.renderRightStatusPanel();
|
|
13370
|
-
if (s && Array.isArray(s.chatMessages) && !preserveReadableHistory) {
|
|
13661
|
+
if (s && Array.isArray(s.chatMessages) && !preserveReadableHistory && !transcriptUnchanged) {
|
|
13371
13662
|
renderChatMessages(s.chatMessages, snapshotTarget);
|
|
13372
13663
|
renderLoadEarlierButton();
|
|
13664
|
+
// 声明"我已画好该修订",宿主下一次轮询才会回瘦快照。只在真正画到屏幕上
|
|
13665
|
+
// (或该目标就是当前渲染目标)时声明。
|
|
13666
|
+
if (typeof s.transcriptRevision === 'string' && s.transcriptRevision
|
|
13667
|
+
&& String(state.renderedChatTargetKey || '') === runtimeKeyFor(snapshotTarget.workspaceId, snapshotTarget.conversationId)
|
|
13668
|
+
&& api.ackTranscript) {
|
|
13669
|
+
try { api.ackTranscript(snapshotTarget, s.transcriptRevision); } catch (e) {}
|
|
13670
|
+
}
|
|
13373
13671
|
}
|
|
13374
13672
|
if (s && s.conversationId) {
|
|
13375
13673
|
state.activeBackendConversationId = s.conversationId;
|
|
@@ -13381,6 +13679,17 @@ function applyConversationSnapshot(s, requestedConversationId, options) {
|
|
|
13381
13679
|
if (window.syncNextQueueFromBackend) window.syncNextQueueFromBackend(state.backendQueue, snapshotTarget);
|
|
13382
13680
|
}
|
|
13383
13681
|
syncQueueItemsFromSnapshot(s, snapshotTarget);
|
|
13682
|
+
// The transcript above is painted from state.pendingOptions, but the
|
|
13683
|
+
// per-target option store is only refreshed by the line just executed.
|
|
13684
|
+
// A conversation that asked its question while another conversation was
|
|
13685
|
+
// focused therefore has to be repainted here, otherwise the held question
|
|
13686
|
+
// never appears when the user returns to the conversation that asked it.
|
|
13687
|
+
if (isActiveConversationTarget(snapshotTarget)
|
|
13688
|
+
&& state.renderedChatTargetKey === runtimeKeyFor(snapshotTarget.workspaceId, snapshotTarget.conversationId)) {
|
|
13689
|
+
// Paint from the same projection renderChatMessages uses, so an already
|
|
13690
|
+
// answered question (cleared optimistically) is never resurrected.
|
|
13691
|
+
renderPendingOptionsInChat(state.pendingOptions || []);
|
|
13692
|
+
}
|
|
13384
13693
|
if (s && Array.isArray(s.workEvents)) {
|
|
13385
13694
|
var eventConversationId = runtimeKeyFor(snapshotTarget.workspaceId, s.conversationId || requestedConversationId || 'default');
|
|
13386
13695
|
if (!state.agentWorkEventsByConversation) state.agentWorkEventsByConversation = {};
|
|
@@ -13400,13 +13709,29 @@ function applyConversationSnapshot(s, requestedConversationId, options) {
|
|
|
13400
13709
|
if (s && s.subagents) state.subagents = s.subagents;
|
|
13401
13710
|
if (s && s.linkedPlan) state.linkedPlan = s.linkedPlan;
|
|
13402
13711
|
replayActiveAgentWorkEvents();
|
|
13403
|
-
// Ensure running state reflects work runs from snapshot for freshly loaded
|
|
13712
|
+
// Ensure running state reflects work runs from snapshot for freshly loaded
|
|
13713
|
+
// conversations. dev-0.6.6: a persisted `running` work run is NOT evidence
|
|
13714
|
+
// that the conversation is still running — a crashed utility host leaves the
|
|
13715
|
+
// record behind forever. Only a snapshot that reports a live run may
|
|
13716
|
+
// resurrect the running state; otherwise close the stale record locally so
|
|
13717
|
+
// the timer stops and the next input is a new send instead of a queued Next
|
|
13718
|
+
// (the reported "timestamp keeps running but nothing works" state).
|
|
13719
|
+
var runtimeSnapshot = s && s.runtime ? s.runtime : null;
|
|
13720
|
+
var snapshotStatusText = String(s && s.status || '').toLowerCase();
|
|
13721
|
+
var backendStillRunning = !!(runtimeSnapshot && (runtimeSnapshot.running || runtimeSnapshot.stopRequested))
|
|
13722
|
+
|| ['running', 'stopping', 'force_restarting'].indexOf(snapshotStatusText) >= 0;
|
|
13404
13723
|
var runs = state.workRunsByTarget && state.workRunsByTarget[runtimeKeyFor(snapshotTarget.workspaceId, snapshotTarget.conversationId)] || [];
|
|
13405
13724
|
for (var ri = 0; ri < runs.length; ri++) {
|
|
13406
|
-
if (runs[ri].status
|
|
13725
|
+
if (runs[ri].status !== 'running') continue;
|
|
13726
|
+
if (backendStillRunning) {
|
|
13407
13727
|
setConversationRuntimeState(snapshotTarget, 'running', runs[ri].runId);
|
|
13728
|
+
continue;
|
|
13408
13729
|
}
|
|
13730
|
+
runs[ri].status = 'interrupted';
|
|
13731
|
+
if (!runs[ri].endedAt) runs[ri].endedAt = new Date().toISOString();
|
|
13732
|
+
if (typeof updateConversationWorkRunElement === 'function') updateConversationWorkRunElement(runs[ri]);
|
|
13409
13733
|
}
|
|
13734
|
+
if (typeof reconcileLocalRuntimeFromSnapshot === 'function') reconcileLocalRuntimeFromSnapshot(s, snapshotTarget);
|
|
13410
13735
|
var isRunning = !!runningConversationRecord(snapshotTarget.conversationId, snapshotTarget.workspaceId);
|
|
13411
13736
|
setWorking(isRunning);
|
|
13412
13737
|
}
|
|
@@ -13449,10 +13774,54 @@ function optionQuestionKey(question, options, index) {
|
|
|
13449
13774
|
return JSON.stringify({ index: index, question: question || '', options: (options || []).map(optionLabel) });
|
|
13450
13775
|
}
|
|
13451
13776
|
|
|
13452
|
-
|
|
13777
|
+
// dev-0.6.4 hotfix: 选项块携带归属会话,作答必须回到提问的那个会话。
|
|
13778
|
+
function optionBlockOwner(element) {
|
|
13779
|
+
var block = element && element.closest ? element.closest('[data-option-owner-conversation]') : null;
|
|
13780
|
+
if (!block) return currentConversationTarget();
|
|
13781
|
+
return {
|
|
13782
|
+
workspaceId: String(block.getAttribute('data-option-owner-workspace') || '') || runtimeWorkspaceId(''),
|
|
13783
|
+
conversationId: String(block.getAttribute('data-option-owner-conversation') || '') || activeConversationId()
|
|
13784
|
+
};
|
|
13785
|
+
}
|
|
13786
|
+
|
|
13787
|
+
function pendingOptionsForTarget(target) {
|
|
13788
|
+
var owner = normalizeQueuedConversationTarget(target || currentConversationTarget());
|
|
13789
|
+
if (!owner) return [];
|
|
13790
|
+
if (!state.pendingOptionsByTarget) state.pendingOptionsByTarget = {};
|
|
13791
|
+
var key = runtimeKeyFor(owner.workspaceId, owner.conversationId);
|
|
13792
|
+
return Array.isArray(state.pendingOptionsByTarget[key]) ? state.pendingOptionsByTarget[key] : [];
|
|
13793
|
+
}
|
|
13794
|
+
|
|
13795
|
+
function setPendingOptionsForTarget(options, target) {
|
|
13796
|
+
var owner = normalizeQueuedConversationTarget(target || currentConversationTarget());
|
|
13797
|
+
if (!owner) return;
|
|
13798
|
+
if (!state.pendingOptionsByTarget) state.pendingOptionsByTarget = {};
|
|
13799
|
+
var key = runtimeKeyFor(owner.workspaceId, owner.conversationId);
|
|
13800
|
+
state.pendingOptionsByTarget[key] = (Array.isArray(options) ? options : []).map(function(question) {
|
|
13801
|
+
return {
|
|
13802
|
+
id: String(question && question.id || ''),
|
|
13803
|
+
question: String(question && question.question || ''),
|
|
13804
|
+
options: (Array.isArray(question && question.options) ? question.options : []).map(function(option) {
|
|
13805
|
+
return typeof option === 'string' ? option : { label: String(option && option.label || ''), description: String(option && option.description || '') };
|
|
13806
|
+
})
|
|
13807
|
+
};
|
|
13808
|
+
});
|
|
13809
|
+
if (isActiveConversationTarget(owner)) state.pendingOptions = state.pendingOptionsByTarget[key];
|
|
13810
|
+
}
|
|
13811
|
+
|
|
13812
|
+
function addOptionBlock(question, options, questionKey, ownerAttributes) {
|
|
13453
13813
|
var div = document.createElement('div');
|
|
13454
13814
|
div.className = 'chat-msg assistant';
|
|
13455
13815
|
div.setAttribute('data-option-question', encodeURIComponent(questionKey));
|
|
13816
|
+
if (ownerAttributes) {
|
|
13817
|
+
var ownerHolder = document.createElement('div');
|
|
13818
|
+
ownerHolder.innerHTML = '<span' + ownerAttributes + '></span>';
|
|
13819
|
+
var ownerNode = ownerHolder.firstElementChild;
|
|
13820
|
+
if (ownerNode) {
|
|
13821
|
+
div.setAttribute('data-option-owner-workspace', ownerNode.getAttribute('data-option-owner-workspace') || '');
|
|
13822
|
+
div.setAttribute('data-option-owner-conversation', ownerNode.getAttribute('data-option-owner-conversation') || '');
|
|
13823
|
+
}
|
|
13824
|
+
}
|
|
13456
13825
|
var html = '<div class="option-block"><div class="option-question">' + esc(question) + '</div>';
|
|
13457
13826
|
for (var i = 0; i < options.length; i++) {
|
|
13458
13827
|
var label = optionLabel(options[i]);
|
|
@@ -13471,11 +13840,18 @@ function addOptionBlock(question, options, questionKey) {
|
|
|
13471
13840
|
}
|
|
13472
13841
|
|
|
13473
13842
|
function renderPendingOptionsInChat(options) {
|
|
13843
|
+
// dev-0.6.4 hotfix: 选项提问按会话归属。旧实现把全局 pendingOptions 渲染到当前
|
|
13844
|
+
// 对话区,后台会话的提问会“溢出”到前台对话,点击后还会用当前会话发送答案,
|
|
13845
|
+
// 等于把提问激活给了另一个对话。
|
|
13846
|
+
if (options === undefined) options = pendingOptionsForTarget(currentConversationTarget());
|
|
13474
13847
|
options = options || [];
|
|
13475
13848
|
if (!options.length) return;
|
|
13849
|
+
var owner = currentConversationTarget();
|
|
13850
|
+
var ownerAttributes = ' data-option-owner-workspace="' + escAttr(String(owner.workspaceId || '')) + '" data-option-owner-conversation="' + escAttr(String(owner.conversationId || 'default')) + '"';
|
|
13476
13851
|
var batchKey = JSON.stringify(options.map(function(q, index) { return optionQuestionKey(q.question || q.id || '', q.options || [], index); }));
|
|
13477
|
-
|
|
13478
|
-
|
|
13852
|
+
var batchKeyWithOwner = String(owner.workspaceId || '') + '::' + String(owner.conversationId || 'default') + '::' + batchKey;
|
|
13853
|
+
if (state.pendingOptionBatchKey !== batchKeyWithOwner) {
|
|
13854
|
+
state.pendingOptionBatchKey = batchKeyWithOwner;
|
|
13479
13855
|
state.pendingOptionAnswers = {};
|
|
13480
13856
|
}
|
|
13481
13857
|
for (var i = 0; i < options.length; i++) {
|
|
@@ -13485,7 +13861,7 @@ function renderPendingOptionsInChat(options) {
|
|
|
13485
13861
|
var encodedKey = encodeURIComponent(key);
|
|
13486
13862
|
if (els['chat-area'] && els['chat-area'].querySelector('[data-option-question="' + encodedKey + '"]')) continue;
|
|
13487
13863
|
state.renderedOptionKeys[key] = true;
|
|
13488
|
-
addOptionBlock(q.question || q.id || t('status.question'), q.options || [], key);
|
|
13864
|
+
addOptionBlock(q.question || q.id || t('status.question'), q.options || [], key, ownerAttributes);
|
|
13489
13865
|
}
|
|
13490
13866
|
}
|
|
13491
13867
|
|
|
@@ -13544,6 +13920,15 @@ function handlePlanExecutionChoice(questionKey, opt) {
|
|
|
13544
13920
|
|
|
13545
13921
|
window.optionSelected = function(questionKey, opt, button) {
|
|
13546
13922
|
if (!questionKey || state.pendingOptionAnswers[questionKey] !== undefined) return;
|
|
13923
|
+
// dev-0.6.4 hotfix: 作答必须回到提问的那个会话;后台会话的提问不允许借用
|
|
13924
|
+
// 当前对话的输入框发送(旧实现用 window.sendMessage() 直接发到当前会话)。
|
|
13925
|
+
var answerOwner = optionBlockOwner(button);
|
|
13926
|
+
if (!isActiveConversationTarget(answerOwner)) {
|
|
13927
|
+
showUiNotice(currentLang() === 'zh'
|
|
13928
|
+
? '该提问属于另一个对话,请先切换到那个对话再作答。'
|
|
13929
|
+
: 'That question belongs to another conversation; switch to it before answering.', 'error', 'option-owner-mismatch');
|
|
13930
|
+
return;
|
|
13931
|
+
}
|
|
13547
13932
|
if (state.mode === 'plan' && handlePlanExecutionChoice(questionKey, opt)) {
|
|
13548
13933
|
var planBlock = button && button.closest ? button.closest('[data-option-question]') : null;
|
|
13549
13934
|
if (planBlock) Array.from(planBlock.querySelectorAll('.option-btn')).forEach(function(candidate) {
|
|
@@ -13560,7 +13945,7 @@ window.optionSelected = function(questionKey, opt, button) {
|
|
|
13560
13945
|
candidate.classList.toggle('selected', candidate === button);
|
|
13561
13946
|
});
|
|
13562
13947
|
}
|
|
13563
|
-
var questions =
|
|
13948
|
+
var questions = pendingOptionsForTarget(answerOwner);
|
|
13564
13949
|
var answered = questions.map(function(q, index) {
|
|
13565
13950
|
var key = optionQuestionKey(q.question || q.id || '', q.options || [], index);
|
|
13566
13951
|
return { question: q.question || q.id || t('status.question'), answer: state.pendingOptionAnswers[key] };
|
|
@@ -13581,6 +13966,13 @@ window.optionSelected = function(questionKey, opt, button) {
|
|
|
13581
13966
|
};
|
|
13582
13967
|
|
|
13583
13968
|
window.optionManualSubmitted = function(element) {
|
|
13969
|
+
var manualOwner = optionBlockOwner(element);
|
|
13970
|
+
if (!isActiveConversationTarget(manualOwner)) {
|
|
13971
|
+
showUiNotice(currentLang() === 'zh'
|
|
13972
|
+
? '该提问属于另一个对话,请先切换到那个对话再作答。'
|
|
13973
|
+
: 'That question belongs to another conversation; switch to it before answering.', 'error', 'option-owner-mismatch');
|
|
13974
|
+
return;
|
|
13975
|
+
}
|
|
13584
13976
|
var manual = element && element.closest ? element.closest('.option-manual') : null;
|
|
13585
13977
|
if (!manual) return;
|
|
13586
13978
|
var input = manual.querySelector('.option-manual-input');
|
|
@@ -13774,6 +14166,66 @@ async function reconcileStaleLocalRuntimeBeforeSend(target) {
|
|
|
13774
14166
|
return true;
|
|
13775
14167
|
}
|
|
13776
14168
|
|
|
14169
|
+
/**
|
|
14170
|
+
* dev-0.6.6: 用权威快照纠正「本地还在跑、后端其实已经死了」的状态。
|
|
14171
|
+
*
|
|
14172
|
+
* 真实现场:utility host 进程被 V8 OOM 杀死后不会再发终态事件,渲染端于是永远
|
|
14173
|
+
* 停在「运行中」——计时一直在走、输入被当成 Next 排进队列、队列也永远等不到执行者。
|
|
14174
|
+
* 这里用已经在手的快照做纠正(不额外请求 IPC),并且绝不与渲染端在飞的发送抢状态。
|
|
14175
|
+
*/
|
|
14176
|
+
function reconcileLocalRuntimeFromSnapshot(snapshot, target) {
|
|
14177
|
+
if (!snapshot || typeof snapshot !== 'object') return false;
|
|
14178
|
+
var owner = normalizeQueuedConversationTarget(target || currentConversationTarget());
|
|
14179
|
+
if (!owner) return false;
|
|
14180
|
+
var key = runtimeKeyFor(owner.workspaceId, owner.conversationId);
|
|
14181
|
+
if (state.activeSendCallsByTarget && state.activeSendCallsByTarget[key]) return false;
|
|
14182
|
+
if (!runningConversationRecord(owner.conversationId, owner.workspaceId)) return false;
|
|
14183
|
+
var runtimeSnapshot = snapshot.runtime || null;
|
|
14184
|
+
var snapshotStatus = String(snapshot.status || '').toLowerCase();
|
|
14185
|
+
var backendStillRunning = !!(runtimeSnapshot && (runtimeSnapshot.running || runtimeSnapshot.stopRequested))
|
|
14186
|
+
|| ['running', 'stopping', 'force_restarting'].indexOf(snapshotStatus) >= 0;
|
|
14187
|
+
if (backendStillRunning) return false;
|
|
14188
|
+
if (['idle', 'completed', 'interrupted', 'force_interrupted', 'error'].indexOf(snapshotStatus) < 0) return false;
|
|
14189
|
+
var runId = String((runtimeSnapshot && runtimeSnapshot.runId) || snapshot.runId || '');
|
|
14190
|
+
var generation = Number((runtimeSnapshot && runtimeSnapshot.generation) || snapshot.generation || 0);
|
|
14191
|
+
setConversationRuntimeState(owner, snapshotStatus, runId, { provisional: false, generation: generation });
|
|
14192
|
+
if (state.nextQueueSchedulesByTarget) {
|
|
14193
|
+
delete state.nextQueueSchedulesByTarget[key];
|
|
14194
|
+
delete state.nextQueueSchedulesByTarget[runtimeKeyFor('', owner.conversationId)];
|
|
14195
|
+
}
|
|
14196
|
+
state._sendInFlight = Object.keys(state.runningConversations || {}).length > 0;
|
|
14197
|
+
setWorking(state._sendInFlight);
|
|
14198
|
+
updateSubmitButtonState();
|
|
14199
|
+
if (window.renderInputStack) window.renderInputStack();
|
|
14200
|
+
return true;
|
|
14201
|
+
}
|
|
14202
|
+
|
|
14203
|
+
/**
|
|
14204
|
+
* While the renderer believes a conversation is running, verify that belief
|
|
14205
|
+
* against the backend. A crashed host cannot publish its own terminal event, so
|
|
14206
|
+
* the watchdog is the only thing that can clear the phantom "running" state.
|
|
14207
|
+
*/
|
|
14208
|
+
var _runtimeReconcileWatchdog = null;
|
|
14209
|
+
function ensureRuntimeReconcileWatchdog() {
|
|
14210
|
+
if (_runtimeReconcileWatchdog || !api.getState) return;
|
|
14211
|
+
_runtimeReconcileWatchdog = setInterval(function() {
|
|
14212
|
+
var records = Object.keys(state.runningConversations || {});
|
|
14213
|
+
if (!records.length) {
|
|
14214
|
+
clearInterval(_runtimeReconcileWatchdog);
|
|
14215
|
+
_runtimeReconcileWatchdog = null;
|
|
14216
|
+
return;
|
|
14217
|
+
}
|
|
14218
|
+
records.forEach(function(key) {
|
|
14219
|
+
var record = (state.runningConversations || {})[key];
|
|
14220
|
+
var target = record && record.target ? record.target : null;
|
|
14221
|
+
if (!target || !target.conversationId) return;
|
|
14222
|
+
Promise.resolve(api.getState(target)).then(function(snapshot) {
|
|
14223
|
+
reconcileLocalRuntimeFromSnapshot(snapshot, target);
|
|
14224
|
+
}).catch(function() {});
|
|
14225
|
+
});
|
|
14226
|
+
}, 15000);
|
|
14227
|
+
}
|
|
14228
|
+
|
|
13777
14229
|
function setSubmitButtonVisual(icon, label, running, stopMode) {
|
|
13778
14230
|
var btn = els['submit-btn'] || document.getElementById('submit-btn');
|
|
13779
14231
|
if (!btn) return;
|
|
@@ -14457,19 +14909,14 @@ function modelDeploymentValueForName(name, providerId) {
|
|
|
14457
14909
|
var modelName = String(name || '').trim();
|
|
14458
14910
|
if (!modelName) return '';
|
|
14459
14911
|
var providers = Array.isArray(state.providers) ? state.providers : [];
|
|
14460
|
-
var
|
|
14461
|
-
|
|
14462
|
-
|
|
14463
|
-
if (
|
|
14464
|
-
|
|
14465
|
-
|
|
14466
|
-
|
|
14467
|
-
|
|
14468
|
-
}
|
|
14469
|
-
}
|
|
14470
|
-
if (!match) return modelName;
|
|
14471
|
-
var providerIdValue = String(match.provider.id || match.provider.name || '');
|
|
14472
|
-
return 'deployment:' + encodeURIComponent(providerIdValue) + ':' + encodeURIComponent(match.name);
|
|
14912
|
+
var matches = [];
|
|
14913
|
+
providers.forEach(function(provider) {
|
|
14914
|
+
if (providerId && String(provider.id || '') !== String(providerId)) return;
|
|
14915
|
+
if ((provider.models || []).some(function(model) { return (typeof model === 'string' ? model : model.name) === modelName; })) matches.push(provider);
|
|
14916
|
+
});
|
|
14917
|
+
// Missing/ambiguous identities must never select another provider's credentials.
|
|
14918
|
+
if (matches.length !== 1 || !matches[0].id) return '';
|
|
14919
|
+
return 'deployment:' + encodeURIComponent(matches[0].id) + ':' + encodeURIComponent(modelName);
|
|
14473
14920
|
}
|
|
14474
14921
|
|
|
14475
14922
|
// 不可用模型回退同步输入框下方选择区:固定模型选择被回退时,选择区
|
|
@@ -17273,13 +17720,24 @@ function queueIndexesForTarget(target) {
|
|
|
17273
17720
|
var requests = state.nextQueueRequests || [];
|
|
17274
17721
|
for (var index = 0; index < queue.length; index++) {
|
|
17275
17722
|
if (!queuedRequestMatchesTarget(requests[index], target)) continue;
|
|
17276
|
-
|
|
17277
|
-
|
|
17278
|
-
|
|
17723
|
+
// dev-0.6.4: 排队行永远可见。它属于「受理时捕获的分支」,但它是这个会话里
|
|
17724
|
+
// 真实存在的用户输入;旧逻辑按当前运行分支过滤,分页之后整队行被隐形,
|
|
17725
|
+
// 用户既看不到内容也无法恢复。可见性与行绑定的分支解耦(分支只决定它
|
|
17726
|
+
// 何时、在哪一页执行)。
|
|
17727
|
+
indexes.push(index);
|
|
17279
17728
|
}
|
|
17280
17729
|
return indexes;
|
|
17281
17730
|
}
|
|
17282
17731
|
|
|
17732
|
+
// 行绑定的分支与当前运行分支不一致时,该行会等待(内核会暂停而不是改投)。
|
|
17733
|
+
// 这个状态必须在界面上可见,用户才能切回对应分页再恢复队列。
|
|
17734
|
+
function queueRequestBranchMismatch(target, request) {
|
|
17735
|
+
var requestBranchPath = String(request && request.branchPath || '');
|
|
17736
|
+
var runtimeBranchPath = queueBranchPathForTarget(target, 'runtime');
|
|
17737
|
+
if (!requestBranchPath || !runtimeBranchPath) return false;
|
|
17738
|
+
return requestBranchPath !== runtimeBranchPath;
|
|
17739
|
+
}
|
|
17740
|
+
|
|
17283
17741
|
function queueIndexesForRuntimeTarget(target) {
|
|
17284
17742
|
var indexes = [];
|
|
17285
17743
|
var queue = state.nextQueue || [];
|
|
@@ -17312,7 +17770,9 @@ function rebindQueueToRuntimeBranch(target) {
|
|
|
17312
17770
|
if (!runtimePath || !key) return;
|
|
17313
17771
|
(state.nextQueueRequests || []).forEach(function(request) {
|
|
17314
17772
|
if (!request || !queuedRequestMatchesTarget(request, target)) return;
|
|
17315
|
-
|
|
17773
|
+
// dev-0.6.4: 只刷新会话 runtime 身份,绝不改写受理时捕获的分支路径。
|
|
17774
|
+
// 静默改绑会把排队输入从用户提交的分页挪到另一条分支,正是
|
|
17775
|
+
// 「跨分支复用/串分支」必须禁止的行为。
|
|
17316
17776
|
request.runtimeKey = key;
|
|
17317
17777
|
});
|
|
17318
17778
|
}
|
|
@@ -17377,6 +17837,21 @@ function setQueueItemsForTarget(items, target) {
|
|
|
17377
17837
|
runId: String(item && item.runId || ''),
|
|
17378
17838
|
createdAt: String(item && item.createdAt || ''),
|
|
17379
17839
|
images: (Array.isArray(item && item.images) ? item.images : []).map(function(image) { return Object.assign({}, image); }),
|
|
17840
|
+
// dev-0.6.4: 保留受理时捕获的分支身份。旧实现只保留展示字段,排队行在
|
|
17841
|
+
// 重新投影时会被默认绑到「当前运行分支」,等于静默改绑到另一条分页,
|
|
17842
|
+
// 也让「等待所属分页」的状态永远无法显示。
|
|
17843
|
+
branchNodeId: String(item && item.branchNodeId || ''),
|
|
17844
|
+
branchPath: Array.isArray(item && item.branchPath)
|
|
17845
|
+
? item.branchPath.map(function(value) { return String(value || ''); }).filter(Boolean)
|
|
17846
|
+
: (item && item.branchPath ? [String(item.branchPath)] : []),
|
|
17847
|
+
ledgerBranchId: String(item && item.ledgerBranchId || ''),
|
|
17848
|
+
targetRuntimeKey: String(item && item.targetRuntimeKey || ''),
|
|
17849
|
+
workspaceKey: String(item && item.workspaceKey || ''),
|
|
17850
|
+
modelSelection: String(item && item.modelSelection || ''),
|
|
17851
|
+
// dev-0.6.4 hotfix: 保留账本给出的受阻原因,界面才能显示“被失败前置阻断”
|
|
17852
|
+
// 并提供显式的修复入口。
|
|
17853
|
+
waitingReason: String(item && item.waitingReason || ''),
|
|
17854
|
+
blocked: item && item.blocked === true,
|
|
17380
17855
|
};
|
|
17381
17856
|
});
|
|
17382
17857
|
}
|
|
@@ -17412,7 +17887,23 @@ function applyConversationCommandSnapshot(snapshot, target) {
|
|
|
17412
17887
|
if (!state.queuePausedByTarget) state.queuePausedByTarget = {};
|
|
17413
17888
|
state.queuePausedByTarget[key] = snapshot.queuePaused;
|
|
17414
17889
|
}
|
|
17415
|
-
if (Array.isArray(snapshot.queueItems))
|
|
17890
|
+
if (Array.isArray(snapshot.queueItems)) {
|
|
17891
|
+
// dev-0.6.4 hotfix: 排队行真正进入对话(出队)时,后端会把这一轮写成正式用户
|
|
17892
|
+
// 消息,但渲染端不会收到发送回执,因此此前对话区一直不显示这条用户输入。
|
|
17893
|
+
// 这里比较权威队列的条数变化,检测到出队就刷新一次转录。
|
|
17894
|
+
var previousQueueItems = queueItemsForTarget(target);
|
|
17895
|
+
var previousQueueIds = previousQueueItems.map(function(item) { return String(item && item.id || ''); }).filter(Boolean);
|
|
17896
|
+
var nextQueueIds = snapshot.queueItems.map(function(item) { return String(item && item.id || ''); }).filter(Boolean);
|
|
17897
|
+
var drainedIds = previousQueueIds.filter(function(id) { return nextQueueIds.indexOf(id) < 0; });
|
|
17898
|
+
setQueueItemsForTarget(snapshot.queueItems, target);
|
|
17899
|
+
if (drainedIds.length) scheduleDrainedTurnTranscriptRefresh(target, drainedIds);
|
|
17900
|
+
}
|
|
17901
|
+
if (Array.isArray(snapshot.pendingOptions)) setPendingOptionsForTarget(snapshot.pendingOptions, target);
|
|
17902
|
+
// dev-0.6.4: goal 停止/清除后,任何携带 goal 字段的权威快照都要同步前端显示。
|
|
17903
|
+
if (Object.prototype.hasOwnProperty.call(snapshot, 'goal') && isActiveConversationTarget(target)) {
|
|
17904
|
+
applyReturnedGoalState(snapshot);
|
|
17905
|
+
if (window.renderInputStack) window.renderInputStack();
|
|
17906
|
+
}
|
|
17416
17907
|
var queue = snapshot.queued && typeof snapshot.queued === 'object' ? snapshot.queued : snapshot.queue;
|
|
17417
17908
|
if (queue && typeof queue === 'object') {
|
|
17418
17909
|
setBackendQueueForTarget(queue, target);
|
|
@@ -17449,6 +17940,55 @@ function applyConversationCommandSnapshot(snapshot, target) {
|
|
|
17449
17940
|
}
|
|
17450
17941
|
}
|
|
17451
17942
|
|
|
17943
|
+
/**
|
|
17944
|
+
* dev-0.6.4 hotfix: 排队输入出队进入对话后,把权威转录刷新到对话区。
|
|
17945
|
+
*
|
|
17946
|
+
* 走发送路径的回合有本地乐观气泡 + 回执刷新;排队回合由内核在后台启动,渲染端
|
|
17947
|
+
* 既没有乐观气泡也没有回执,于是用户输入在对话区“消失”。这里在检测到排队行
|
|
17948
|
+
* 出队时按目标做一次去抖刷新:只在当前前台会话、且没有渲染端发送在飞时执行,
|
|
17949
|
+
* 避免与发送回执互相覆盖。
|
|
17950
|
+
*/
|
|
17951
|
+
function scheduleDrainedTurnTranscriptRefresh(target, drainedIds) {
|
|
17952
|
+
var owner = normalizeQueuedConversationTarget(target || currentConversationTarget());
|
|
17953
|
+
if (!owner || !api.getState) return;
|
|
17954
|
+
var key = runtimeKeyFor(owner.workspaceId, owner.conversationId);
|
|
17955
|
+
if (!isActiveConversationTarget(owner)) return;
|
|
17956
|
+
if (state.activeSendCallsByTarget && state.activeSendCallsByTarget[key]) return;
|
|
17957
|
+
if (!state.pendingDrainRefreshByTarget) state.pendingDrainRefreshByTarget = {};
|
|
17958
|
+
if (state.pendingDrainRefreshByTarget[key]) return;
|
|
17959
|
+
// 出队事件早于正式用户消息落盘,因此按几个时间点各刷新一次:首次拿到用户输入,
|
|
17960
|
+
// 后续拿到助手回复;全部完成后清除登记,下一次出队可再次触发。
|
|
17961
|
+
state.pendingDrainRefreshByTarget[key] = { ids: (drainedIds || []).slice(), timers: [] };
|
|
17962
|
+
var pending = state.pendingDrainRefreshByTarget[key];
|
|
17963
|
+
[150, 1200, 3000].forEach(function(delay, index, all) {
|
|
17964
|
+
pending.timers.push(setTimeout(function() {
|
|
17965
|
+
refreshConversationTranscriptFromBackend(owner);
|
|
17966
|
+
if (index === all.length - 1 && state.pendingDrainRefreshByTarget) delete state.pendingDrainRefreshByTarget[key];
|
|
17967
|
+
}, delay));
|
|
17968
|
+
});
|
|
17969
|
+
}
|
|
17970
|
+
|
|
17971
|
+
function refreshConversationTranscriptFromBackend(target) {
|
|
17972
|
+
var owner = normalizeQueuedConversationTarget(target || currentConversationTarget());
|
|
17973
|
+
if (!owner || !api.getState || !isActiveConversationTarget(owner)) return Promise.resolve(null);
|
|
17974
|
+
var key = runtimeKeyFor(owner.workspaceId, owner.conversationId);
|
|
17975
|
+
if (state.activeSendCallsByTarget && state.activeSendCallsByTarget[key]) return Promise.resolve(null);
|
|
17976
|
+
return Promise.resolve(api.getState(owner)).then(function(snapshot) {
|
|
17977
|
+
if (!snapshot || typeof snapshot !== 'object') return null;
|
|
17978
|
+
if (!isActiveConversationTarget(owner)) return null;
|
|
17979
|
+
if (state.activeSendCallsByTarget && state.activeSendCallsByTarget[key]) return null;
|
|
17980
|
+
// dev-0.6.4: goal 完成/停止后,前端必须在收到权威快照时同步清除 goal 显示。
|
|
17981
|
+
applyReturnedGoalState(snapshot);
|
|
17982
|
+
if (Array.isArray(snapshot.workRuns)) syncWorkRunsSnapshot(snapshot.workRuns, owner);
|
|
17983
|
+
if (Array.isArray(snapshot.chatMessages)) {
|
|
17984
|
+
hydrateConversationBranchState(snapshot);
|
|
17985
|
+
cacheConversationMessages(snapshot.chatMessages, owner);
|
|
17986
|
+
renderChatMessages(snapshot.chatMessages, owner);
|
|
17987
|
+
}
|
|
17988
|
+
return snapshot;
|
|
17989
|
+
}).catch(function() { return null; });
|
|
17990
|
+
}
|
|
17991
|
+
|
|
17452
17992
|
window.queueAction = function(action, input, target) {
|
|
17453
17993
|
if (!api.queueAction) return Promise.resolve({ ok: false, error: 'queueAction unavailable' });
|
|
17454
17994
|
var owner = normalizeQueuedConversationTarget(target || currentConversationTarget());
|
|
@@ -17549,6 +18089,15 @@ window.renderQueuePanel = function() {
|
|
|
17549
18089
|
var visibleIndexes = queueIndexesForTarget(target);
|
|
17550
18090
|
var count = visibleIndexes.length;
|
|
17551
18091
|
panel.style.display = count ? '' : 'none';
|
|
18092
|
+
// dev-0.6.4: 新排队输入必须被看见。旧行为把队列面板默认折叠,配合按分支
|
|
18093
|
+
// 过滤的隐形逻辑,用户提交的排队内容在界面上完全不出现。条数增加时自动
|
|
18094
|
+
// 展开,用户手动折叠在条数不变时仍然有效。
|
|
18095
|
+
if (!state.queueSeenCountsByTarget) state.queueSeenCountsByTarget = {};
|
|
18096
|
+
// 仅用于「新增排队行时自动展开面板」的本地记账键,不参与执行身份判定。
|
|
18097
|
+
var seenKey = String(target.workspaceId || '') + '::' + String(target.conversationId || 'default');
|
|
18098
|
+
var seenCount = Number(state.queueSeenCountsByTarget[seenKey] || 0);
|
|
18099
|
+
if (count > seenCount) state.queueCollapsed = false;
|
|
18100
|
+
state.queueSeenCountsByTarget[seenKey] = count;
|
|
17552
18101
|
panel.classList.toggle('collapsed', !!state.queueCollapsed);
|
|
17553
18102
|
label.textContent = t('queue.title') + ' ' + count;
|
|
17554
18103
|
var pauseToggle = document.getElementById('queue-pause-btn');
|
|
@@ -17592,17 +18141,73 @@ window.renderQueuePanel = function() {
|
|
|
17592
18141
|
'<button type="button" class="stack-icon-btn queue-guide-btn" onclick="window.guideQueueItem(' + eventIndex + ')" title="' + escAttr(t('queue.guideNow')) + '">' + iconSvg('corner-down-right', t('queue.guideNow'), 'tiny') + '<span>' + esc(t('queue.guideAction')) + '</span></button>' +
|
|
17593
18142
|
'<button class="stack-icon-btn" onclick="window.focusQueueItem(' + eventIndex + ')" title="' + escAttr(t('common.edit')) + '">' + iconSvg('square-pen', t('common.edit'), 'tiny') + '</button>' +
|
|
17594
18143
|
'<button class="stack-icon-btn danger" onclick="window.deleteQueueItem(' + eventIndex + ')" title="' + escAttr(t('common.delete')) + '">' + iconSvg('trash', t('common.delete'), 'tiny') + '</button>' : '';
|
|
17595
|
-
|
|
18144
|
+
// 绑定了另一条分页的行会等待而不是改投,这个状态必须在界面上显式可见,
|
|
18145
|
+
// 否则用户会以为排队输入被丢弃。
|
|
18146
|
+
var waitingForBranch = queueRequestBranchMismatch(target, request);
|
|
18147
|
+
var waitingLabel = currentLang() === 'zh'
|
|
18148
|
+
? '等待它所属的分页:切回该分页后队列可恢复执行'
|
|
18149
|
+
: 'Waiting for its own page: switch back to that page to resume the queue';
|
|
18150
|
+
// dev-0.6.4 hotfix: 被失败前置阻断的排队行必须显式可见,否则用户只会看到
|
|
18151
|
+
// “恢复后什么都不发生”。恢复队列时内核会把这些行重新挂到最后一个已提交 Build。
|
|
18152
|
+
var authoritativeItem = queueItemId
|
|
18153
|
+
? queueItemsForTarget(target).find(function(item) { return item.id === queueItemId; })
|
|
18154
|
+
: null;
|
|
18155
|
+
var blockedRow = !!(authoritativeItem && (authoritativeItem.blocked === true || authoritativeItem.waitingReason));
|
|
18156
|
+
var blockedLabel = currentLang() === 'zh'
|
|
18157
|
+
? '这一行被失败的前置 Build 阻断:按“恢复队列”会把它重新挂到最后一个已提交 Build 后执行'
|
|
18158
|
+
: 'Blocked by a failed predecessor: resuming the queue re-anchors this row to the last committed build';
|
|
18159
|
+
html += '<div class="queue-item" data-queue-index="' + queueIndex + '"' + identityAttributes
|
|
18160
|
+
+ (waitingForBranch ? ' data-queue-branch-waiting="1" title="' + escAttr(waitingLabel) + '"' : '')
|
|
18161
|
+
+ (blockedRow ? ' data-queue-blocked="1" title="' + escAttr(blockedLabel) + '"' : '')
|
|
18162
|
+
+ dragAttributes + '>' +
|
|
17596
18163
|
dragHandle +
|
|
17597
18164
|
'<input class="queue-edit" value="' + escAttr(state.nextQueue[queueIndex] || '') + '"' + inputAttributes + '>' +
|
|
17598
18165
|
'<span class="stack-actions">' + actions + '</span>' +
|
|
17599
18166
|
'</div>';
|
|
17600
18167
|
}
|
|
17601
18168
|
list.innerHTML = html;
|
|
18169
|
+
|
|
18170
|
+
// dev-0.6.4 hotfix: 存在受阻行时提供显式修复入口。0.6.3 合约要求失败前置继续
|
|
18171
|
+
// 阻断后继(不静默跳过),因此修复必须是用户主动动作,并且可见、可审计。
|
|
18172
|
+
var blockedCount = 0;
|
|
18173
|
+
for (var blockedIndex = 0; blockedIndex < visibleIndexes.length; blockedIndex++) {
|
|
18174
|
+
var blockedRequest = state.nextQueueRequests[visibleIndexes[blockedIndex]];
|
|
18175
|
+
var blockedItemId = blockedRequest ? String(blockedRequest.queueItemId || '') : '';
|
|
18176
|
+
var blockedItem = blockedItemId
|
|
18177
|
+
? queueItemsForTarget(target).find(function(item) { return item.id === blockedItemId; })
|
|
18178
|
+
: null;
|
|
18179
|
+
if (blockedItem && (blockedItem.blocked === true || blockedItem.waitingReason)) blockedCount++;
|
|
18180
|
+
}
|
|
18181
|
+
var headerActions = panel.querySelector('#queue-header .stack-actions');
|
|
18182
|
+
var repairButton = document.getElementById('queue-repair-btn');
|
|
18183
|
+
if (blockedCount) {
|
|
18184
|
+
if (!repairButton && headerActions) {
|
|
18185
|
+
repairButton = document.createElement('button');
|
|
18186
|
+
repairButton.className = 'stack-icon-btn';
|
|
18187
|
+
repairButton.id = 'queue-repair-btn';
|
|
18188
|
+
repairButton.setAttribute('type', 'button');
|
|
18189
|
+
repairButton.setAttribute('onclick', 'event.stopPropagation();window.repairBlockedQueue()');
|
|
18190
|
+
var pauseButton = document.getElementById('queue-pause-btn');
|
|
18191
|
+
if (pauseButton && pauseButton.parentNode === headerActions) headerActions.insertBefore(repairButton, pauseButton);
|
|
18192
|
+
else headerActions.appendChild(repairButton);
|
|
18193
|
+
}
|
|
18194
|
+
if (repairButton) {
|
|
18195
|
+
var repairLabel = currentLang() === 'zh' ? '修复受阻队列' : 'Repair blocked queue';
|
|
18196
|
+
repairButton.title = repairLabel;
|
|
18197
|
+
repairButton.innerHTML = iconSvg('wrench', repairLabel, 'tiny') + '<span class="icon-only-label">' + esc(repairLabel) + '</span>';
|
|
18198
|
+
}
|
|
18199
|
+
} else if (repairButton && repairButton.parentNode) {
|
|
18200
|
+
repairButton.remove();
|
|
18201
|
+
}
|
|
17602
18202
|
};
|
|
17603
18203
|
|
|
17604
18204
|
window.renderNextQueue = window.renderQueuePanel;
|
|
17605
18205
|
|
|
18206
|
+
// 显式修复受阻队列:把被失败前置阻断的排队行重新挂到最后一个已提交 Build。
|
|
18207
|
+
window.repairBlockedQueue = function() {
|
|
18208
|
+
return window.queueAction('repair_blocked', {}, currentConversationTarget());
|
|
18209
|
+
};
|
|
18210
|
+
|
|
17606
18211
|
window.toggleQueuePanel = function() {
|
|
17607
18212
|
state.queueCollapsed = !state.queueCollapsed;
|
|
17608
18213
|
window.renderInputStack();
|
|
@@ -18203,8 +18808,6 @@ window.sendMessage = async function(modeOverride, queuedText, opts) {
|
|
|
18203
18808
|
addMsg('user', displayText, opts.fromQueue ? 'next-build' : requestedMode, state.model, opts.optimisticMessageIndex, { attachments: optimisticAttachments });
|
|
18204
18809
|
}
|
|
18205
18810
|
renderConversationBranchPagers();
|
|
18206
|
-
applyAutoRouteRatingState(null);
|
|
18207
|
-
renderAutoRouteRatingControls();
|
|
18208
18811
|
}
|
|
18209
18812
|
var responseMsg = null;
|
|
18210
18813
|
var sendFailure = '';
|
|
@@ -18248,7 +18851,6 @@ window.sendMessage = async function(modeOverride, queuedText, opts) {
|
|
|
18248
18851
|
setConversationRuntimeState(lockedTarget, receivedSendStatus, r.runId, { provisional: false, generation: r.generation || 0, runtimeKey: r.runtimeKey || '' });
|
|
18249
18852
|
}
|
|
18250
18853
|
var stillActive = sendResultOwnsRuntime && sendLoadGeneration === state.conversationLoadGeneration && isActiveConversationTarget(lockedTarget) && isViewingRuntimeConversationBranch(lockedTarget);
|
|
18251
|
-
if (stillActive) applyAutoRouteRatingState(r);
|
|
18252
18854
|
if (stillActive && r && Array.isArray(r.workRuns)) syncWorkRunsSnapshot(r.workRuns, lockedTarget);
|
|
18253
18855
|
var responseOwnedByRun = !!(r && Array.isArray(r.workRuns) && r.workRuns.some(function(run) {
|
|
18254
18856
|
return (run.events || []).some(function(event) { return ['response', 'final_response'].indexOf(String(event.type || '').toLowerCase()) >= 0; });
|
|
@@ -18351,7 +18953,6 @@ window.sendMessage = async function(modeOverride, queuedText, opts) {
|
|
|
18351
18953
|
}
|
|
18352
18954
|
if (stillActiveAfterRefresh && s && s.conversationPlan) state.conversationPlan = normalizeConversationPlan(s.conversationPlan);
|
|
18353
18955
|
if (stillActiveAfterRefresh && s && Array.isArray(s.workRuns)) {
|
|
18354
|
-
applyAutoRouteRatingState(s);
|
|
18355
18956
|
hydrateConversationBranchState(s);
|
|
18356
18957
|
syncWorkRunsSnapshot(s.workRuns, lockedTarget);
|
|
18357
18958
|
if (Array.isArray(s.chatMessages)) renderChatMessages(s.chatMessages, lockedTarget);
|
|
@@ -18944,8 +19545,6 @@ window.refreshRightStatus = function(options) {
|
|
|
18944
19545
|
var statusContextRequest = captureContextWindowSnapshotRequest(statusTarget);
|
|
18945
19546
|
return api.getState(statusTarget).then(function(s) {
|
|
18946
19547
|
if (!isActiveConversationTarget(statusTarget) || statusLoadGeneration !== state.conversationLoadGeneration) return null;
|
|
18947
|
-
applyAutoRouteRatingState(s);
|
|
18948
|
-
renderAutoRouteRatingControls();
|
|
18949
19548
|
applyConversationCommandSnapshot(s, statusTarget);
|
|
18950
19549
|
if (s && s.model !== undefined) state.model = s.model;
|
|
18951
19550
|
if (s && s.modelLabel) state.modelLabel = s.modelLabel;
|
|
@@ -18962,7 +19561,7 @@ window.refreshRightStatus = function(options) {
|
|
|
18962
19561
|
}
|
|
18963
19562
|
if (s && s.subagents) state.subagents = s.subagents;
|
|
18964
19563
|
if (s && s.fileDiffs) state.fileDiffs = s.fileDiffs;
|
|
18965
|
-
if (s && s.pendingOptions)
|
|
19564
|
+
if (s && s.pendingOptions) setPendingOptionsForTarget(s.pendingOptions, computerTarget || currentConversationTarget(s.conversationId || requestedConversationId || activeConversationId()));
|
|
18966
19565
|
// Bind the Flow takeover to backend per-conversation state. The takeover
|
|
18967
19566
|
// for the requested target reflects exactly what the backend holds for that
|
|
18968
19567
|
// conversation: a suspension (paused) or an active running Flow. It is
|
|
@@ -19848,7 +20447,9 @@ function applyReturnedGoalState(snapshot) {
|
|
|
19848
20447
|
if (!snapshot || !Object.prototype.hasOwnProperty.call(snapshot, 'goal')) return;
|
|
19849
20448
|
state.goalText = snapshot.goal && snapshot.goal.objective ? String(snapshot.goal.objective) : '';
|
|
19850
20449
|
state.goalVisible = !!state.goalText;
|
|
19851
|
-
|
|
20450
|
+
// dev-0.6.4: goal 结束后端会停止/清空 goal,但前端此前只清 text/visible,paused
|
|
20451
|
+
// 残留会让 goal 条继续以“暂停”状态显示。这里在无 goal 时一并清除暂停态。
|
|
20452
|
+
state.goalPaused = state.goalVisible ? !!(snapshot.goal && snapshot.goal.paused) : false;
|
|
19852
20453
|
if (!snapshot.goal && snapshot.mode === 'build') {
|
|
19853
20454
|
state.mode = 'build';
|
|
19854
20455
|
if (els['mode-select']) {
|
|
@@ -20217,6 +20818,29 @@ function renderGeneralSettings() {
|
|
|
20217
20818
|
'</div>';
|
|
20218
20819
|
}
|
|
20219
20820
|
|
|
20821
|
+
/**
|
|
20822
|
+
* Current-conversation-model decision policy. No separate JEV model, runtime,
|
|
20823
|
+
* model ID, artifact path, or selector is exposed here. When Auto is off every
|
|
20824
|
+
* policy control is locked.
|
|
20825
|
+
*/
|
|
20826
|
+
function jevDecisionModelSettingsHtml() {
|
|
20827
|
+
var jev = state.jevDecisionModel && typeof state.jevDecisionModel === 'object' ? state.jevDecisionModel : {};
|
|
20828
|
+
var locked = jev.locked === true || state.autoSwitch === 'off';
|
|
20829
|
+
var disabled = locked ? ' disabled' : '';
|
|
20830
|
+
var enabled = jev.enabled !== false;
|
|
20831
|
+
var currentModelStatus = t('model.jevCurrentModelStatus');
|
|
20832
|
+
return '<div class="provider-card" id="jev-decision-model-card" data-jev-locked="' + (locked ? 'true' : 'false') + '" style="margin-top:12px;">' +
|
|
20833
|
+
'<div id="jev-decision-model-title" style="font-size:13px;font-weight:700;color:var(--text);margin-bottom:4px;">' + esc(t('model.jevTitle')) + '</div>' +
|
|
20834
|
+
'<div id="jev-decision-model-desc" style="font-size:11px;color:var(--text-dim);line-height:1.45;margin-bottom:6px;">' + esc(t('model.jevDesc')) + '</div>' +
|
|
20835
|
+
(locked ? '<div id="jev-decision-model-lock" class="model-eval-pending" style="font-size:11px;margin-bottom:6px;">' + esc(t('model.jevLocked')) + '</div>' : '') +
|
|
20836
|
+
'<div class="setting-row"><span class="setting-label">' + esc(t('model.jevEnabled')) + '</span><div class="setting-control">' +
|
|
20837
|
+
'<input type="checkbox" id="jev-field-enabled" data-jev-field="enabled" role="switch"' + (enabled ? ' checked' : '') + disabled + ' onchange="window.setJevDecisionModel({enabled:this.checked})"></div></div>' +
|
|
20838
|
+
'<div class="setting-row"><span class="setting-label">' + esc(t('model.jevTimeout')) + '</span><div class="setting-control">' +
|
|
20839
|
+
'<input type="number" id="jev-field-timeout" data-jev-field="timeout-ms" min="100" max="30000" step="100" style="width:90px;" value="' + escAttr(String(Number(jev.timeoutMs) || 3000)) + '"' + disabled + ' onchange="window.setJevDecisionModel({timeoutMs:Number(this.value)})"></div></div>' +
|
|
20840
|
+
'<div id="jev-decision-model-status" data-jev-model="current-eligible-model" data-jev-thinking="off" data-jev-output="strict-json-schema" style="font-size:11px;color:var(--text-dim);margin-top:4px;">' + esc(currentModelStatus) + '</div>' +
|
|
20841
|
+
'</div>';
|
|
20842
|
+
}
|
|
20843
|
+
|
|
20220
20844
|
function renderModelSettings() {
|
|
20221
20845
|
var providersHtml = '';
|
|
20222
20846
|
for (var i = 0; i < state.providers.length; i++) {
|
|
@@ -20292,11 +20916,11 @@ function renderModelSettings() {
|
|
|
20292
20916
|
'<option value="off"' + (state.fallbackOnUnavailable === 'off' ? ' selected' : '') + '>' + esc(t('common.off')) + '</option>' +
|
|
20293
20917
|
'<option value="on"' + (state.fallbackOnUnavailable === 'on' ? ' selected' : '') + '>' + esc(t('common.on')) + '</option></select></div></div>' +
|
|
20294
20918
|
'<div class="setting-row"><span class="setting-label">' + esc(t('model.preference')) + '</span><div class="setting-control"><select onchange="window.setSwitchTendency(this.value)">' +
|
|
20295
|
-
'<option value="
|
|
20296
|
-
'<option value="balanced"' + (state.switchTendency === 'balanced' ? ' selected' : '') + '>' + esc(t('model.
|
|
20297
|
-
'<option value="
|
|
20298
|
-
'<
|
|
20299
|
-
|
|
20919
|
+
'<option value="conservative"' + (state.switchTendency === 'conservative' ? ' selected' : '') + '>' + esc(t('model.conservativeTendency')) + '</option>' +
|
|
20920
|
+
'<option value="balanced"' + (state.switchTendency === 'balanced' ? ' selected' : '') + '>' + esc(t('model.balancedTendency')) + '</option>' +
|
|
20921
|
+
'<option value="aggressive"' + (state.switchTendency === 'aggressive' ? ' selected' : '') + '>' + esc(t('model.aggressiveTendency')) + '</option></select></div></div>' +
|
|
20922
|
+
'<div class="setting-desc" style="margin:4px 0 12px;">' + esc(t('model.preferenceDesc')) + '</div>' +
|
|
20923
|
+
jevDecisionModelSettingsHtml() +
|
|
20300
20924
|
'</div>' +
|
|
20301
20925
|
'<div class="provider-card" style="margin-top:12px;">' +
|
|
20302
20926
|
'<label for="global-agent-prompt" style="display:block;font-size:13px;font-weight:700;color:var(--text);margin-bottom:6px;">' + esc(t('model.globalAgentPrompt')) + '</label>' +
|
|
@@ -20777,6 +21401,47 @@ window.setAutoSwitchMode = function(v) {
|
|
|
20777
21401
|
state.autoSwitchScope = v === 'provider' ? 'provider' : 'all';
|
|
20778
21402
|
api.saveConfig({ autoSwitch: state.autoSwitch, autoSwitchScope: state.autoSwitchScope });
|
|
20779
21403
|
window.refreshModelSelect();
|
|
21404
|
+
// Auto off locks the decision-model editor; Auto on unlocks it again. Ask the
|
|
21405
|
+
// backend for the authoritative lock state instead of guessing locally.
|
|
21406
|
+
window.refreshJevDecisionModel();
|
|
21407
|
+
};
|
|
21408
|
+
|
|
21409
|
+
window.refreshJevDecisionModel = function() {
|
|
21410
|
+
return api.getState().then(function(s) {
|
|
21411
|
+
window.applyJevDecisionModelState(s);
|
|
21412
|
+
var panel = document.getElementById('stab-models');
|
|
21413
|
+
if (panel && panel.getAttribute('data-lazy') !== '1') panel.innerHTML = renderModelSettings();
|
|
21414
|
+
return state.jevDecisionModel;
|
|
21415
|
+
}).catch(function() { return state.jevDecisionModel; });
|
|
21416
|
+
};
|
|
21417
|
+
|
|
21418
|
+
window.applyJevDecisionModelState = function(snapshot) {
|
|
21419
|
+
if (snapshot && typeof snapshot === 'object' && snapshot.jevDecisionModel && typeof snapshot.jevDecisionModel === 'object') {
|
|
21420
|
+
state.jevDecisionModel = snapshot.jevDecisionModel;
|
|
21421
|
+
}
|
|
21422
|
+
};
|
|
21423
|
+
|
|
21424
|
+
/**
|
|
21425
|
+
* Persist one decision-model field. The backend is authoritative: it refuses
|
|
21426
|
+
* every edit while Auto is off (`auto_disabled`) and rejects invalid values, so
|
|
21427
|
+
* the UI never claims a change that did not happen.
|
|
21428
|
+
*/
|
|
21429
|
+
window.setJevDecisionModel = function(patch) {
|
|
21430
|
+
if (!api.setJevDecisionModel) return Promise.resolve(null);
|
|
21431
|
+
return api.setJevDecisionModel(patch || {}).then(function(result) {
|
|
21432
|
+
if (result && result.state) state.jevDecisionModel = result.state;
|
|
21433
|
+
var panel = document.getElementById('stab-models');
|
|
21434
|
+
if (panel) panel.innerHTML = renderModelSettings();
|
|
21435
|
+
if (!result || result.ok === false) {
|
|
21436
|
+
showUiNotice(result && result.reason === 'auto_disabled' ? t('model.jevLocked') : t('model.jevRefused'), 'error', 'jev-decision-model');
|
|
21437
|
+
} else {
|
|
21438
|
+
showUiNotice(t('model.jevSaved'), 'info', 'jev-decision-model');
|
|
21439
|
+
}
|
|
21440
|
+
return result;
|
|
21441
|
+
}).catch(function(error) {
|
|
21442
|
+
showUiNotice(t('model.jevRefused') + ' ' + (error && error.message ? error.message : String(error)), 'error', 'jev-decision-model');
|
|
21443
|
+
return null;
|
|
21444
|
+
});
|
|
20780
21445
|
};
|
|
20781
21446
|
|
|
20782
21447
|
var systemColorScheme = window.matchMedia('(prefers-color-scheme: dark)');
|
|
@@ -20919,14 +21584,10 @@ window.setFallbackOnUnavailable = function(v) {
|
|
|
20919
21584
|
};
|
|
20920
21585
|
|
|
20921
21586
|
window.setSwitchTendency = function(v) {
|
|
20922
|
-
state.switchTendency = ['
|
|
21587
|
+
state.switchTendency = ['conservative', 'balanced', 'aggressive'].indexOf(v) >= 0 ? v : 'balanced';
|
|
20923
21588
|
api.saveConfig({ switchTendency: state.switchTendency });
|
|
20924
21589
|
};
|
|
20925
21590
|
|
|
20926
|
-
window.clearLearnedAutoPreferences = function() {
|
|
20927
|
-
api.saveConfig({ clearLearnedAutoPreferences: true }).catch(function(){});
|
|
20928
|
-
};
|
|
20929
|
-
|
|
20930
21591
|
window.setNativeToolEnabled = function(name, enabled) {
|
|
20931
21592
|
if (!name) return;
|
|
20932
21593
|
state.nativeToolEnabled = state.nativeToolEnabled || {};
|
|
@@ -21037,6 +21698,7 @@ window.removeProvider = function(idx) {
|
|
|
21037
21698
|
|
|
21038
21699
|
window.addModel = function() {
|
|
21039
21700
|
window.openSubWin(t('model.addModel'), '<div class="auto-input-group"><label>' + esc(t('model.modelName')) + '</label><input id="new-model-name" placeholder="gpt-4"></div>' +
|
|
21701
|
+
'<div class="auto-input-group"><label>' + esc(t('model.displayName')) + '</label><input id="new-model-display"></div>' +
|
|
21040
21702
|
'<div class="auto-input-group"><label>' + esc(t('model.provider')) + '</label><select id="new-model-provider">' + state.providers.map(function(p,i){return '<option value="'+i+'">'+esc(p.name)+'</option>';}).join('') + '</select></div>' +
|
|
21041
21703
|
'<div class="auto-input-group"><label>' + esc(t('model.contextSize')) + '</label><input id="new-model-ctx" type="number" value="128000" placeholder="tokens"></div>' +
|
|
21042
21704
|
'<div style="display:flex;gap:10px;margin-bottom:10px;"><label style="font-size:11px;color:var(--text-dim);"><input type="checkbox" id="new-model-vision"> ' + esc(t('model.vision')) + '</label>' +
|
|
@@ -21068,7 +21730,8 @@ window.serializeThinkingTierMap = function(map) {
|
|
|
21068
21730
|
};
|
|
21069
21731
|
|
|
21070
21732
|
window.saveNewModel = function() {
|
|
21071
|
-
var name = document.getElementById('new-model-name').value;
|
|
21733
|
+
var name = document.getElementById('new-model-name').value.trim();
|
|
21734
|
+
var display = document.getElementById('new-model-display').value.trim();
|
|
21072
21735
|
var provIdx = parseInt(document.getElementById('new-model-provider').value);
|
|
21073
21736
|
var ctxEl = document.getElementById('new-model-ctx');
|
|
21074
21737
|
var visionEl = document.getElementById('new-model-vision');
|
|
@@ -21077,10 +21740,11 @@ window.saveNewModel = function() {
|
|
|
21077
21740
|
var tierMapEl = document.getElementById('new-model-tier-map');
|
|
21078
21741
|
if (name && !isNaN(provIdx) && state.providers[provIdx]) {
|
|
21079
21742
|
if (!state.providers[provIdx].models) state.providers[provIdx].models = [];
|
|
21743
|
+
if (state.providers[provIdx].models.some(function(m) { return String(typeof m === 'string' ? m : m.name).toLowerCase() === name.toLowerCase(); })) { showUiNotice('该供应商下模型标识已存在', 'error', 'duplicate-model'); return; }
|
|
21080
21744
|
var tierMap = window.parseThinkingTierMap(tierMapEl ? tierMapEl.value : '');
|
|
21081
21745
|
var entry = {
|
|
21082
21746
|
name: name,
|
|
21083
|
-
display: name,
|
|
21747
|
+
display: display || name,
|
|
21084
21748
|
description: descEl ? descEl.value : '',
|
|
21085
21749
|
max_tokens: ctxEl ? parseInt(ctxEl.value || '128000') : 128000,
|
|
21086
21750
|
vision: !!(visionEl && visionEl.checked),
|
|
@@ -21111,8 +21775,10 @@ window.editModel = function(provIdx, modelIdx) {
|
|
|
21111
21775
|
var model = provider.models[modelIdx];
|
|
21112
21776
|
if (!model) return;
|
|
21113
21777
|
if (typeof model === 'string') model = { name: model, display: model };
|
|
21778
|
+
state.modelEditIdentity = { providerId: provider.id, modelName: model.name };
|
|
21114
21779
|
window.openSubWin(t('common.edit'), '<div class="auto-input-group"><label>' + esc(t('model.modelName')) + '</label><input id="edit-model-name" value="' + escAttr(model.name || '') + '"></div>' +
|
|
21115
|
-
'<div class="auto-input-group"><label>' + esc(t('model.
|
|
21780
|
+
'<div class="auto-input-group"><label>' + esc(t('model.displayName')) + '</label><input id="edit-model-display" value="' + escAttr(model.display || '') + '"></div>' +
|
|
21781
|
+
'<div class="auto-input-group"><label>' + esc(t('model.provider')) + '</label><select id="edit-model-provider" disabled>' + state.providers.map(function(p,i){return '<option value="'+i+'"' + (i === provIdx ? ' selected' : '') + '>'+esc(p.name)+'</option>';}).join('') + '</select></div>' +
|
|
21116
21782
|
'<div class="auto-input-group"><label>' + esc(t('model.contextSize')) + '</label><input id="edit-model-ctx" type="number" value="' + escAttr(model.max_tokens || 4096) + '"></div>' +
|
|
21117
21783
|
'<div style="display:flex;gap:10px;margin-bottom:10px;"><label style="font-size:11px;color:var(--text-dim);"><input type="checkbox" id="edit-model-vision"' + (model.vision ? ' checked' : '') + '> ' + esc(t('model.vision')) + '</label>' +
|
|
21118
21784
|
'<label style="font-size:11px;color:var(--text-dim);"><input type="checkbox" id="edit-model-thinking"' + (model.thinking ? ' checked' : '') + '> ' + esc(t('model.thinking')) + '</label></div>' +
|
|
@@ -21123,7 +21789,11 @@ window.editModel = function(provIdx, modelIdx) {
|
|
|
21123
21789
|
};
|
|
21124
21790
|
|
|
21125
21791
|
window.saveModelEdit = function(oldProvIdx, modelIdx) {
|
|
21792
|
+
var identity = state.modelEditIdentity;
|
|
21793
|
+
if (!identity) return;
|
|
21794
|
+
oldProvIdx = state.providers.findIndex(function(p) { return p.id === identity.providerId; });
|
|
21126
21795
|
var oldProvider = state.providers[oldProvIdx];
|
|
21796
|
+
modelIdx = oldProvider && oldProvider.models ? oldProvider.models.findIndex(function(m) { return (typeof m === 'string' ? m : m.name) === identity.modelName; }) : -1;
|
|
21127
21797
|
if (!oldProvider || !oldProvider.models || !oldProvider.models[modelIdx]) return;
|
|
21128
21798
|
var nameEl = document.getElementById('edit-model-name');
|
|
21129
21799
|
var provEl = document.getElementById('edit-model-provider');
|
|
@@ -21133,15 +21803,17 @@ window.saveModelEdit = function(oldProvIdx, modelIdx) {
|
|
|
21133
21803
|
var descEl = document.getElementById('edit-model-desc');
|
|
21134
21804
|
var tierMapEl = document.getElementById('edit-model-tier-map');
|
|
21135
21805
|
var name = nameEl ? nameEl.value.trim() : '';
|
|
21136
|
-
var newProvIdx =
|
|
21806
|
+
var newProvIdx = oldProvIdx;
|
|
21807
|
+
var displayEl = document.getElementById('edit-model-display');
|
|
21137
21808
|
if (!name || isNaN(newProvIdx) || !state.providers[newProvIdx]) return;
|
|
21809
|
+
if (oldProvider.models.some(function(m, i) { return i !== modelIdx && String(typeof m === 'string' ? m : m.name).toLowerCase() === name.toLowerCase(); })) { showUiNotice('该供应商下模型标识已存在', 'error', 'duplicate-model'); return; }
|
|
21138
21810
|
var previous = oldProvider.models[modelIdx];
|
|
21139
21811
|
if (typeof previous === 'string') previous = { name: previous, display: previous };
|
|
21140
21812
|
var tierMap = window.parseThinkingTierMap(tierMapEl ? tierMapEl.value : '');
|
|
21141
21813
|
var updated = Object.assign({}, previous, {
|
|
21142
21814
|
_previous_name: previous.name || name,
|
|
21143
21815
|
name: name,
|
|
21144
|
-
display: name,
|
|
21816
|
+
display: displayEl ? displayEl.value.trim() || name : name,
|
|
21145
21817
|
description: descEl ? descEl.value : '',
|
|
21146
21818
|
max_tokens: ctxEl ? parseInt(ctxEl.value || '4096') : 4096,
|
|
21147
21819
|
vision: !!(visionEl && visionEl.checked),
|
|
@@ -24312,6 +24984,34 @@ window.activateConversationWithGlass = function(idx, row) {
|
|
|
24312
24984
|
} });
|
|
24313
24985
|
};
|
|
24314
24986
|
|
|
24987
|
+
/**
|
|
24988
|
+
* dev-0.6.6 hotfix: 拖动排序期间,行的顺序归用户所有。
|
|
24989
|
+
*
|
|
24990
|
+
* 拖动过程中任何权威列表推送(重命名、跑马灯状态、其他窗口或移动端)都会走到
|
|
24991
|
+
* renderConversations()。旧实现无条件把行重排回 state 顺序,用户刚刚让出来的落点
|
|
24992
|
+
* 会被抹掉,松手时提交的就是被重置后的位置——「拖到哪就落在哪」因此失效。
|
|
24993
|
+
* 拖动期间以 DOM 现有顺序为准,只把刚出现的新行按 state 位置插进去。
|
|
24994
|
+
*/
|
|
24995
|
+
function conversationRowOrderDuringDrag(orderedRows, list) {
|
|
24996
|
+
var dragRow = conversationNativeDrag.row;
|
|
24997
|
+
if (!dragRow || dragRow.parentNode !== list) return orderedRows;
|
|
24998
|
+
var domRows = Array.prototype.slice.call(list.querySelectorAll('.conv-item'));
|
|
24999
|
+
var stateIndex = new Map();
|
|
25000
|
+
orderedRows.forEach(function(row, index) { stateIndex.set(row, index); });
|
|
25001
|
+
var sequence = domRows.filter(function(row) { return stateIndex.has(row); });
|
|
25002
|
+
if (!sequence.length) return orderedRows;
|
|
25003
|
+
orderedRows.forEach(function(row) {
|
|
25004
|
+
if (sequence.indexOf(row) >= 0) return;
|
|
25005
|
+
var index = stateIndex.get(row);
|
|
25006
|
+
var insertAt = sequence.length;
|
|
25007
|
+
for (var i = 0; i < sequence.length; i++) {
|
|
25008
|
+
if (stateIndex.get(sequence[i]) > index) { insertAt = i; break; }
|
|
25009
|
+
}
|
|
25010
|
+
sequence.splice(insertAt, 0, row);
|
|
25011
|
+
});
|
|
25012
|
+
return sequence;
|
|
25013
|
+
}
|
|
25014
|
+
|
|
24315
25015
|
function renderConversations() {
|
|
24316
25016
|
var list = els['conversation-list'];
|
|
24317
25017
|
if (!list) return;
|
|
@@ -24472,7 +25172,8 @@ function renderConversations() {
|
|
|
24472
25172
|
var stale = existingRows[id];
|
|
24473
25173
|
if (stale && stale.parentNode === list) stale.parentNode.removeChild(stale);
|
|
24474
25174
|
});
|
|
24475
|
-
orderedRows
|
|
25175
|
+
var rowsToPlace = conversationRowOrderDuringDrag(orderedRows, list);
|
|
25176
|
+
rowsToPlace.forEach(function(row, idx) {
|
|
24476
25177
|
var current = list.children[idx];
|
|
24477
25178
|
if (current === row) return;
|
|
24478
25179
|
list.insertBefore(row, current || null);
|
|
@@ -24561,6 +25262,7 @@ window.newConversation = function(workspaceReference, branchCommunication) {
|
|
|
24561
25262
|
state._promptBoundKey = conversationDraftKey();
|
|
24562
25263
|
renderConversations();
|
|
24563
25264
|
els['chat-area'].innerHTML = '';
|
|
25265
|
+
state.renderedChatTargetKey = '';
|
|
24564
25266
|
var target = currentConversationTarget(id);
|
|
24565
25267
|
var modelRevisionAtActivation = Number(state.modelSelectionRevision || 0);
|
|
24566
25268
|
var activation = api.activateConversation ? api.activateConversation(target) : (api.ensureConversation ? api.ensureConversation(target) : Promise.resolve(null));
|
|
@@ -24648,6 +25350,11 @@ window.switchConversation = function(idx) {
|
|
|
24648
25350
|
setActiveWorkspaceConversation(idx);
|
|
24649
25351
|
var activeId = activeConversationId();
|
|
24650
25352
|
var activeBrowserTarget = currentConversationTarget(activeId);
|
|
25353
|
+
// Rebind the visible composer to the selected conversation before any async
|
|
25354
|
+
// backend hydration. Users can type as soon as the row is selected; leaving
|
|
25355
|
+
// _promptBoundKey on the previous conversation would persist that input to
|
|
25356
|
+
// the wrong target while syncBackendConversation is still in flight.
|
|
25357
|
+
restoreConversationDraft(activeBrowserTarget);
|
|
24651
25358
|
var activeBrowserView = browserGuestForTarget(activeBrowserTarget);
|
|
24652
25359
|
if (activeBrowserView) setActiveBrowserGuest(activeBrowserView, activeBrowserTarget);
|
|
24653
25360
|
else if (state.rightTab === 'browser') window.ensureBrowserPanel({ target: activeBrowserTarget, activate: true }).catch(function(error) {
|
|
@@ -24774,6 +25481,7 @@ window.archiveConv = function(conversationId) {
|
|
|
24774
25481
|
if (priorActiveId === targetId) {
|
|
24775
25482
|
state.activeBackendConversationId = '';
|
|
24776
25483
|
if (els['chat-area']) els['chat-area'].innerHTML = '';
|
|
25484
|
+
state.renderedChatTargetKey = '';
|
|
24777
25485
|
if (window.renderFlowTakeover) window.renderFlowTakeover(false, '', { target: targetRuntime });
|
|
24778
25486
|
}
|
|
24779
25487
|
} else {
|
|
@@ -24786,6 +25494,7 @@ window.archiveConv = function(conversationId) {
|
|
|
24786
25494
|
if (priorActiveId === targetId) {
|
|
24787
25495
|
state.activeBackendConversationId = nextActiveId;
|
|
24788
25496
|
if (els['chat-area']) els['chat-area'].innerHTML = '';
|
|
25497
|
+
state.renderedChatTargetKey = '';
|
|
24789
25498
|
}
|
|
24790
25499
|
}
|
|
24791
25500
|
if (state.flowTakeovers) {
|
|
@@ -26665,7 +27374,15 @@ window.commitConversationDomOrder = function() {
|
|
|
26665
27374
|
var ids = Array.from(list.querySelectorAll('.conv-item')).map(function(row) {
|
|
26666
27375
|
return String(row.getAttribute('data-conversation-id') || '');
|
|
26667
27376
|
}).filter(function(id) { return !!byId[id]; });
|
|
26668
|
-
if (ids.length
|
|
27377
|
+
if (!ids.length) return renderConversations();
|
|
27378
|
+
// dev-0.6.6 hotfix: 拖动期间权威列表可能新增/删除会话(重命名、其他窗口、
|
|
27379
|
+
// 移动端)。旧实现一旦发现行数与 state 不一致就整表重渲染,用户刚拖出来的落点
|
|
27380
|
+
// 会被丢弃。这里以 DOM 顺序为准,把没有出现在 DOM 里的会话按 state 顺序补在末尾,
|
|
27381
|
+
// 保证提交的永远是一份完整、且以用户拖动结果为准的顺序。
|
|
27382
|
+
var missingIds = convs
|
|
27383
|
+
.filter(function(item) { return ids.indexOf(String(item.id)) < 0; })
|
|
27384
|
+
.map(function(item) { return String(item.id); });
|
|
27385
|
+
ids = ids.concat(missingIds);
|
|
26669
27386
|
var activeId = activeConversationId();
|
|
26670
27387
|
convs.splice.apply(convs, [0, convs.length].concat(ids.map(function(id) { return byId[id]; })));
|
|
26671
27388
|
setActiveWorkspaceConversationById(activeId);
|
|
@@ -28140,7 +28857,6 @@ function schedulePostStartupUiRendering() {
|
|
|
28140
28857
|
if (Array.isArray(s.wslDistros)) state.wslDistros = s.wslDistros;
|
|
28141
28858
|
if (s.conversationId) state.activeBackendConversationId = s.conversationId;
|
|
28142
28859
|
if (s.conversationPlan) state.conversationPlan = normalizeConversationPlan(s.conversationPlan);
|
|
28143
|
-
applyAutoRouteRatingState(s);
|
|
28144
28860
|
if (s.chatMessages) {
|
|
28145
28861
|
hydrateConversationBranchState(s);
|
|
28146
28862
|
renderChatMessages(s.chatMessages);
|
|
@@ -28148,10 +28864,11 @@ function schedulePostStartupUiRendering() {
|
|
|
28148
28864
|
if (s.defaultFlow) state.defaultFlow = s.defaultFlow;
|
|
28149
28865
|
if (s.autoSwitch !== undefined) state.autoSwitch = s.autoSwitch ? 'on' : 'off';
|
|
28150
28866
|
if (s.autoSwitchScope) state.autoSwitchScope = s.autoSwitchScope === 'provider' ? 'provider' : 'all';
|
|
28867
|
+
window.applyJevDecisionModelState(s);
|
|
28151
28868
|
if (s.openAIApiMode) state.openAIApiMode = (s.openAIApiMode === 'chat' || s.openAIApiMode === 'responses') ? s.openAIApiMode : 'chat_stream';
|
|
28152
28869
|
if (s.fallbackOnUnavailable !== undefined) state.fallbackOnUnavailable = s.fallbackOnUnavailable ? 'on' : 'off';
|
|
28153
28870
|
if (s.switchTendency) {
|
|
28154
|
-
state.switchTendency =
|
|
28871
|
+
state.switchTendency = ['conservative', 'balanced', 'aggressive'].indexOf(s.switchTendency) >= 0 ? s.switchTendency : 'balanced';
|
|
28155
28872
|
}
|
|
28156
28873
|
if (s.providers) {
|
|
28157
28874
|
state.providers = s.providers;
|