@tea-agent/loop-agent 0.43.0-next.1 → 0.43.0-next.2
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/CHANGELOG.md +18 -0
- package/dist/application/evaluation/budget.js +19 -1
- package/dist/build-stamp.json +3 -3
- package/dist/executors/dag-pi-executor.js +762 -252
- package/dist/executors/pi-executor.js +20 -1
- package/dist/executors/pi-sdk-executor.js +64 -1
- package/dist/executors/shell-executor.js +5 -2
- package/dist/shared/frontend-execution-policy.js +22 -0
- package/dist/task/config-types.js +4 -0
- package/dist/task/source-prepare/ledger-reconciliation.js +2 -2
- package/dist/task/source-prepare/ledger-review.js +6 -9
- package/dist/task/source-prepare/semantic-intake.js +16 -26
- package/dist/task/source-prepare/source-fidelity-pi.js +26 -7
- package/dist/worker/observe/node-transparency.js +81 -72
- package/dist/worker/observe/routes.js +20 -1
- package/dist/worker/observe/static/dag-inspector-humanize.js +3 -0
- package/dist/worker/observe/static/dom.js +20 -1
- package/dist/worker/observe/static/format-pool.d.ts +2 -0
- package/dist/worker/observe/static/format-pool.js +6 -0
- package/dist/worker/observe/static/format.js +7 -0
- package/dist/worker/observe/static/inspect-workspace.js +34 -7
- package/dist/worker/observe/static/inspector-submission.js +32 -0
- package/dist/worker/observe/static/kpi.js +1 -0
- package/dist/worker/observe/static/relations.js +2 -0
- package/dist/worker/observe/static/router.js +13 -0
- package/dist/worker/observe/static/run-processing.js +2 -0
- package/dist/worker/observe/static/shell-chrome.js +36 -3
- package/dist/worker/observe/static/state.js +35 -2
- package/dist/worker/observe/static/styles.css +260 -39
- package/dist/worker/observe/static/task-failure-labels.d.ts +4 -0
- package/dist/worker/observe/static/task-failure-labels.js +67 -0
- package/dist/worker/observe/static/task-history.js +12 -0
- package/dist/worker/observe/static/views/batch.js +6 -13
- package/dist/worker/observe/static/views/dag-graph.js +50 -3
- package/dist/worker/observe/static/views/dag-inspector.js +746 -265
- package/dist/worker/observe/static/views/dag-trajectory.js +3 -0
- package/dist/worker/observe/static/views/dag.d.ts +6 -0
- package/dist/worker/observe/static/views/dag.js +48 -10
- package/dist/worker/observe/static/views/dags.js +2 -0
- package/dist/worker/observe/static/views/dashboard.js +21 -12
- package/dist/worker/observe/static/views/failures.js +21 -11
- package/dist/worker/observe/static/views/feature.js +11 -29
- package/dist/worker/observe/static/views/pool.js +37 -28
- package/dist/worker/observe/static/views/run.js +48 -5
- package/dist/worker/observe/static/views/session-timeline.js +189 -240
- package/dist/worker/observe/static/views/task.js +81 -62
- package/dist/workflows/dag/budget-enforcement.js +53 -3
- package/dist/workflows/dag/frontend-durable-tools.js +193 -0
- package/dist/workflows/dag/frontend-execution-groups.js +24 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +9 -0
- package/dist/workflows/dag/frontend-input-projection.js +76 -0
- package/dist/workflows/dag/frontend-plan-render.js +10 -3
- package/dist/workflows/dag/frontend-recovery-controller.js +7 -7
- package/dist/workflows/dag/frontend-recovery-lineage.js +13 -0
- package/dist/workflows/dag/frontend-recovery-run.js +4 -0
- package/dist/workflows/dag/frontend-review-scopes.js +117 -0
- package/dist/workflows/dag/frontend-session-budget.js +249 -0
- package/dist/workflows/dag/frontend-shadow-dual-write.js +20 -2
- package/dist/workflows/dag/frontend-test-execution-evidence.js +3 -2
- package/dist/workflows/dag/frontend-typed-event-store.js +11 -0
- package/dist/workflows/dag/init-hybrid.js +16 -10
- package/dist/workflows/dag/node-execution.js +32 -155
- package/dist/workflows/dag/prompt.js +4 -0
- package/dist/workflows/dag/rerun-plan.js +7 -1
- package/dist/workflows/dag/runner.js +26 -1
- package/dist/workflows/dag/types.js +6 -0
- package/docs/operations/README.md +1 -0
- package/docs/templates/frontend-design-contract.md +4 -4
- package/docs/templates/frontend-implementation-contract.schema.json +34 -2
- package/docs/templates/frontend-implementation-dag.json +5 -5
- package/package.json +1 -1
- package/skills/frontend-contract/SKILL.md +2 -1
- package/skills/frontend-contract/references/contract-protocol.md +19 -3
- package/skills/frontend-design-review/SKILL.md +12 -11
- package/skills/frontend-plan/SKILL.md +2 -2
- package/skills/frontend-plan/references/decision-contract.md +18 -5
- package/skills/frontend-review/SKILL.md +10 -11
- package/skills/frontend-scout/references/scout-evidence.md +4 -0
|
@@ -183,6 +183,13 @@ body[data-shell="observe-console"] .process-timeline-time,
|
|
|
183
183
|
color: var(--muted);
|
|
184
184
|
font-size: 12px;
|
|
185
185
|
}
|
|
186
|
+
/* 末级(当前页)窄屏截断,避免长标题/裸 ID 撑换行。 */
|
|
187
|
+
.breadcrumb .breadcrumb-current {
|
|
188
|
+
overflow: hidden;
|
|
189
|
+
min-width: 0;
|
|
190
|
+
text-overflow: ellipsis;
|
|
191
|
+
white-space: nowrap;
|
|
192
|
+
}
|
|
186
193
|
|
|
187
194
|
.breadcrumb[hidden] {
|
|
188
195
|
display: none;
|
|
@@ -274,9 +281,34 @@ body[data-shell="observe-console"] .process-timeline-time,
|
|
|
274
281
|
|
|
275
282
|
.view-heading {
|
|
276
283
|
display: grid;
|
|
277
|
-
|
|
284
|
+
grid-template-columns: auto minmax(0, 1fr) auto;
|
|
285
|
+
align-items: center;
|
|
286
|
+
gap: 4px 10px;
|
|
278
287
|
margin-bottom: var(--space-6);
|
|
279
288
|
}
|
|
289
|
+
/* 标题行:kicker + 标题 + 刷新按钮同行(按钮靠右);说明文字独占下一行。
|
|
290
|
+
用显式 grid 而非 flex-wrap:flex-basis 百分比在部分嵌套下不触发换行。 */
|
|
291
|
+
.view-heading .view-kicker {
|
|
292
|
+
grid-row: 1;
|
|
293
|
+
grid-column: 1;
|
|
294
|
+
}
|
|
295
|
+
/* 无 kicker 的页面标题从第一列开始,避免空列 + 列间距造成缩进。 */
|
|
296
|
+
.view-heading:not(:has(.view-kicker)) > h2 {
|
|
297
|
+
grid-column: 1 / 3;
|
|
298
|
+
}
|
|
299
|
+
.view-heading > h2 {
|
|
300
|
+
grid-row: 1;
|
|
301
|
+
grid-column: 2;
|
|
302
|
+
margin: 0;
|
|
303
|
+
}
|
|
304
|
+
.view-heading .view-manual-refresh {
|
|
305
|
+
grid-row: 1;
|
|
306
|
+
grid-column: 3;
|
|
307
|
+
}
|
|
308
|
+
.view-heading > p:last-of-type {
|
|
309
|
+
grid-row: 2;
|
|
310
|
+
grid-column: 1 / -1;
|
|
311
|
+
}
|
|
280
312
|
|
|
281
313
|
.view-kicker {
|
|
282
314
|
display: inline-flex;
|
|
@@ -301,7 +333,7 @@ body[data-shell="observe-console"] .process-timeline-time,
|
|
|
301
333
|
letter-spacing: -0.02em;
|
|
302
334
|
}
|
|
303
335
|
|
|
304
|
-
.view-heading > p:last-
|
|
336
|
+
.view-heading > p:last-of-type {
|
|
305
337
|
max-width: 66ch;
|
|
306
338
|
margin: 0;
|
|
307
339
|
color: var(--body);
|
|
@@ -1321,6 +1353,26 @@ body.is-resizing-dag-graph {
|
|
|
1321
1353
|
overscroll-behavior-x: contain;
|
|
1322
1354
|
scrollbar-width: thin;
|
|
1323
1355
|
}
|
|
1356
|
+
/* 溢出渐隐:边缘阴影提示还有更多 Tab(类名由 overflow 测量驱动)。 */
|
|
1357
|
+
.dag-inspector-tabs.is-overflow-start,
|
|
1358
|
+
.dag-inspector-tabs.is-overflow-end {
|
|
1359
|
+
background-repeat: no-repeat;
|
|
1360
|
+
background-size: 22px 100%;
|
|
1361
|
+
}
|
|
1362
|
+
.dag-inspector-tabs.is-overflow-start {
|
|
1363
|
+
background-image: linear-gradient(90deg, rgb(38 37 30 / 12%), rgb(38 37 30 / 0));
|
|
1364
|
+
background-position: left center;
|
|
1365
|
+
}
|
|
1366
|
+
.dag-inspector-tabs.is-overflow-end {
|
|
1367
|
+
background-image: linear-gradient(270deg, rgb(38 37 30 / 12%), rgb(38 37 30 / 0));
|
|
1368
|
+
background-position: right center;
|
|
1369
|
+
}
|
|
1370
|
+
.dag-inspector-tabs.is-overflow-start.is-overflow-end {
|
|
1371
|
+
background-image:
|
|
1372
|
+
linear-gradient(90deg, rgb(38 37 30 / 12%), rgb(38 37 30 / 0)),
|
|
1373
|
+
linear-gradient(270deg, rgb(38 37 30 / 12%), rgb(38 37 30 / 0));
|
|
1374
|
+
background-position: left center, right center;
|
|
1375
|
+
}
|
|
1324
1376
|
.dag-inspector-tab {
|
|
1325
1377
|
flex: 0 0 auto;
|
|
1326
1378
|
min-height: 30px;
|
|
@@ -1406,6 +1458,25 @@ body.is-resizing-dag-graph {
|
|
|
1406
1458
|
font-weight: 800;
|
|
1407
1459
|
letter-spacing: 0.12em;
|
|
1408
1460
|
}
|
|
1461
|
+
.inspector-page-eyebrow-row {
|
|
1462
|
+
display: flex;
|
|
1463
|
+
align-items: center;
|
|
1464
|
+
justify-content: space-between;
|
|
1465
|
+
gap: 8px;
|
|
1466
|
+
}
|
|
1467
|
+
.inspector-page-eyebrow-row .observe-icon-button {
|
|
1468
|
+
flex: 0 0 24px;
|
|
1469
|
+
width: 24px;
|
|
1470
|
+
min-width: 24px;
|
|
1471
|
+
height: 24px;
|
|
1472
|
+
min-height: 24px;
|
|
1473
|
+
font-size: 14px;
|
|
1474
|
+
}
|
|
1475
|
+
/* 收起态只保留 eyebrow 行,点击可再展开(会话级记忆)。 */
|
|
1476
|
+
.inspector-page-lead.is-collapsed h4,
|
|
1477
|
+
.inspector-page-lead.is-collapsed p {
|
|
1478
|
+
display: none;
|
|
1479
|
+
}
|
|
1409
1480
|
.dag-inspector-attempt-bar {
|
|
1410
1481
|
position: sticky;
|
|
1411
1482
|
top: calc(-1 * var(--space-4));
|
|
@@ -2029,13 +2100,16 @@ body.is-resizing-dag-graph {
|
|
|
2029
2100
|
margin: 0 2px;
|
|
2030
2101
|
pointer-events: none;
|
|
2031
2102
|
}
|
|
2103
|
+
/* display:flex 会盖过 UA 的 [hidden],贴底/无溢出隐藏 dock 需显式声明。 */
|
|
2104
|
+
.process-timeline-autoscroll-dock[hidden] {
|
|
2105
|
+
display: none;
|
|
2106
|
+
}
|
|
2032
2107
|
.process-timeline-autoscroll {
|
|
2033
2108
|
position: relative;
|
|
2034
2109
|
pointer-events: auto;
|
|
2035
2110
|
display: inline-flex;
|
|
2036
2111
|
align-items: center;
|
|
2037
2112
|
justify-content: center;
|
|
2038
|
-
gap: 7px;
|
|
2039
2113
|
width: 36px;
|
|
2040
2114
|
height: 36px;
|
|
2041
2115
|
padding: 0;
|
|
@@ -2044,7 +2118,9 @@ body.is-resizing-dag-graph {
|
|
|
2044
2118
|
background: color-mix(in srgb, var(--surface) 94%, var(--canvas));
|
|
2045
2119
|
color: var(--body);
|
|
2046
2120
|
box-shadow: 0 8px 24px rgb(38 37 30 / 14%);
|
|
2047
|
-
font-size:
|
|
2121
|
+
font-size: 11px;
|
|
2122
|
+
font-weight: 650;
|
|
2123
|
+
white-space: nowrap;
|
|
2048
2124
|
cursor: pointer;
|
|
2049
2125
|
backdrop-filter: blur(10px);
|
|
2050
2126
|
}
|
|
@@ -2080,25 +2156,22 @@ body.is-resizing-dag-graph {
|
|
|
2080
2156
|
outline: none;
|
|
2081
2157
|
box-shadow: 0 8px 24px rgb(38 37 30 / 16%), 0 0 0 3px var(--orange-soft);
|
|
2082
2158
|
}
|
|
2083
|
-
.process-timeline-autoscroll.
|
|
2084
|
-
border-color: var(--ink);
|
|
2085
|
-
background: var(--ink);
|
|
2086
|
-
color: #fff;
|
|
2087
|
-
}
|
|
2088
|
-
.process-timeline-autoscroll.is-active:hover,
|
|
2089
|
-
.process-timeline-autoscroll.is-active:focus-visible {
|
|
2159
|
+
.process-timeline-autoscroll.has-unread {
|
|
2090
2160
|
border-color: var(--orange);
|
|
2091
|
-
|
|
2092
|
-
color: #fff;
|
|
2161
|
+
color: var(--orange-active);
|
|
2093
2162
|
}
|
|
2094
2163
|
.process-timeline-autoscroll-signal {
|
|
2164
|
+
display: none;
|
|
2165
|
+
position: absolute;
|
|
2166
|
+
top: 5px;
|
|
2167
|
+
right: 5px;
|
|
2095
2168
|
width: 5px;
|
|
2096
2169
|
height: 5px;
|
|
2097
2170
|
border-radius: 50%;
|
|
2098
|
-
background: var(--muted-soft);
|
|
2099
|
-
}
|
|
2100
|
-
.process-timeline-autoscroll.is-active .process-timeline-autoscroll-signal {
|
|
2101
2171
|
background: var(--orange);
|
|
2172
|
+
}
|
|
2173
|
+
.process-timeline-autoscroll.has-unread .process-timeline-autoscroll-signal {
|
|
2174
|
+
display: block;
|
|
2102
2175
|
box-shadow: 0 0 0 4px rgb(245 78 0 / 16%);
|
|
2103
2176
|
animation: process-timeline-live-pulse 1.8s ease-out infinite;
|
|
2104
2177
|
}
|
|
@@ -2107,7 +2180,7 @@ body.is-resizing-dag-graph {
|
|
|
2107
2180
|
75% { box-shadow: 0 0 0 7px rgb(245 78 0 / 0%); }
|
|
2108
2181
|
}
|
|
2109
2182
|
@media (prefers-reduced-motion: reduce) {
|
|
2110
|
-
.process-timeline-autoscroll.
|
|
2183
|
+
.process-timeline-autoscroll.has-unread .process-timeline-autoscroll-signal {
|
|
2111
2184
|
animation: none;
|
|
2112
2185
|
}
|
|
2113
2186
|
.process-timeline-autoscroll::after {
|
|
@@ -2119,28 +2192,6 @@ body.is-resizing-dag-graph {
|
|
|
2119
2192
|
flex-wrap: wrap;
|
|
2120
2193
|
gap: 4px;
|
|
2121
2194
|
}
|
|
2122
|
-
.process-timeline-toggle {
|
|
2123
|
-
min-height: 28px;
|
|
2124
|
-
padding: 0 10px;
|
|
2125
|
-
border: 1px solid var(--hairline-strong);
|
|
2126
|
-
border-radius: var(--radius-sm);
|
|
2127
|
-
background: var(--surface);
|
|
2128
|
-
color: var(--body);
|
|
2129
|
-
font-size: 11px;
|
|
2130
|
-
font-weight: 600;
|
|
2131
|
-
cursor: pointer;
|
|
2132
|
-
}
|
|
2133
|
-
.process-timeline-toggle:hover,
|
|
2134
|
-
.process-timeline-toggle:focus-visible {
|
|
2135
|
-
border-color: var(--orange);
|
|
2136
|
-
color: var(--orange-active);
|
|
2137
|
-
outline: none;
|
|
2138
|
-
}
|
|
2139
|
-
.process-timeline-toggle[aria-pressed="true"] {
|
|
2140
|
-
border-color: color-mix(in srgb, var(--orange) 55%, var(--hairline));
|
|
2141
|
-
background: #fff5ed;
|
|
2142
|
-
color: var(--orange-active);
|
|
2143
|
-
}
|
|
2144
2195
|
.process-timeline-body {
|
|
2145
2196
|
min-width: 0;
|
|
2146
2197
|
padding: 4px 0 8px var(--space-2);
|
|
@@ -2995,6 +3046,19 @@ body.is-resizing-dag-graph {
|
|
|
2995
3046
|
white-space: pre-wrap;
|
|
2996
3047
|
overflow-wrap: anywhere;
|
|
2997
3048
|
}
|
|
3049
|
+
.run-output-shell {
|
|
3050
|
+
position: relative;
|
|
3051
|
+
}
|
|
3052
|
+
.run-output-follow-dock {
|
|
3053
|
+
position: absolute;
|
|
3054
|
+
right: 10px;
|
|
3055
|
+
bottom: 10px;
|
|
3056
|
+
z-index: 3;
|
|
3057
|
+
pointer-events: none;
|
|
3058
|
+
}
|
|
3059
|
+
.run-output-follow-dock[hidden] {
|
|
3060
|
+
display: none;
|
|
3061
|
+
}
|
|
2998
3062
|
|
|
2999
3063
|
.artifact-list {
|
|
3000
3064
|
display: grid;
|
|
@@ -5199,3 +5263,160 @@ button.copy-command:focus-visible {
|
|
|
5199
5263
|
.prompt-restart-status.prompt-restart-error {
|
|
5200
5264
|
color: var(--red);
|
|
5201
5265
|
}
|
|
5266
|
+
|
|
5267
|
+
/* Reader controls stay within reach while the body scrolls. */
|
|
5268
|
+
.dag-inspector.is-expanded { width: calc(100vw - 48px); }
|
|
5269
|
+
.inspect-workspace-root .dag-inspector.is-expanded { width: calc(100% - 32px); }
|
|
5270
|
+
.dag-inspector-header .dag-inspector-attempt-bar { position: relative; top: auto; grid-column: 1 / -1; margin: 0; padding: 8px 0 0; border-bottom: 0; border-top: 1px solid var(--hairline); }
|
|
5271
|
+
.process-timeline-toolbar { position: sticky; top: -16px; z-index: 5; padding: 8px 0; background: var(--surface); border-bottom: 1px solid var(--hairline); }
|
|
5272
|
+
.process-timeline-search { flex: 1 1 160px; min-width: 100px; padding: 6px 8px; border: 1px solid var(--hairline-strong); border-radius: var(--radius-sm); background: var(--surface); color: var(--ink); }
|
|
5273
|
+
.process-timeline-item.is-search-match { background: color-mix(in srgb, var(--amber) 12%, var(--surface)); border-radius: var(--radius-sm); }
|
|
5274
|
+
.dag-inspector.is-expanded .inspector-page-lead { max-width: none; }
|
|
5275
|
+
.dag-inspector-content .process-timeline-code, .dag-inspector-content .process-timeline-thinking-body { max-height: none; overflow-y: visible; }
|
|
5276
|
+
@media (max-width: 700px) {
|
|
5277
|
+
.dag-inspector.is-expanded { width: calc(100vw - 24px); }
|
|
5278
|
+
.dag-inspector-controls { flex-wrap: wrap; }
|
|
5279
|
+
.dag-inspector-header { padding: 12px; }
|
|
5280
|
+
.dag-inspector-purpose, .dag-inspector-attempt-label { display: none; }
|
|
5281
|
+
.dag-inspector-attempt-preview { font-size: 11px; }
|
|
5282
|
+
}
|
|
5283
|
+
|
|
5284
|
+
.dag-action-intents, .node-relation-links { display:flex; flex-wrap:wrap; gap:8px; align-items:center; }
|
|
5285
|
+
.node-relation-links h5 { width:100%; margin:8px 0 0; }
|
|
5286
|
+
.dag-action-intents [aria-pressed="true"] { border-color:var(--orange); color:var(--orange-active); }
|
|
5287
|
+
.dag-action-card[hidden] { display:none; }
|
|
5288
|
+
.context-component { padding:10px 0; border-top:1px solid var(--hairline); }
|
|
5289
|
+
.context-component > summary { cursor:pointer; overflow-wrap:anywhere; }
|
|
5290
|
+
.context-component-body { margin-top:12px; }
|
|
5291
|
+
.context-record-heading { margin:0; color:var(--ink); font-weight:650; }
|
|
5292
|
+
.context-record-meta { margin:0; color:var(--muted); font-size:12px; line-height:1.6; overflow-wrap:anywhere; }
|
|
5293
|
+
.context-component > summary { display:list-item; color:var(--ink); }
|
|
5294
|
+
.context-component-name { font-weight:600; }
|
|
5295
|
+
.context-component-meta { display:block; margin:4px 0 0 16px; color:var(--muted); font-size:11px; font-weight:400; }
|
|
5296
|
+
.context-source-details { margin-top:12px; color:var(--muted); font-size:12px; }
|
|
5297
|
+
.context-source-details > summary { cursor:pointer; }
|
|
5298
|
+
.context-source-details .node-transparency-summary { margin-top:12px; }
|
|
5299
|
+
.inspector-operation { padding:8px 12px; border:1px solid var(--hairline); border-radius:var(--radius-sm); margin-bottom:8px; overflow-wrap:anywhere; }
|
|
5300
|
+
.dag-graph-toolbar { flex-wrap:wrap; gap:8px; }
|
|
5301
|
+
.dag-graph-search, .relation-task-picker { padding:6px 8px; border:1px solid var(--hairline); border-radius:var(--radius-sm); background:var(--surface); max-width:100%; }
|
|
5302
|
+
#view-dashboard { display:flex; flex-direction:column; }
|
|
5303
|
+
#view-dashboard[hidden] { display:none; }
|
|
5304
|
+
#view-dashboard .dashboard-workspace { order:-2; }
|
|
5305
|
+
#dashboard-kpi { padding-top:8px; padding-bottom:8px; }
|
|
5306
|
+
#dashboard-kpi .kpi-card { padding-top:12px; padding-bottom:12px; }
|
|
5307
|
+
#dashboard-kpi .kpi-value { font-size:24px; }
|
|
5308
|
+
|
|
5309
|
+
.attempt-comparison-toolbar { display:flex; gap:12px; padding-bottom:12px; }
|
|
5310
|
+
.attempt-comparison-columns { display:grid; grid-template-columns: minmax(0,1fr) minmax(0,1fr); gap:16px; }
|
|
5311
|
+
.attempt-comparison-columns > p { grid-column:1/-1; }
|
|
5312
|
+
.attempt-comparison-column { min-width:0; overflow-wrap:anywhere; }
|
|
5313
|
+
@media (max-width:700px) { .attempt-comparison-columns { grid-template-columns:minmax(0,1fr); } }
|
|
5314
|
+
|
|
5315
|
+
/* Compact controls share the existing palette and keep names available on focus. */
|
|
5316
|
+
.observe-icon-button,
|
|
5317
|
+
.dag-graph-toolbar .observe-icon-button {
|
|
5318
|
+
display: inline-grid;
|
|
5319
|
+
place-items: center;
|
|
5320
|
+
flex: 0 0 32px;
|
|
5321
|
+
width: 32px;
|
|
5322
|
+
min-width: 32px;
|
|
5323
|
+
height: 32px;
|
|
5324
|
+
min-height: 32px;
|
|
5325
|
+
padding: 0;
|
|
5326
|
+
border: 1px solid transparent;
|
|
5327
|
+
border-radius: 7px;
|
|
5328
|
+
background: transparent;
|
|
5329
|
+
color: var(--body);
|
|
5330
|
+
font-size: 17px;
|
|
5331
|
+
line-height: 1;
|
|
5332
|
+
box-shadow: none;
|
|
5333
|
+
cursor: pointer;
|
|
5334
|
+
}
|
|
5335
|
+
.observe-icon-button:hover:not(:disabled),
|
|
5336
|
+
.observe-icon-button[aria-pressed="true"] {
|
|
5337
|
+
background: var(--surface-muted);
|
|
5338
|
+
color: var(--ink);
|
|
5339
|
+
border-color: var(--hairline);
|
|
5340
|
+
transform: none;
|
|
5341
|
+
}
|
|
5342
|
+
.observe-icon-button:focus-visible,
|
|
5343
|
+
.observe-select:focus-visible {
|
|
5344
|
+
outline: 2px solid var(--orange);
|
|
5345
|
+
outline-offset: 2px;
|
|
5346
|
+
}
|
|
5347
|
+
.observe-icon-button:disabled { opacity: .35; cursor: default; }
|
|
5348
|
+
.observe-select {
|
|
5349
|
+
min-height: 32px;
|
|
5350
|
+
max-width: 100%;
|
|
5351
|
+
padding: 4px 24px 4px 10px;
|
|
5352
|
+
border: 1px solid var(--hairline);
|
|
5353
|
+
border-radius: 7px;
|
|
5354
|
+
background: var(--surface);
|
|
5355
|
+
color: var(--ink);
|
|
5356
|
+
font: inherit;
|
|
5357
|
+
font-size: 12px;
|
|
5358
|
+
cursor: pointer;
|
|
5359
|
+
}
|
|
5360
|
+
.dag-inspector-header { gap: 10px; padding: 12px 16px; }
|
|
5361
|
+
.dag-inspector-title { gap: 3px; }
|
|
5362
|
+
.dag-inspector-purpose { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
5363
|
+
.dag-inspector-controls { gap: 3px; }
|
|
5364
|
+
.dag-inspector-header .dag-inspector-attempt-bar { gap: 4px; padding-top: 8px; }
|
|
5365
|
+
.dag-inspector-attempt-label { display: none; }
|
|
5366
|
+
.dag-inspector-attempt-preview { flex: 1; gap: 8px; min-width: 0; }
|
|
5367
|
+
.dag-inspector-attempt-category { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
|
|
5368
|
+
.process-timeline-toolbar { gap: 4px; align-items: center; flex-shrink: 0; }
|
|
5369
|
+
.process-timeline-filter-group { flex: 0 0 auto; max-width: 135px; }
|
|
5370
|
+
.process-timeline-search { margin: 0 4px; height: 32px; flex: 1 1 120px; }
|
|
5371
|
+
.process-timeline-toolbar > [role="status"] { font-size: 11px; white-space: nowrap; }
|
|
5372
|
+
.dag-graph-toolbar { gap: 4px; }
|
|
5373
|
+
.dag-graph-toolbar-left { margin-right: auto; }
|
|
5374
|
+
.dag-graph-search { height: 32px; width: 160px; margin-left: 8px; }
|
|
5375
|
+
@media (max-width: 700px) {
|
|
5376
|
+
.dag-inspector-header { padding: 10px 12px; }
|
|
5377
|
+
.dag-inspector-controls { flex-wrap: nowrap; }
|
|
5378
|
+
.dag-inspector-status { padding: 2px 6px; }
|
|
5379
|
+
.dag-inspector-attempt-preview { flex-basis: 100%; padding-left: 36px; }
|
|
5380
|
+
.process-timeline-search { flex-basis: 96px; min-width: 76px; }
|
|
5381
|
+
.dag-graph-toolbar-left { flex: 1 1 100%; }
|
|
5382
|
+
.dag-graph-search { flex: 1; min-width: 96px; }
|
|
5383
|
+
}
|
|
5384
|
+
.observe-icon-button { position: relative; }
|
|
5385
|
+
.observe-icon-button::after {
|
|
5386
|
+
content: attr(data-tooltip);
|
|
5387
|
+
position: absolute;
|
|
5388
|
+
right: 0;
|
|
5389
|
+
top: calc(100% + 6px);
|
|
5390
|
+
z-index: 30;
|
|
5391
|
+
padding: 6px 8px;
|
|
5392
|
+
border-radius: 5px;
|
|
5393
|
+
background: var(--ink);
|
|
5394
|
+
color: var(--surface);
|
|
5395
|
+
font: 11px/1.4 system-ui, sans-serif;
|
|
5396
|
+
white-space: nowrap;
|
|
5397
|
+
opacity: 0;
|
|
5398
|
+
pointer-events: none;
|
|
5399
|
+
}
|
|
5400
|
+
.observe-icon-button:focus-visible::after { opacity: 1; }
|
|
5401
|
+
.risk-dag-items { display: grid; gap: 6px; padding-top: 8px; }
|
|
5402
|
+
/* The hosted Console also has .panel styles; keep the reader shell flush. */
|
|
5403
|
+
#dag-inspector { padding: 0; gap: 0; }
|
|
5404
|
+
.dag-inspector-header .dag-inspector-attempt-bar { flex-wrap: nowrap; background: transparent; }
|
|
5405
|
+
.dag-inspector-header .dag-inspector-attempt-select { min-width: 0; flex: 0 1 126px; }
|
|
5406
|
+
.dag-inspector-header .dag-inspector-attempt-preview { flex: 1 1 auto; flex-wrap: nowrap; padding: 0; }
|
|
5407
|
+
.dag-inspector-header .dag-inspector-attempt-category,
|
|
5408
|
+
.dag-inspector-header .dag-inspector-attempt-duration { display: none; }
|
|
5409
|
+
@container (max-width: 560px) {
|
|
5410
|
+
.process-timeline-toolbar { display: grid; grid-template-columns: minmax(80px, 1fr) 32px 32px 32px; gap: 4px; }
|
|
5411
|
+
.process-timeline-filter-group { grid-row: 1; grid-column: 1 / 3; width: fit-content; }
|
|
5412
|
+
.process-timeline-errors { grid-row: 1; grid-column: 3; }
|
|
5413
|
+
.process-timeline-protocol-toggle { grid-row: 1; grid-column: 4; }
|
|
5414
|
+
.process-timeline-search { grid-row: 2; grid-column: 1 / 3; width: 100%; margin: 0; }
|
|
5415
|
+
.process-search-previous { grid-row: 2; grid-column: 3; }
|
|
5416
|
+
.process-search-next { grid-row: 2; grid-column: 4; }
|
|
5417
|
+
.process-timeline-toolbar > [role="status"] { grid-row: 3; grid-column: 1 / -1; }
|
|
5418
|
+
.process-timeline-toolbar > [role="status"]:empty { display: none; }
|
|
5419
|
+
}
|
|
5420
|
+
|
|
5421
|
+
.process-search-highlight { background: #ffe082; color: #382800; border-radius: 2px; }
|
|
5422
|
+
.process-timeline-item.is-search-current { outline: 2px solid var(--amber); outline-offset: -2px; }
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 异常 Task 页业务文案映射(Observe UI,2026-09-06-observe-reading-and-polish)。
|
|
3
|
+
*
|
|
4
|
+
* 表驱动映射:Task 级 failureCategory(ProductLineFailureCategory)与
|
|
5
|
+
* 建议跟进(FOLLOW_UP_BY_CATEGORY / DAG_RECOVERY_ACTIONS)仅保留 raw token
|
|
6
|
+
* 于 title 等诊断属性,主文案一律中文业务术语。未知或缺失分类 fail-closed
|
|
7
|
+
* 原文透传(异常页是排障入口,未收录 token 比误译更有诊断价值)。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** 表一:Task 失败分类(src/worker/pool/types.ts ProductLineFailureCategory)→ 中文。 */
|
|
11
|
+
const TASK_FAILURE_CATEGORY_LABELS = {
|
|
12
|
+
SpecUnclear: "需求不明确",
|
|
13
|
+
ContractMismatch: "契约不一致",
|
|
14
|
+
ProductBug: "产品缺陷",
|
|
15
|
+
TestBug: "测试缺陷",
|
|
16
|
+
EnvFailure: "环境问题",
|
|
17
|
+
FlakyTest: "测试不稳定",
|
|
18
|
+
RiskyChange: "变更风险",
|
|
19
|
+
DependencyFailure: "依赖阻塞",
|
|
20
|
+
NeedsHuman: "需要人工",
|
|
21
|
+
Unknown: "未分类",
|
|
22
|
+
// run 级补充(read-model:state.json controller-interrupted)。
|
|
23
|
+
"controller-interrupted": "控制器中断",
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 表二:建议跟进/恢复动作 → 中文。并集覆盖
|
|
28
|
+
* src/worker/pool/failure-routing.ts FOLLOW_UP_BY_CATEGORY 与
|
|
29
|
+
* src/workflows/dag/recovery-recommendation.ts DAG_RECOVERY_ACTIONS。
|
|
30
|
+
*/
|
|
31
|
+
const FOLLOW_UP_LABELS = {
|
|
32
|
+
"spec-clarification": "澄清需求",
|
|
33
|
+
"architecture-contract-fix": "修订契约",
|
|
34
|
+
"dev-fix": "开发修复",
|
|
35
|
+
"qa-fix-test": "修复测试",
|
|
36
|
+
"env-fix": "修复环境",
|
|
37
|
+
"flaky-test-analysis": "分析不稳定测试",
|
|
38
|
+
"human-review": "人工审查",
|
|
39
|
+
"manual-review": "人工审查",
|
|
40
|
+
"unblock-dependency": "解除依赖阻塞",
|
|
41
|
+
"human-triage": "人工分诊",
|
|
42
|
+
none: "无需处理",
|
|
43
|
+
monitor: "持续观察",
|
|
44
|
+
"retry-node": "重试节点",
|
|
45
|
+
"rerun-after-fix": "修复后重跑",
|
|
46
|
+
"resume-or-reject": "恢复或拒绝",
|
|
47
|
+
"inspect-upstream": "检查上游",
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const TASK_FAILURE_UNKNOWN_LABEL = "未分类";
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Task 失败分类 → 中文;未知英文 token 原样透传(title 仍可标注 raw),
|
|
54
|
+
* 空值返回空串由调用方决定占位。
|
|
55
|
+
*/
|
|
56
|
+
export function taskFailureCategoryLabel(category) {
|
|
57
|
+
const raw = String(category ?? "").trim();
|
|
58
|
+
if (!raw) return "";
|
|
59
|
+
return TASK_FAILURE_CATEGORY_LABELS[raw] ?? raw;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** 建议跟进/恢复动作 → 中文;未知 token 原样透传,空值返回空串。 */
|
|
63
|
+
export function followUpLabel(action) {
|
|
64
|
+
const raw = String(action ?? "").trim();
|
|
65
|
+
if (!raw) return "";
|
|
66
|
+
return FOLLOW_UP_LABELS[raw] ?? raw;
|
|
67
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Merge poll/older pages without dropping the pages the reader loaded. */
|
|
2
|
+
export function mergeTaskHistoryPage(state, page, older = false) {
|
|
3
|
+
const existing = new Map(state.runs.map((run) => [run.workerRunId, run]));
|
|
4
|
+
for (const run of page.runs ?? []) existing.set(run.workerRunId, run);
|
|
5
|
+
const order = older ? [...state.runs, ...(page.runs ?? [])] : [...(page.runs ?? []), ...state.runs];
|
|
6
|
+
const seen = new Set();
|
|
7
|
+
state.runs = order.filter((run) => { if (seen.has(run.workerRunId)) return false; seen.add(run.workerRunId); return true; }).map((run) => existing.get(run.workerRunId));
|
|
8
|
+
if (older || !state.loadedOlder) state.nextBefore = page.nextBefore ?? null;
|
|
9
|
+
if (older) state.loadedOlder = true;
|
|
10
|
+
state.error = null;
|
|
11
|
+
return state;
|
|
12
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { bindManualViewRefresh, mountTaskRelationPicker } from "../shell-chrome.js";
|
|
1
2
|
/** Observe UI view module (R5). */
|
|
2
3
|
import {
|
|
3
4
|
UI_TEXT,
|
|
@@ -19,6 +20,7 @@ import {
|
|
|
19
20
|
import { clearNode, el, metaGrid, buildTable } from "../dom.js";
|
|
20
21
|
import {
|
|
21
22
|
badge,
|
|
23
|
+
batchStatusBadge,
|
|
22
24
|
badgeClass,
|
|
23
25
|
statusLabel,
|
|
24
26
|
formatTs,
|
|
@@ -71,6 +73,7 @@ import { mountBoundedEventTimeline } from "./timeline.js";
|
|
|
71
73
|
|
|
72
74
|
export async function renderBatch(batchRunId) {
|
|
73
75
|
showView("batch");
|
|
76
|
+
bindManualViewRefresh("batch", () => renderBatch(batchRunId));
|
|
74
77
|
setBreadcrumb([
|
|
75
78
|
{ label: UI_TEXT.dashboard, href: "#/" },
|
|
76
79
|
{ label: batchRunId },
|
|
@@ -88,7 +91,7 @@ export async function renderBatch(batchRunId) {
|
|
|
88
91
|
]);
|
|
89
92
|
if (snapshot) {
|
|
90
93
|
uiState.lastSnapshot = snapshot;
|
|
91
|
-
updateHeaderRefresh(snapshot.generatedAt);
|
|
94
|
+
updateHeaderRefresh(snapshot.generatedAt, { mode: "manual" });
|
|
92
95
|
}
|
|
93
96
|
if (!batch) {
|
|
94
97
|
clearNode(metaEl);
|
|
@@ -100,17 +103,7 @@ export async function renderBatch(batchRunId) {
|
|
|
100
103
|
return;
|
|
101
104
|
}
|
|
102
105
|
|
|
103
|
-
|
|
104
|
-
"batch-relations",
|
|
105
|
-
{
|
|
106
|
-
featureId: batch.featureId,
|
|
107
|
-
batchRunId: batch.batchRunId,
|
|
108
|
-
taskId: batch.tasks?.[0]?.taskId,
|
|
109
|
-
workerRunId: batch.tasks?.[0]?.workerRunId,
|
|
110
|
-
dagRunId: batch.tasks?.[0]?.dagRunId,
|
|
111
|
-
},
|
|
112
|
-
snapshot,
|
|
113
|
-
);
|
|
106
|
+
mountTaskRelationPicker("batch-relations", { featureId: batch.featureId, batchRunId: batch.batchRunId }, (batch.tasks ?? []).map((task) => ({ ...task, featureId: task.featureId ?? batch.featureId })), snapshot);
|
|
114
107
|
|
|
115
108
|
const totalDerived = derivedDurationMs(batch.startedAt, batch.finishedAt);
|
|
116
109
|
clearNode(metaEl);
|
|
@@ -123,7 +116,7 @@ export async function renderBatch(batchRunId) {
|
|
|
123
116
|
? objectRouteLink("feature", batch.featureId, snapshot)
|
|
124
117
|
: el("span", "muted", "—"),
|
|
125
118
|
],
|
|
126
|
-
["状态",
|
|
119
|
+
["状态", batchStatusBadge(batch)],
|
|
127
120
|
["开始时间", formatTs(batch.startedAt)],
|
|
128
121
|
["结束时间", formatTs(batch.finishedAt)],
|
|
129
122
|
["总耗时", formatDuration(totalDerived)],
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
edgeCollidesNonEndpointNodes,
|
|
7
7
|
routeControlEdge,
|
|
8
8
|
} from "../dag-edge-routing.js";
|
|
9
|
-
import { el } from "../dom.js";
|
|
9
|
+
import { el, iconButton } from "../dom.js";
|
|
10
10
|
import {
|
|
11
11
|
statusLabel,
|
|
12
12
|
statusSymbol,
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
currentDagGraphViewMode,
|
|
24
24
|
} from "../state.js";
|
|
25
25
|
import { describeDagNodePurpose } from "../dag-node-purpose.js";
|
|
26
|
-
import { selectDagNode } from "./dag-inspector.js";
|
|
26
|
+
import { selectDagNode, selectDagExecution } from "./dag-inspector.js";
|
|
27
27
|
import { renderTrajectoryStoryboard } from "./dag-trajectory.js";
|
|
28
28
|
|
|
29
29
|
export function svgEl(tag, attrs = {}) {
|
|
@@ -138,6 +138,10 @@ export function renderDagGraph(dag, dagRunId, existingWrap = null) {
|
|
|
138
138
|
controlLaneCount,
|
|
139
139
|
});
|
|
140
140
|
const wrap = existingWrap ?? el("div", "dag-graph-wrap");
|
|
141
|
+
const active = document.activeElement;
|
|
142
|
+
const focusedNodeId = existingWrap?.contains?.(active) ? active?.getAttribute?.("data-node-id") : null;
|
|
143
|
+
const focusedAction = existingWrap?.contains?.(active) ? active?.dataset?.graphAction : null;
|
|
144
|
+
const previousSearch = existingWrap?.querySelector(".dag-graph-search")?.value ?? "";
|
|
141
145
|
// Preserve .dag-graph-viewport DOM identity across poll refreshes so the
|
|
142
146
|
// user's active scroll/pointer target is not destroyed mid-gesture.
|
|
143
147
|
let viewport =
|
|
@@ -169,7 +173,7 @@ export function renderDagGraph(dag, dagRunId, existingWrap = null) {
|
|
|
169
173
|
}
|
|
170
174
|
toolbarLeft.appendChild(el("span", "dag-graph-summary", summaryText));
|
|
171
175
|
toolbar.appendChild(toolbarLeft);
|
|
172
|
-
const reset =
|
|
176
|
+
const reset = iconButton("重置视图", "ri-restart-line", "dag-graph-reset");
|
|
173
177
|
toolbar.appendChild(reset);
|
|
174
178
|
const appendBeforeViewport = (child) => {
|
|
175
179
|
if (viewport?.parentNode === wrap) wrap.insertBefore(child, viewport);
|
|
@@ -339,6 +343,7 @@ export function renderDagGraph(dag, dagRunId, existingWrap = null) {
|
|
|
339
343
|
const purpose = describeDagNodePurpose(node.nodeId, node);
|
|
340
344
|
const group = svgEl("g", {
|
|
341
345
|
transform: `translate(${position.x} ${position.y})`,
|
|
346
|
+
"data-node-id": node.nodeId,
|
|
342
347
|
class: `dag-graph-node status-${status}${node.nodeId === uiState.selectedDagNodeId ? " selected" : ""}${uiState.selectedDagNodeId && !related.has(node.nodeId) ? " dag-dimmed" : ""}`,
|
|
343
348
|
tabindex: 0,
|
|
344
349
|
role: "button",
|
|
@@ -402,6 +407,9 @@ export function renderDagGraph(dag, dagRunId, existingWrap = null) {
|
|
|
402
407
|
});
|
|
403
408
|
svg.appendChild(group);
|
|
404
409
|
}
|
|
410
|
+
const scale = Number(viewport.dataset.scale) || 1;
|
|
411
|
+
svg.style.width = `${Number(svg.getAttribute("width")) * scale}px`;
|
|
412
|
+
svg.style.height = `${Number(svg.getAttribute("height")) * scale}px`;
|
|
405
413
|
viewport.appendChild(svg);
|
|
406
414
|
}
|
|
407
415
|
if (viewport.parentNode !== wrap) wrap.appendChild(viewport);
|
|
@@ -476,9 +484,48 @@ export function renderDagGraph(dag, dagRunId, existingWrap = null) {
|
|
|
476
484
|
}
|
|
477
485
|
}
|
|
478
486
|
}
|
|
487
|
+
const findNode = (id) => {
|
|
488
|
+
const matches = [...viewport.querySelectorAll("[data-node-id]")].filter((element) => element.getAttribute("data-node-id") === id);
|
|
489
|
+
const key = uiState.dagInspectorAttempt?.nodeId === id ? uiState.dagInspectorAttempt.key : null;
|
|
490
|
+
return matches.find((element) => key && element.getAttribute("data-execution-key") === key) ?? matches.at(-1);
|
|
491
|
+
};
|
|
492
|
+
const center = (id, executionId = null) => {
|
|
493
|
+
const target = executionId ? [...viewport.querySelectorAll("[data-execution-id]")].find((item) => item.getAttribute("data-execution-id") === executionId) : findNode(id);
|
|
494
|
+
if (!target) return;
|
|
495
|
+
const rect = target.getBoundingClientRect(), container = viewport.getBoundingClientRect();
|
|
496
|
+
viewport.scrollTo({ left: viewport.scrollLeft + rect.left - container.left - (container.width - rect.width) / 2, top: viewport.scrollTop + rect.top - container.top - (container.height - rect.height) / 2, behavior: "smooth" });
|
|
497
|
+
};
|
|
498
|
+
const addAction = (id, label, action) => { const icons = { selected: "ri-focus-3-line", running: "ri-play-circle-line", failed: "ri-error-warning-line", fit: "ri-fullscreen-line" }; const button = iconButton(label, icons[id], "dag-graph-reset"); button.type = "button"; button.dataset.graphAction = id; button.addEventListener("click", action); toolbar.appendChild(button); return button; };
|
|
499
|
+
addAction("selected", "定位选中", () => center(uiState.selectedDagNodeId)).disabled = !findNode(uiState.selectedDagNodeId);
|
|
500
|
+
addAction("running", "定位运行中", () => { const node = nodes.find((item) => String(item.status).toLowerCase() === "running"); if (node) center(node.nodeId); }).disabled = !nodes.some((item) => String(item.status).toLowerCase() === "running");
|
|
501
|
+
const failedNodes = (mode === "trajectory" ? dag.executionTrajectory?.occurrences ?? [] : nodes).filter((item) => ["error", "failed"].includes(String(item.status).toLowerCase()));
|
|
502
|
+
const failed = addAction("failed", `下个失败 (${failedNodes.length})`, () => {
|
|
503
|
+
if (!failedNodes.length) return;
|
|
504
|
+
const index = failedNodes.findIndex((item) => item.nodeId === uiState.selectedDagNodeId && (mode !== "trajectory" || (item.pass ? `pass-${item.pass}` : null) === (uiState.dagInspectorAttempt?.key ?? null)));
|
|
505
|
+
const node = failedNodes[(index + 1) % failedNodes.length];
|
|
506
|
+
if (mode === "trajectory") selectDagExecution(dagRunId, node.nodeId, node.pass ? `pass-${node.pass}` : null);
|
|
507
|
+
else selectDagNode(dagRunId, node.nodeId);
|
|
508
|
+
requestAnimationFrame(() => center(node.nodeId, node.executionId));
|
|
509
|
+
});
|
|
510
|
+
failed.disabled = !failedNodes.some((node) => findNode(node.nodeId));
|
|
511
|
+
const fit = addAction("fit", "适应画布", () => {
|
|
512
|
+
const svg = viewport.querySelector("svg.dag-graph"); if (!svg) return;
|
|
513
|
+
const width = Number(svg.getAttribute("width")), height = Number(svg.getAttribute("height"));
|
|
514
|
+
const scale = Math.min(1, (viewport.clientWidth - 24) / width, (viewport.clientHeight - 24) / height);
|
|
515
|
+
if (!(scale > 0)) return;
|
|
516
|
+
viewport.dataset.scale = String(scale); svg.style.width = `${width * scale}px`; svg.style.height = `${height * scale}px`; viewport.scrollTo({ left: 0, top: 0 });
|
|
517
|
+
});
|
|
518
|
+
fit.disabled = mode !== "topology";
|
|
519
|
+
const search = el("input", "dag-graph-search"); search.type = "search"; search.placeholder = "查找节点…"; search.value = previousSearch; search.setAttribute("aria-label", "查找流程节点"); search.dataset.graphAction = "search";
|
|
520
|
+
search.addEventListener("keydown", (event) => { if (event.key !== "Enter" || event.isComposing) return; event.preventDefault(); const matches = nodes.filter((item) => `${item.nodeId} ${describeDagNodePurpose(item.nodeId, item)}`.toLocaleLowerCase().includes(search.value.trim().toLocaleLowerCase())); if (!matches.length) return; const index = matches.findIndex((item) => item.nodeId === uiState.selectedDagNodeId); const node = matches[(index + 1) % matches.length]; center(node.nodeId); selectDagNode(dagRunId, node.nodeId); }); toolbar.appendChild(search);
|
|
521
|
+
if (focusedNodeId) findNode(focusedNodeId)?.focus?.({ preventScroll: true });
|
|
522
|
+
else if (focusedAction) [...toolbar.querySelectorAll("[data-graph-action]")].find((item) => item.dataset.graphAction === focusedAction)?.focus?.({ preventScroll: true });
|
|
479
523
|
// Toolbar is rebuilt each paint; bind reset to the current viewport/dagRunId.
|
|
480
524
|
// 重置视图只重置当前模式的 viewport:不切换模式、不重置另一模式、不关闭 Inspector。
|
|
481
525
|
reset.addEventListener("click", () => {
|
|
526
|
+
viewport.dataset.scale = "1";
|
|
527
|
+
const svg = viewport.querySelector("svg.dag-graph");
|
|
528
|
+
if (svg) { svg.style.width = `${svg.getAttribute("width")}px`; svg.style.height = `${svg.getAttribute("height")}px`; }
|
|
482
529
|
if (mode === "trajectory") {
|
|
483
530
|
uiState.dagTrajectoryViewportState = updateDagTrajectoryViewportState(
|
|
484
531
|
uiState.dagTrajectoryViewportState,
|