dsh-activity-pane 0.14.0 → 0.15.0
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/.dsh-plugin/client.js +143 -280
- package/package.json +1 -1
- package/scripts/acceptance.mjs +11 -1
- package/scripts/check.mjs +80 -61
- package/src/client.mjs +131 -179
- package/src/core.mjs +12 -101
- package/src/host.mjs +1 -143
package/.dsh-plugin/client.js
CHANGED
|
@@ -152,6 +152,15 @@ function nextDensity(value) {
|
|
|
152
152
|
return DENSITY_ORDER[(index + 1) % DENSITY_ORDER.length];
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
/**
|
|
156
|
+
* 档位资源纪律谓词(R-01-025/AC-01):紧凑呈现档下主会话日志窗口不建立或保持。
|
|
157
|
+
* 子代理日志窗口承载标题行可见的模型上下文(R-01-012/AC-17),恒不裁剪;
|
|
158
|
+
* 输入档位经 normalizeDensity 归一,非法值视作中间档即不裁剪(R-01-025/AC-01)。
|
|
159
|
+
*/
|
|
160
|
+
function logWindowSuppressed(densityLevel, isSubagent) {
|
|
161
|
+
return normalizeDensity(densityLevel) === "compact" && isSubagent !== true;
|
|
162
|
+
}
|
|
163
|
+
|
|
155
164
|
function isRecord(value) {
|
|
156
165
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
157
166
|
}
|
|
@@ -1566,7 +1575,7 @@ function mainTitle(byId, id) {
|
|
|
1566
1575
|
* jobsBySession(R-01-023):快照携带的 `jobsBySession` 任务视图映射——存在在跑后台任务
|
|
1567
1576
|
* (status ∈ {running, stopping})的主会话获得第三种自身活动来源:保留在活动区归入运行组、
|
|
1568
1577
|
* 完成提醒与错误提醒被抑制(AC-01、AC-02);条目携带 liveJobs(startedAt 升序)供渲染层
|
|
1569
|
-
* 标注数量与构建任务行(R-01-
|
|
1578
|
+
* 标注数量与构建任务行(R-01-023)。子代理行不派生 liveJobs(后台任务当前仅主会话呈现)。
|
|
1570
1579
|
*/
|
|
1571
1580
|
function buildEntries(snapshot, workspaceItems, detailsById = {}, completions = null, delegatingIds = null, archivedIds = [], waitingStarts = null, jobsBySession = null) {
|
|
1572
1581
|
const byId = isRecord(snapshot) && isRecord(snapshot.byId) ? snapshot.byId : {};
|
|
@@ -1743,7 +1752,7 @@ function buildEntries(snapshot, workspaceItems, detailsById = {}, completions =
|
|
|
1743
1752
|
stateAt,
|
|
1744
1753
|
questionPreview: m.pending && m.row.pendingInteraction === "question" ? (questionPreview ?? null) : undefined,
|
|
1745
1754
|
});
|
|
1746
|
-
// 后台任务子条目(R-01-
|
|
1755
|
+
// 后台任务子条目(R-01-023):与子代理同形的缩进子卡,跟随母会话在其全部
|
|
1747
1756
|
// 子代理之前(即母亲条目的直接后继);结束即随 liveJobs 清空而消失(R-01-023/AC-03)。
|
|
1748
1757
|
// 复合 id 在去重视野(entries/visited/cardsById)中唯一,且携 jobs 前缀与子代理
|
|
1749
1758
|
// 会话 id 空间天然隔离;层级连接线与子代理同规则参与 trackRuns(R-01-003/AC-04),
|
|
@@ -1765,7 +1774,6 @@ function buildEntries(snapshot, workspaceItems, detailsById = {}, completions =
|
|
|
1765
1774
|
userPreview: "",
|
|
1766
1775
|
agentPreview: "",
|
|
1767
1776
|
isCurrent: false,
|
|
1768
|
-
jobId: job.id,
|
|
1769
1777
|
jobStatus: job.status,
|
|
1770
1778
|
jobStartedAt: job.startedAt,
|
|
1771
1779
|
});
|
|
@@ -2003,11 +2011,7 @@ function entryErrorNote(completion) {
|
|
|
2003
2011
|
return typeof message === "string" && message !== "" ? message : ERROR_NOTE_FALLBACK;
|
|
2004
2012
|
}
|
|
2005
2013
|
|
|
2006
|
-
// ---- 后台任务(R-01-023
|
|
2007
|
-
|
|
2008
|
-
/** 后台任务输出回放的字符上限(R-01-024/AC-04):模型每次 job_output 读取的
|
|
2009
|
-
* 已定案文本拼接后超出即截断并置 truncated;与单条错误信息截断同量级考虑。 */
|
|
2010
|
-
const JOB_OUTPUT_MAX_CHARS = 20000;
|
|
2014
|
+
// ---- 后台任务(R-01-023):在跑活性归一 ----
|
|
2011
2015
|
|
|
2012
2016
|
/** 在跑后台任务的活性状态全集(R-01-023):stopping 视同在跑——停止请求已发出但
|
|
2013
2017
|
* 任务尚未结束,呈现与提醒抑制口径与 running 一致。 */
|
|
@@ -2053,99 +2057,6 @@ function jobKindLabel(kind) {
|
|
|
2053
2057
|
return JOB_KIND_LABELS[kind] ?? kind;
|
|
2054
2058
|
}
|
|
2055
2059
|
|
|
2056
|
-
/** 从 tool-result 消息提取纯文本:tool-result 内容块内的 text 片段按换行拼接;
|
|
2057
|
-
* 非数组内容或无文本块返回 undefined(错误结果是否纳入由调用方按 isError 判定)。 */
|
|
2058
|
-
function jobResultText(message) {
|
|
2059
|
-
if (!Array.isArray(message.content)) return undefined;
|
|
2060
|
-
const parts = [];
|
|
2061
|
-
for (const block of message.content) {
|
|
2062
|
-
if (!isRecord(block) || block.type !== "tool-result") continue;
|
|
2063
|
-
if (!Array.isArray(block.content)) continue;
|
|
2064
|
-
for (const item of block.content) {
|
|
2065
|
-
if (isRecord(item) && item.type === "text" && typeof item.text === "string") parts.push(item.text);
|
|
2066
|
-
}
|
|
2067
|
-
}
|
|
2068
|
-
return parts.length > 0 ? parts.join("\n") : undefined;
|
|
2069
|
-
}
|
|
2070
|
-
|
|
2071
|
-
/** tool-result 是否为错误结果(内层 tool-result 块的 isError 标志)。 */
|
|
2072
|
-
function jobResultIsError(message) {
|
|
2073
|
-
if (!Array.isArray(message.content)) return false;
|
|
2074
|
-
return message.content.some((block) => isRecord(block) && block.type === "tool-result" && block.isError === true);
|
|
2075
|
-
}
|
|
2076
|
-
|
|
2077
|
-
/**
|
|
2078
|
-
* 从事件记录列表提取 job_output 读写轨迹(R-01-024):`tool/call` 名为 `job_output`
|
|
2079
|
-
* 的行经 `arguments.job_id`(JSON 字符串或已解析对象)登记 callId → jobId 映射,
|
|
2080
|
-
* `tool/result` 行按 `message.source.callId` 配对并携带模型收到的定案文本与错误标志。
|
|
2081
|
-
* 兼容包装记录 `{ seq, event }` 与平面事件两种形状(seq 取事件或记录顶层);seq 非法的
|
|
2082
|
-
* 事件跳过。返回按 seq 升序的去重轨迹数组(同 seq 后到者胜——实时镜像与日志重放合并
|
|
2083
|
-
* 时的自然口径)。
|
|
2084
|
-
*/
|
|
2085
|
-
function jobOutputTraces(records) {
|
|
2086
|
-
const bySeq = new Map();
|
|
2087
|
-
for (const record of Array.isArray(records) ? records : []) {
|
|
2088
|
-
const event = eventOf(record);
|
|
2089
|
-
if (!isRecord(event) || (event.type !== "tool/call" && event.type !== "tool/result")) continue;
|
|
2090
|
-
const seq = Number(event.seq ?? (isRecord(record) ? record.seq : undefined));
|
|
2091
|
-
if (!Number.isFinite(seq)) continue;
|
|
2092
|
-
const data = isRecord(event.data) ? event.data : {};
|
|
2093
|
-
if (event.type === "tool/call") {
|
|
2094
|
-
if (data.name !== "job_output" || typeof data.callId !== "string") continue;
|
|
2095
|
-
let jobId = null;
|
|
2096
|
-
if (typeof data.arguments === "string") {
|
|
2097
|
-
try {
|
|
2098
|
-
const parsed = JSON.parse(data.arguments);
|
|
2099
|
-
if (isRecord(parsed) && typeof parsed.job_id === "string") jobId = parsed.job_id;
|
|
2100
|
-
} catch {
|
|
2101
|
-
// 参数不可解析:不构成有效的 job_output 调用轨迹。
|
|
2102
|
-
}
|
|
2103
|
-
} else if (isRecord(data.arguments) && typeof data.arguments.job_id === "string") {
|
|
2104
|
-
jobId = data.arguments.job_id;
|
|
2105
|
-
}
|
|
2106
|
-
if (jobId === null) continue;
|
|
2107
|
-
bySeq.set(seq, { seq, kind: "call", callId: data.callId, jobId });
|
|
2108
|
-
} else {
|
|
2109
|
-
const message = isRecord(data.message) ? data.message : null;
|
|
2110
|
-
const callId = typeof message?.source?.callId === "string" ? message.source.callId : null;
|
|
2111
|
-
if (callId === null) continue;
|
|
2112
|
-
bySeq.set(seq, { seq, kind: "result", callId, text: jobResultText(message), isError: jobResultIsError(message) });
|
|
2113
|
-
}
|
|
2114
|
-
}
|
|
2115
|
-
return [...bySeq.values()].sort((a, b) => a.seq - b.seq);
|
|
2116
|
-
}
|
|
2117
|
-
|
|
2118
|
-
/**
|
|
2119
|
-
* 从轨迹回放指定后台任务的模型已读输出(R-01-024/AC-01):按 seq 序配对 call → result,
|
|
2120
|
-
* 拼接归属 jobId 的全部读取文本;剔除错误结果与 `(no new output)` 占位(模型视角的
|
|
2121
|
-
* 「无新内容」对人无展示价值)。返回 `{ text, truncated, read }`——read = 存在已配对
|
|
2122
|
-
* 读取(无配对时 text 为空,客户端以「尚未被读取」承接,R-01-024/AC-02);超出
|
|
2123
|
-
* limit 按字符截断并置 truncated(R-01-024/AC-04)。同 seq 轨迹去重(后到者胜),
|
|
2124
|
-
* 供日志重放与实时镜像两源合并。jobId 非法返回未读取空结果。
|
|
2125
|
-
*/
|
|
2126
|
-
function jobOutputFromTraces(traces, jobId, limit = JOB_OUTPUT_MAX_CHARS) {
|
|
2127
|
-
if (typeof jobId !== "string" || jobId === "") return { text: "", truncated: false, read: false };
|
|
2128
|
-
const bySeq = new Map();
|
|
2129
|
-
for (const trace of Array.isArray(traces) ? traces : []) {
|
|
2130
|
-
if (isRecord(trace) && Number.isFinite(Number(trace?.seq))) bySeq.set(Number(trace.seq), trace);
|
|
2131
|
-
}
|
|
2132
|
-
const jobOf = new Map();
|
|
2133
|
-
const parts = [];
|
|
2134
|
-
let read = false;
|
|
2135
|
-
for (const trace of [...bySeq.values()].sort((a, b) => a.seq - b.seq)) {
|
|
2136
|
-
if (trace?.kind === "call") {
|
|
2137
|
-
if (typeof trace.jobId === "string") jobOf.set(trace.callId, trace.jobId);
|
|
2138
|
-
} else if (jobOf.get(trace.callId) === jobId) {
|
|
2139
|
-
read = true;
|
|
2140
|
-
if (trace.isError !== true && typeof trace.text === "string" && !trace.text.startsWith("(no new output)")) {
|
|
2141
|
-
parts.push(trace.text);
|
|
2142
|
-
}
|
|
2143
|
-
}
|
|
2144
|
-
}
|
|
2145
|
-
const text = parts.join("\n");
|
|
2146
|
-
return { text: text.length > limit ? text.slice(0, limit) : text, truncated: text.length > limit, read };
|
|
2147
|
-
}
|
|
2148
|
-
|
|
2149
2060
|
/**
|
|
2150
2061
|
* 活动区→历史区迁移检测(R-01-010/AC-07):上一帧活动区 id 在本帧离开活动区且出现于
|
|
2151
2062
|
* 历史区即判定为一次迁移;彻底消失(归档、不再被会话服务列出)不判定。prevActiveIds 为上一帧
|
|
@@ -3566,12 +3477,17 @@ body:not([data-ds-dark-theme]) [data-dsh-activity-pane] .dap-workspace {
|
|
|
3566
3477
|
}
|
|
3567
3478
|
[data-dsh-activity-pane] .dap-jobs-chip[hidden] { display: none; }
|
|
3568
3479
|
/* 后台任务子卡(R-01-023/AC-05、AC-07):与子代理卡同构的两行卡面,底色在子代理卡
|
|
3569
|
-
底色上轻染任务状态点同族的蓝以相互可辨;状态点色随任务状态翻转。
|
|
3480
|
+
底色上轻染任务状态点同族的蓝以相互可辨;状态点色随任务状态翻转。
|
|
3481
|
+
子卡为纯展示(R-01-023/AC-08):默认光标,悬停不提供可点反馈。 */
|
|
3570
3482
|
[data-dsh-activity-pane] .dap-card[data-kind="job"] {
|
|
3571
3483
|
padding: 6px 10px;
|
|
3572
3484
|
border-radius: 12px;
|
|
3573
3485
|
background: color-mix(in srgb, #65a0ff 8%, rgba(25, 27, 32, 0.95));
|
|
3574
|
-
cursor:
|
|
3486
|
+
cursor: default;
|
|
3487
|
+
}
|
|
3488
|
+
[data-dsh-activity-pane] .dap-card[data-kind="job"]:hover {
|
|
3489
|
+
border-color: rgba(255, 255, 255, 0.13);
|
|
3490
|
+
filter: none;
|
|
3575
3491
|
}
|
|
3576
3492
|
/* 任务卡行 1(R-01-023/AC-05):状态点 + 工具名称 + 右缘随时钟时长;工具名不可得时
|
|
3577
3493
|
文本段隐藏,仅保留状态点与时长。 */
|
|
@@ -3599,23 +3515,6 @@ body:not([data-ds-dark-theme]) [data-dsh-activity-pane] .dap-workspace {
|
|
|
3599
3515
|
flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
3600
3516
|
font-family: var(--dsh-font-mono, monospace); font-size: 11px; line-height: 15px;
|
|
3601
3517
|
}
|
|
3602
|
-
/* 子卡输出区(R-01-024/AC-01):展开时追加于卡内底部,终端风回放。 */
|
|
3603
|
-
[data-dsh-activity-pane] .dap-jobout {
|
|
3604
|
-
min-width: 0; max-height: 160px; overflow: auto;
|
|
3605
|
-
margin: 4px 0 0;
|
|
3606
|
-
background: color-mix(in srgb, currentColor 7%, transparent);
|
|
3607
|
-
border-radius: 6px; padding: 4px 6px;
|
|
3608
|
-
}
|
|
3609
|
-
[data-dsh-activity-pane] .dap-jobout-pre {
|
|
3610
|
-
margin: 0; white-space: pre-wrap; word-break: break-word;
|
|
3611
|
-
font-family: var(--dsh-font-mono, monospace); font-size: 10px; line-height: 14px;
|
|
3612
|
-
color: color-mix(in srgb, currentColor 78%, transparent);
|
|
3613
|
-
}
|
|
3614
|
-
[data-dsh-activity-pane] .dap-jobout-hint {
|
|
3615
|
-
font-size: 10px; line-height: 14px;
|
|
3616
|
-
color: color-mix(in srgb, currentColor 55%, transparent);
|
|
3617
|
-
}
|
|
3618
|
-
[data-dsh-activity-pane] .dap-jobout-hint:empty { display: none; }
|
|
3619
3518
|
[data-dsh-activity-pane] .dap-history-line {
|
|
3620
3519
|
display: flex; align-items: center; gap: 4px; height: 15px;
|
|
3621
3520
|
min-width: 0; overflow: hidden; white-space: nowrap;
|
|
@@ -3925,10 +3824,15 @@ body:not([data-ds-dark-theme]) [data-dsh-activity-pane] .dap-card:hover {
|
|
|
3925
3824
|
body:not([data-ds-dark-theme]) [data-dsh-activity-pane] .dap-card[data-kind="subagent"] {
|
|
3926
3825
|
background: var(--dsw-specific-sidebar-fill, rgb(249, 250, 251));
|
|
3927
3826
|
}
|
|
3928
|
-
/* 后台任务子卡浅色主题与子代理卡同源(R-01-
|
|
3827
|
+
/* 后台任务子卡浅色主题与子代理卡同源(R-01-023):淡侧栏填充底,轻染同族蓝与子代理卡区分(R-01-023/AC-07)。 */
|
|
3929
3828
|
body:not([data-ds-dark-theme]) [data-dsh-activity-pane] .dap-card[data-kind="job"] {
|
|
3930
3829
|
background: color-mix(in srgb, #65a0ff 8%, var(--dsw-specific-sidebar-fill, rgb(249, 250, 251)));
|
|
3931
3830
|
}
|
|
3831
|
+
/* 纯展示子卡(R-01-023/AC-08):浅色主题同样不提供悬停反馈(回归基态描边)。 */
|
|
3832
|
+
body:not([data-ds-dark-theme]) [data-dsh-activity-pane] .dap-card[data-kind="job"]:hover {
|
|
3833
|
+
border-color: var(--dsw-alias-border-l2, rgba(0, 0, 0, 0.1));
|
|
3834
|
+
filter: none;
|
|
3835
|
+
}
|
|
3932
3836
|
body:not([data-ds-dark-theme]) [data-dsh-activity-pane] .dap-card[data-kind="recent"] {
|
|
3933
3837
|
/* 暗于活动卡的 --dsw-alias-bg-layer-2 纯白、深于窗格底色(R-01-013/AC-10、AC-11)。 */
|
|
3934
3838
|
background: rgb(243, 244, 246);
|
|
@@ -4414,12 +4318,10 @@ function apply(ctx) {
|
|
|
4414
4318
|
}
|
|
4415
4319
|
}
|
|
4416
4320
|
|
|
4417
|
-
/** 回到前台(含 bfcache 还原)时 ack
|
|
4418
|
-
* 重连后由下一次轨迹通知或重新选中收敛;选中输出随卡片渲染帧刷新)。 */
|
|
4321
|
+
/** 回到前台(含 bfcache 还原)时 ack 通道自愈:重建 SSE(连接即发全量,状态必然收敛)。 */
|
|
4419
4322
|
function resumePushChannels() {
|
|
4420
4323
|
if (!disposed) {
|
|
4421
4324
|
connectAcksStream();
|
|
4422
|
-
connectJobsStream();
|
|
4423
4325
|
}
|
|
4424
4326
|
}
|
|
4425
4327
|
const onVisibilityResume = () => {
|
|
@@ -4432,51 +4334,6 @@ function apply(ctx) {
|
|
|
4432
4334
|
window.addEventListener("pageshow", onPageShow);
|
|
4433
4335
|
connectAcksStream();
|
|
4434
4336
|
|
|
4435
|
-
// ---- 后台任务输出通道(R-01-024) ----
|
|
4436
|
-
// SSE 订阅宿主侧 job_output 轨迹通知:连接即发空快照(轨迹不入库、无全量快照语义),
|
|
4437
|
-
// 此后每个新轨迹广播 { sessionId, jobId };客户端仅对「已选中该任务」的可见卡片
|
|
4438
|
-
// 回读输出(R-01-024/AC-03)。无 EventSource 环境静默降级为选中时不自动刷新,
|
|
4439
|
-
// 不引入轮询。
|
|
4440
|
-
let jobsSource = null;
|
|
4441
|
-
|
|
4442
|
-
/** 轨迹通知应用:命中已展开该任务的 job 子卡即置脏并回读一次;其余通知忽略。 */
|
|
4443
|
-
function applyJobTraceNotice(raw) {
|
|
4444
|
-
if (disposed) return;
|
|
4445
|
-
let notice = null;
|
|
4446
|
-
try {
|
|
4447
|
-
notice = JSON.parse(raw);
|
|
4448
|
-
} catch {
|
|
4449
|
-
notice = null;
|
|
4450
|
-
}
|
|
4451
|
-
const sessionId = typeof notice?.sessionId === "string" ? notice.sessionId : null;
|
|
4452
|
-
const jobId = typeof notice?.jobId === "string" ? notice.jobId : null;
|
|
4453
|
-
if (sessionId === null || jobId === null) return;
|
|
4454
|
-
for (const [, card] of cardsById) {
|
|
4455
|
-
const el = card?.el;
|
|
4456
|
-
if (el?.dataset?.kind !== "job" || el.dataset.jobOwner !== sessionId || el.dataset.jobId !== jobId) continue;
|
|
4457
|
-
// 脏标记 + 回读钩子:仅展开中的输出区需要拉取新轨迹(R-01-024/AC-03)。
|
|
4458
|
-
el.__jobOutDirty = true;
|
|
4459
|
-
el.__jobOutReload?.();
|
|
4460
|
-
}
|
|
4461
|
-
}
|
|
4462
|
-
|
|
4463
|
-
/** (重)建 jobs SSE 连接:与 acks 通道同模式的断连自愈口径。 */
|
|
4464
|
-
function connectJobsStream() {
|
|
4465
|
-
try {
|
|
4466
|
-
jobsSource?.close();
|
|
4467
|
-
} catch {}
|
|
4468
|
-
jobsSource = null;
|
|
4469
|
-
if (disposed || typeof window.EventSource !== "function") return;
|
|
4470
|
-
try {
|
|
4471
|
-
const source = new window.EventSource(`${PANE_API_BASE}/jobs/stream`);
|
|
4472
|
-
source.addEventListener("state", (event) => applyJobTraceNotice(event.data ?? ""));
|
|
4473
|
-
jobsSource = source;
|
|
4474
|
-
} catch {
|
|
4475
|
-
jobsSource = null;
|
|
4476
|
-
}
|
|
4477
|
-
}
|
|
4478
|
-
connectJobsStream();
|
|
4479
|
-
|
|
4480
4337
|
/** 确认写回(R-01-002/AC-10~AC-12):乐观更新本地游标(签名驱动即时解除),
|
|
4481
4338
|
* 再 POST 宿主侧持久化并广播;写回失败回滚本地游标(提醒恢复),不吞异常。 */
|
|
4482
4339
|
async function ackCompletion(sessionId) {
|
|
@@ -4770,7 +4627,10 @@ function apply(ctx) {
|
|
|
4770
4627
|
// 选择),订阅与一次性 load 一并触发;子代理的目录不可用(宿主以 agent-busy
|
|
4771
4628
|
// 拒绝其模型 RPC),不建立订阅。e2e 接缝把订阅与 load 整体延后,使模型上下文
|
|
4772
4629
|
// 严格晚于时间线呈现(R-01-014/AC-03 渐进语义可观察)。
|
|
4773
|
-
|
|
4630
|
+
// 档位资源纪律(R-01-025/AC-06):紧凑档主会话的模型区域在头部行内、整体
|
|
4631
|
+
// 隐藏——模型上下文无可见消费者,不订阅不 load;切回更高档位后经既有
|
|
4632
|
+
// 一次性读取语义补齐。logWindowSuppressed 单点判定。
|
|
4633
|
+
if (!subagent && !logWindowSuppressedFor(id, byId)) {
|
|
4774
4634
|
if (e2eModelDelayMs === 0) {
|
|
4775
4635
|
subscribeModelDirectory(id, detail);
|
|
4776
4636
|
loadDirectoryOnce(id);
|
|
@@ -4789,6 +4649,10 @@ function apply(ctx) {
|
|
|
4789
4649
|
const lastSeq = Number(windowEntries.at(-1)?.event?.seq);
|
|
4790
4650
|
const deepReadNeeded =
|
|
4791
4651
|
(previewFallbackIds.has(id) || durationFallbackIds.has(id) || (subagent && subagentModelReadIds.has(id))) &&
|
|
4652
|
+
// 档位资源纪律(R-01-025/AC-03):紧凑档主会话不发起日志分页深翻——
|
|
4653
|
+
// 预览行与最近回合耗时在该档位均不可见,最近历史排序以既有可用时刻
|
|
4654
|
+
// 承载(R-01-010/AC-09 档位限定);子代理模型溯源读取不受影响。
|
|
4655
|
+
(subagent || !logWindowSuppressed(densityLevel, subagent)) &&
|
|
4792
4656
|
detail.log?.hasMore === true &&
|
|
4793
4657
|
Number.isFinite(lastSeq) &&
|
|
4794
4658
|
detail.historyDeepReadDone !== true;
|
|
@@ -4803,6 +4667,10 @@ function apply(ctx) {
|
|
|
4803
4667
|
.then(async () => {
|
|
4804
4668
|
const { events, error } = await pagedHistoryEvents({
|
|
4805
4669
|
fetchPage: async (beforeSeq) => {
|
|
4670
|
+
// 档位资源纪律(R-01-025/AC-03):分页响应点用时重读档位——
|
|
4671
|
+
// 切入紧凑档后,在途深翻不再发起下一页;中止时已取页不落地、
|
|
4672
|
+
// 保持既有详情,切回后重新入队重取。子代理模型溯源读取不受影响。
|
|
4673
|
+
if (logWindowSuppressed(densityLevel, subagent)) return null;
|
|
4806
4674
|
const value = remoteValue(
|
|
4807
4675
|
await sessionRemote.page({ address, throughSeq: lastSeq, beforeSeq, maxMessages: 50 }),
|
|
4808
4676
|
);
|
|
@@ -4810,6 +4678,10 @@ function apply(ctx) {
|
|
|
4810
4678
|
return { events: Array.isArray(value.records) ? value.records : [], hasMore: value.hasMore === true };
|
|
4811
4679
|
},
|
|
4812
4680
|
});
|
|
4681
|
+
// 切档中止的在途深翻不落地、不标记完成——historyDeepReadDone 无复位
|
|
4682
|
+
// 路径,中止即置位会让切回高档位后的预览/耗时补齐永久失效;中止时
|
|
4683
|
+
// 保持既有详情不变,切回后经 deepReadNeeded 重新入队(R-01-025/AC-01)。
|
|
4684
|
+
if (logWindowSuppressed(densityLevel, subagent)) return;
|
|
4813
4685
|
if (error) detail.historyError = error instanceof Error ? error.message : String(error);
|
|
4814
4686
|
detail.history = events;
|
|
4815
4687
|
detail.historyDeepReadDone = true;
|
|
@@ -4831,6 +4703,44 @@ function apply(ctx) {
|
|
|
4831
4703
|
}
|
|
4832
4704
|
}
|
|
4833
4705
|
|
|
4706
|
+
/** 档位资源纪律单点判定(R-01-025):档位谓词与子代理判定的唯一组合。
|
|
4707
|
+
* byId 缺省时读当帧列表快照;调用方已持有快照时传入以免重复读取。 */
|
|
4708
|
+
function logWindowSuppressedFor(id, byId = getSnapshot(sessions, "list")?.byId ?? {}) {
|
|
4709
|
+
return logWindowSuppressed(densityLevel, isSubagentRow(byId[id], byId));
|
|
4710
|
+
}
|
|
4711
|
+
|
|
4712
|
+
/** 档位资源纪律拆窗(R-01-025/AC-01):对非当前主会话的日志窗口订阅先
|
|
4713
|
+
* unsubscribe 再除名并清除窗口引用,并 dispose 会话运行时的 live 源
|
|
4714
|
+
* (openState 归 cold,底层 session/follow 流终止)——订阅回调移除不终止
|
|
4715
|
+
* 共享传输流,dispose 才真正停带宽。当前会话的窗口由外壳会话视图持有,
|
|
4716
|
+
* 不 dispose、不拆订阅(由本函数在渲染期持续执法:其切走后的下一轮渲染
|
|
4717
|
+
* 照常释放);当前会话不可判定(列表快照未就绪)时整轮跳过,不误释放。
|
|
4718
|
+
* 子代理日志窗口承载标题行可见的模型上下文,保留。切回更高档位时经既有
|
|
4719
|
+
* 渐进路径(captureSessionLog 重订阅 + session.open 重新水合)就绪,
|
|
4720
|
+
* 不新增恢复机制。 */
|
|
4721
|
+
function enforceDensityDataDiscipline() {
|
|
4722
|
+
if (densityLevel !== "compact") return;
|
|
4723
|
+
const listSnap = getSnapshot(sessions, "list");
|
|
4724
|
+
if (listSnap?.current == null) return;
|
|
4725
|
+
for (const [id, unsubscribe] of [...logSourceSubs]) {
|
|
4726
|
+
if (!logWindowSuppressedFor(id, listSnap?.byId ?? {})) continue;
|
|
4727
|
+
if (String(id) === String(listSnap.current)) continue;
|
|
4728
|
+
try {
|
|
4729
|
+
unsubscribe?.();
|
|
4730
|
+
} catch {
|
|
4731
|
+
/* 监听器已失效:除名即可 */
|
|
4732
|
+
}
|
|
4733
|
+
logSourceSubs.delete(id);
|
|
4734
|
+
const detail = sessionDetailsById.get(id) ?? null;
|
|
4735
|
+
if (detail !== null) detail.log = null;
|
|
4736
|
+
try {
|
|
4737
|
+
sessions?.binding?.(id)?.session?.dispose?.();
|
|
4738
|
+
} catch {
|
|
4739
|
+
/* dispose 不可用:订阅已断即达成本档位纪律的可用部分 */
|
|
4740
|
+
}
|
|
4741
|
+
}
|
|
4742
|
+
}
|
|
4743
|
+
|
|
4834
4744
|
/** 日志页落地后的详情派生(R-01-012、R-01-017):时间线折叠、消息预览与子代理模型上下文。
|
|
4835
4745
|
* 子代理沿用溯源链(assistant/message source.model + 请求头 effort,T-122);主会话模型
|
|
4836
4746
|
* 不经日志提取(目录链订阅 + 一次性 load 承载,见 installServiceSubscriptions 注)。
|
|
@@ -4868,6 +4778,11 @@ function apply(ctx) {
|
|
|
4868
4778
|
session = null;
|
|
4869
4779
|
}
|
|
4870
4780
|
if (session === null) return;
|
|
4781
|
+
// 档位资源纪律(R-01-025/AC-01):紧凑档主会话不建立或保持会话日志窗口——
|
|
4782
|
+
// 不订阅事件流、不发起 open 水合、不读窗口快照。判定在函数入口用时重读,
|
|
4783
|
+
// 切档在途的回调与重试不落地新窗口。子代理日志窗口承载标题行可见的
|
|
4784
|
+
// 模型上下文(R-01-012/AC-17),恒不裁剪(单点判定见 logWindowSuppressedFor)。
|
|
4785
|
+
if (logWindowSuppressedFor(id)) return;
|
|
4871
4786
|
// 日志窗口无效化订阅(事件驱动,不构成轮询,R-02-004):等待/历史卡无会话状态
|
|
4872
4787
|
// 订阅(syncLiveness 只订阅运行中),窗口推进(回合收尾、流式尾)必须经此回调
|
|
4873
4788
|
// 重读快照并重绘;否则窗口更新只能靠渲染期重读兜底——渲染一旦静默(一次性
|
|
@@ -5177,6 +5092,9 @@ function apply(ctx) {
|
|
|
5177
5092
|
const anchorTop = currentCard ? currentCard.getBoundingClientRect().top - viewportTop : null;
|
|
5178
5093
|
densityLevel = nextDensity(densityLevel);
|
|
5179
5094
|
writeStoredDensity(densityLevel);
|
|
5095
|
+
// 档位资源纪律(R-01-025/AC-01):进入紧凑档即拆主会话日志窗口订阅;
|
|
5096
|
+
// captureSessionLog 入口门控阻止在途回调重建。
|
|
5097
|
+
enforceDensityDataDiscipline();
|
|
5180
5098
|
applyDensity();
|
|
5181
5099
|
// 档位已纳入渲染签名:触发一轮同步让时间线按新档位以 lastOnly 重建;
|
|
5182
5100
|
// 锚定补偿在本轮渲染提交后执行,此时量测才含新行高
|
|
@@ -5404,8 +5322,8 @@ function apply(ctx) {
|
|
|
5404
5322
|
return [head, row, makeEl("div", "dap-trace"), makeStatsRow(), foot];
|
|
5405
5323
|
}
|
|
5406
5324
|
if (kind === "job") {
|
|
5407
|
-
// 后台任务子卡(R-01-023/AC-05
|
|
5408
|
-
//
|
|
5325
|
+
// 后台任务子卡(R-01-023/AC-05):行 1 = 状态点 + 工具名称 + 随时钟时长;
|
|
5326
|
+
// 行 2 = 任务内容原文(mono 省略 + tooltip)。
|
|
5409
5327
|
const row = makeEl("div", "dap-row");
|
|
5410
5328
|
row.append(makeEl("span", "dap-job-dot"), makeEl("span", "dap-job-kind"), makeEl("span", "dap-job-elapsed"));
|
|
5411
5329
|
const content = makeEl("div", "dap-job-content");
|
|
@@ -5898,7 +5816,7 @@ function apply(ctx) {
|
|
|
5898
5816
|
const JOB_STATUS_LABELS = { running: "运行中", stopping: "停止中", completed: "已完成", killed: "已终止", failed: "失败" };
|
|
5899
5817
|
|
|
5900
5818
|
/** 后台任务数量注(R-01-023/AC-01):母卡标题行内 `后台 ×N` 小注;任务本体以 job
|
|
5901
|
-
* 子卡呈现(R-01-
|
|
5819
|
+
* 子卡呈现(R-01-023),本注仅承载数量语义。无 liveJobs 时隐藏节点。 */
|
|
5902
5820
|
function renderJobsChip(el, entry) {
|
|
5903
5821
|
const chip = el.querySelector(".dap-jobs-chip");
|
|
5904
5822
|
if (chip === null) return;
|
|
@@ -5909,84 +5827,9 @@ function apply(ctx) {
|
|
|
5909
5827
|
if (chip.hidden !== hidden) chip.hidden = hidden;
|
|
5910
5828
|
}
|
|
5911
5829
|
|
|
5912
|
-
/**
|
|
5913
|
-
*
|
|
5914
|
-
|
|
5915
|
-
if (el.hasAttribute("data-expanded")) {
|
|
5916
|
-
el.removeAttribute("data-expanded");
|
|
5917
|
-
el.querySelector(".dap-jobout")?.remove();
|
|
5918
|
-
delete el.dataset.loadedFor;
|
|
5919
|
-
el.__jobOutReload = null;
|
|
5920
|
-
return;
|
|
5921
|
-
}
|
|
5922
|
-
const ownerId = el.dataset.jobOwner ?? "";
|
|
5923
|
-
for (const [, card] of cardsById) {
|
|
5924
|
-
const other = card?.el;
|
|
5925
|
-
if (other === el || other?.dataset?.kind !== "job" || other.dataset.jobOwner !== ownerId) continue;
|
|
5926
|
-
other.removeAttribute("data-expanded");
|
|
5927
|
-
other.querySelector(".dap-jobout")?.remove();
|
|
5928
|
-
delete other.dataset.loadedFor;
|
|
5929
|
-
other.__jobOutReload = null;
|
|
5930
|
-
}
|
|
5931
|
-
el.setAttribute("data-expanded", "");
|
|
5932
|
-
// 展开视为显式回读请求:置脏令 renderJobOutput 必发一次(含重复点选的重试语义)。
|
|
5933
|
-
el.__jobOutDirty = true;
|
|
5934
|
-
renderJobOutput(el);
|
|
5935
|
-
}
|
|
5936
|
-
|
|
5937
|
-
/** 输出区装载/刷新(R-01-024/AC-01~AC-04):按卡上归属与任务 id 回读一次模型已读
|
|
5938
|
-
* 输出——未被读取显示「尚未被读取」(AC-02),超限截断提示(AC-04),失败诚实降级。
|
|
5939
|
-
* 回读仅在展开、选中切换(=重新展开)或轨迹通知置脏时发出(R-01-024/AC-03):
|
|
5940
|
-
* loadedFor 未变且未置脏时渲染帧直达 return,不构成周期性拉取(R-02-004)。
|
|
5941
|
-
* token 使迟到的响应失效(快速切换不串内容)。 */
|
|
5942
|
-
function renderJobOutput(el) {
|
|
5943
|
-
if (!el.hasAttribute("data-expanded")) return;
|
|
5944
|
-
const sessionId = el.dataset.jobOwner ?? null;
|
|
5945
|
-
const jobId = el.dataset.jobId ?? null;
|
|
5946
|
-
if (sessionId === null || sessionId === "" || jobId === null || jobId === "") return;
|
|
5947
|
-
let out = el.querySelector(".dap-jobout");
|
|
5948
|
-
if (out === null) {
|
|
5949
|
-
out = makeEl("div", "dap-jobout");
|
|
5950
|
-
out.append(makeEl("pre", "dap-jobout-pre"), makeEl("div", "dap-jobout-hint"));
|
|
5951
|
-
el.append(out);
|
|
5952
|
-
}
|
|
5953
|
-
const loadedFor = el.dataset.loadedFor;
|
|
5954
|
-
const key = `${sessionId}/${jobId}`;
|
|
5955
|
-
if (loadedFor === key && el.__jobOutDirty !== true) return;
|
|
5956
|
-
el.dataset.loadedFor = key;
|
|
5957
|
-
el.__jobOutDirty = false;
|
|
5958
|
-
const pre = out.querySelector(".dap-jobout-pre");
|
|
5959
|
-
const hint = out.querySelector(".dap-jobout-hint");
|
|
5960
|
-
const token = `${sessionId} ${jobId} ${Date.now()}`;
|
|
5961
|
-
el.__jobOutToken = token;
|
|
5962
|
-
// jobs SSE 轨迹通知按此钩子触发回读(R-01-024/AC-03);随卡片重建自然失效。
|
|
5963
|
-
el.__jobOutReload = () => renderJobOutput(el);
|
|
5964
|
-
pre.textContent = "";
|
|
5965
|
-
hint.textContent = "读取中…";
|
|
5966
|
-
fetch(`${PANE_API_BASE}/jobs-output?sessionId=${encodeURIComponent(sessionId)}&jobId=${encodeURIComponent(jobId)}`)
|
|
5967
|
-
.then((response) => (response.ok ? response.json() : null))
|
|
5968
|
-
.then((payload) => {
|
|
5969
|
-
if (el.__jobOutToken !== token || disposed) return;
|
|
5970
|
-
if (payload === null || typeof payload !== "object") {
|
|
5971
|
-
hint.textContent = "输出读取失败";
|
|
5972
|
-
return;
|
|
5973
|
-
}
|
|
5974
|
-
pre.textContent = typeof payload.text === "string" ? payload.text : "";
|
|
5975
|
-
hint.textContent =
|
|
5976
|
-
payload.read === false
|
|
5977
|
-
? "尚未被读取"
|
|
5978
|
-
: payload.truncated === true
|
|
5979
|
-
? "输出过长,已截断"
|
|
5980
|
-
: "";
|
|
5981
|
-
})
|
|
5982
|
-
.catch(() => {
|
|
5983
|
-
if (el.__jobOutToken === token && !disposed) hint.textContent = "输出读取失败";
|
|
5984
|
-
});
|
|
5985
|
-
}
|
|
5986
|
-
|
|
5987
|
-
/** 后台任务子卡渲染(R-01-023/AC-01、R-01-024/AC-01):状态点着色、标题与随时钟
|
|
5988
|
-
* 推进的已运行时长;展开态下装载/刷新输出区。任务消失由条目派生侧收卡(随
|
|
5989
|
-
* liveJobs 清空整个条目消失),此处只呈现当帧状态。 */
|
|
5830
|
+
/** 后台任务子卡渲染(R-01-023/AC-01、AC-05):状态点着色、标题与随时钟推进的
|
|
5831
|
+
* 已运行时长。任务消失由条目派生侧收卡(随 liveJobs 清空整个条目消失),
|
|
5832
|
+
* 此处只呈现当帧状态。 */
|
|
5990
5833
|
function renderJobCardInto(el, entry) {
|
|
5991
5834
|
const dot = el.querySelector(".dap-job-dot");
|
|
5992
5835
|
if (dot !== null && dot.dataset.status !== entry.jobStatus) dot.dataset.status = entry.jobStatus;
|
|
@@ -5999,8 +5842,7 @@ function apply(ctx) {
|
|
|
5999
5842
|
if (kindEl.hidden !== kindHidden) kindEl.hidden = kindHidden;
|
|
6000
5843
|
}
|
|
6001
5844
|
// 任务内容行(R-01-023/AC-05、AC-06):mono 原文单行省略,完整原文以原生 tooltip
|
|
6002
|
-
//
|
|
6003
|
-
// 不补空白或占位。
|
|
5845
|
+
// 显示(tooltip 归内容行);内容不可得时整行隐藏,不补空白或占位。
|
|
6004
5846
|
const contentRow = el.querySelector(".dap-job-content");
|
|
6005
5847
|
const labelText = String(entry.title ?? "");
|
|
6006
5848
|
if (contentRow !== null) {
|
|
@@ -6022,9 +5864,6 @@ function apply(ctx) {
|
|
|
6022
5864
|
: "";
|
|
6023
5865
|
if (elapsed.textContent !== elapsedText) elapsed.textContent = elapsedText;
|
|
6024
5866
|
}
|
|
6025
|
-
if (el.dataset.jobOwner !== String(entry.parentId)) el.dataset.jobOwner = String(entry.parentId);
|
|
6026
|
-
if (el.dataset.jobId !== String(entry.jobId)) el.dataset.jobId = String(entry.jobId);
|
|
6027
|
-
renderJobOutput(el);
|
|
6028
5867
|
}
|
|
6029
5868
|
|
|
6030
5869
|
function renderCardInto(el, entry, colorByWorkspace) {
|
|
@@ -6145,7 +5984,9 @@ function apply(ctx) {
|
|
|
6145
5984
|
const jobsOnly = Array.isArray(entry.liveJobs) && entry.liveJobs.length > 0 && entry.selfRunning !== true;
|
|
6146
5985
|
renderJobsChip(el, entry);
|
|
6147
5986
|
const progressRow = el.querySelector(".dap-progress");
|
|
6148
|
-
|
|
5987
|
+
// 档位资源纪律(R-01-025/AC-05):进度行在中间/紧凑档被 CSS 隐藏,不写入;
|
|
5988
|
+
// 切回完整档后随档位签名重渲染恢复。
|
|
5989
|
+
if (progressRow !== null && densityLevel === "full") {
|
|
6149
5990
|
if (jobsOnly) {
|
|
6150
5991
|
if (!progressRow.hidden) progressRow.hidden = true;
|
|
6151
5992
|
} else {
|
|
@@ -6154,8 +5995,10 @@ function apply(ctx) {
|
|
|
6154
5995
|
}
|
|
6155
5996
|
}
|
|
6156
5997
|
const traceContainer = el.querySelector(".dap-trace");
|
|
6157
|
-
|
|
6158
|
-
|
|
5998
|
+
// 档位资源纪律(R-01-025/AC-05):紧凑档不构建被隐藏的时间线内容;
|
|
5999
|
+
// 中间档 lastOnly 单行语义不变(R-01-021/AC-08)。切回后随档位签名重渲染恢复。
|
|
6000
|
+
if (traceContainer !== null && densityLevel !== "compact") renderTimelineArea(traceContainer, entry, { lastOnly: densityLevel === "medium" });
|
|
6001
|
+
if (densityLevel === "full") renderTokenStats(el, entry);
|
|
6159
6002
|
return;
|
|
6160
6003
|
}
|
|
6161
6004
|
|
|
@@ -6166,10 +6009,13 @@ function apply(ctx) {
|
|
|
6166
6009
|
|
|
6167
6010
|
if (entry.kind === "subagent") {
|
|
6168
6011
|
const traceContainer = el.querySelector(".dap-subtrace");
|
|
6169
|
-
|
|
6012
|
+
// 档位资源纪律(R-01-025/AC-05):紧凑档不构建被隐藏的子卡时间线内容;
|
|
6013
|
+
// 标题行内联模型上下文(R-01-012/AC-17)不受影响。
|
|
6014
|
+
if (traceContainer !== null && densityLevel !== "compact") renderTimelineArea(traceContainer, entry, { lastOnly: true });
|
|
6170
6015
|
// 运行中呈现与运行卡同构的进度行;锚点空闲(暂停等待)时整行隐藏(R-01-009/AC-15)。
|
|
6171
6016
|
const progressRow = el.querySelector(".dap-progress");
|
|
6172
|
-
|
|
6017
|
+
// 档位资源纪律(R-01-025/AC-05):进度行与统计行在中间/紧凑档不写入。
|
|
6018
|
+
if (progressRow !== null && densityLevel === "full") {
|
|
6173
6019
|
if (Number.isFinite(entry.progress)) {
|
|
6174
6020
|
renderProgressRow(el, entry.progress);
|
|
6175
6021
|
if (progressRow.hidden) progressRow.hidden = false;
|
|
@@ -6177,7 +6023,7 @@ function apply(ctx) {
|
|
|
6177
6023
|
progressRow.hidden = true;
|
|
6178
6024
|
}
|
|
6179
6025
|
}
|
|
6180
|
-
renderTokenStats(el, entry);
|
|
6026
|
+
if (densityLevel === "full") renderTokenStats(el, entry);
|
|
6181
6027
|
return;
|
|
6182
6028
|
}
|
|
6183
6029
|
|
|
@@ -6198,14 +6044,18 @@ function apply(ctx) {
|
|
|
6198
6044
|
restoreTextField(text, previews[i]);
|
|
6199
6045
|
}
|
|
6200
6046
|
}
|
|
6201
|
-
|
|
6047
|
+
// 档位资源纪律(R-01-025/AC-05):最近卡统计行在中间/紧凑档不写入。
|
|
6048
|
+
if (densityLevel === "full") renderTokenStats(el, entry);
|
|
6202
6049
|
}
|
|
6203
6050
|
|
|
6204
6051
|
if (entry.kind === "awaiting") {
|
|
6205
6052
|
const traceContainer = el.querySelector(".dap-trace");
|
|
6206
|
-
|
|
6053
|
+
// 档位资源纪律(R-01-025/AC-05):紧凑档不构建被隐藏的时间线内容;
|
|
6054
|
+
// 中间档 lastOnly 单行语义不变(R-01-021/AC-08)。
|
|
6055
|
+
if (traceContainer !== null && densityLevel !== "compact") renderTimelineArea(traceContainer, entry, { lastOnly: densityLevel === "medium" });
|
|
6207
6056
|
removeAwaitingHeadDuration(el);
|
|
6208
|
-
|
|
6057
|
+
// 档位资源纪律(R-01-025/AC-05):等待卡统计行在中间/紧凑档不写入。
|
|
6058
|
+
if (densityLevel === "full") renderTokenStats(el, entry);
|
|
6209
6059
|
const confirm = el.querySelector(".dap-confirm");
|
|
6210
6060
|
if (confirm !== null) {
|
|
6211
6061
|
// 激活锚点:只在结构重建时绑一次(卡片按 id 复用,kind 变化会重建骨架)。
|
|
@@ -6276,11 +6126,18 @@ function apply(ctx) {
|
|
|
6276
6126
|
}
|
|
6277
6127
|
// 子代理事件流仅接受持久父地址:open 前先安装(T-151),否则宿主拒绝、窗口永不水合。
|
|
6278
6128
|
ensureSubagentAddress(id, session);
|
|
6129
|
+
// 档位资源纪律(R-01-025/AC-01):紧凑档主会话不发起 open 水合——轮内状态
|
|
6130
|
+
// 订阅(本函数职责,R-02-004)保留,liveness 记账与正常路径共用下方代码,
|
|
6131
|
+
// 日志窗口水合在切回更高档位后经渲染期重试重建。子代理模型上下文依赖其
|
|
6132
|
+
// 窗口,不裁剪(单点判定见 logWindowSuppressedFor)。
|
|
6133
|
+
const livenessSuppressed = logWindowSuppressedFor(id);
|
|
6279
6134
|
let opening = null;
|
|
6280
|
-
|
|
6281
|
-
|
|
6282
|
-
|
|
6283
|
-
|
|
6135
|
+
if (!livenessSuppressed) {
|
|
6136
|
+
try {
|
|
6137
|
+
opening = session.open?.();
|
|
6138
|
+
} catch {
|
|
6139
|
+
opening = null;
|
|
6140
|
+
}
|
|
6284
6141
|
}
|
|
6285
6142
|
if (opening && typeof opening.then === "function") {
|
|
6286
6143
|
const tracked = Promise.resolve(opening).then(
|
|
@@ -6499,13 +6356,11 @@ function apply(ctx) {
|
|
|
6499
6356
|
if (rec === undefined) {
|
|
6500
6357
|
const el = document.createElement("div");
|
|
6501
6358
|
el.className = CARD_CLASS;
|
|
6502
|
-
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
|
|
6506
|
-
|
|
6507
|
-
return;
|
|
6508
|
-
}
|
|
6359
|
+
// 后台任务子卡为纯展示(R-01-023/AC-08):不绑激活监听,job 复合 id 亦非会话
|
|
6360
|
+
// id,不存在可跳转目标。job 复合 id 与会话 id 空间隔离(`job:` 前缀),reuseMap
|
|
6361
|
+
// 按条目 id 复用,同一 card 元素不会跨 kind 复用——创建时按 entry.kind 一次性
|
|
6362
|
+
// 决定是否绑激活是安全的;若 id 规则变化须同步复核此处。
|
|
6363
|
+
const unbind = entry.kind === "job" ? null : bindCardActivation(el, (sessionId) => {
|
|
6509
6364
|
if (typeof sessions?.open !== "function") return;
|
|
6510
6365
|
lastActivatedId = sessionId;
|
|
6511
6366
|
// 新激活意图取代一切旧重试链,避免过期链条稍后把当前会话拽回旧目标;
|
|
@@ -6534,8 +6389,14 @@ function apply(ctx) {
|
|
|
6534
6389
|
rec.el.dataset.kind = entry.kind;
|
|
6535
6390
|
rec.el.dataset.sessionId = entry.id;
|
|
6536
6391
|
rec.el.dataset.depth = String(entry.depth ?? 0);
|
|
6537
|
-
|
|
6538
|
-
|
|
6392
|
+
// 纯展示子卡(R-01-023/AC-08)不进入按钮语义与 tab 序。
|
|
6393
|
+
if (entry.kind === "job") {
|
|
6394
|
+
rec.el.removeAttribute("role");
|
|
6395
|
+
rec.el.removeAttribute("tabindex");
|
|
6396
|
+
} else {
|
|
6397
|
+
rec.el.setAttribute("role", "button");
|
|
6398
|
+
rec.el.tabIndex = 0;
|
|
6399
|
+
}
|
|
6539
6400
|
rec.el.replaceChildren(...cardChildren(entry.kind));
|
|
6540
6401
|
rec.kind = entry.kind;
|
|
6541
6402
|
}
|
|
@@ -7077,6 +6938,10 @@ function apply(ctx) {
|
|
|
7077
6938
|
}
|
|
7078
6939
|
const detailIds = [...active, ...recent].map((entry) => entry.id);
|
|
7079
6940
|
detailIds.sort((a, b) => Number(String(b) === String(snapshot?.current)) - Number(String(a) === String(snapshot?.current)));
|
|
6941
|
+
// 档位资源纪律(R-01-025/AC-01):渲染期持续执法——进入紧凑档即拆主会话窗口;
|
|
6942
|
+
// 拆窗时被豁免的当前会话在切走后的下一轮渲染照常释放。幂等:主会话拆窗后
|
|
6943
|
+
// 不在 logSourceSubs,重复执法为无操作。
|
|
6944
|
+
enforceDensityDataDiscipline();
|
|
7080
6945
|
loadNativeDetails({ ids: detailIds, previewFallbackIds, durationFallbackIds, subagentModelReadIds });
|
|
7081
6946
|
// 累计运行时长懒回填(R-01-020/AC-05):对可见主会话触发宿主侧存量回合补齐;
|
|
7082
6947
|
// 宿主侧每会话单飞,重复触发由宿主合并;完成后经 busy SSE 广播推送。
|
|
@@ -7464,8 +7329,6 @@ function apply(ctx) {
|
|
|
7464
7329
|
pendingUnsubscribe?.();
|
|
7465
7330
|
acksSource?.close();
|
|
7466
7331
|
acksSource = null;
|
|
7467
|
-
jobsSource?.close();
|
|
7468
|
-
jobsSource = null;
|
|
7469
7332
|
busySource?.close();
|
|
7470
7333
|
busySource = null;
|
|
7471
7334
|
document.removeEventListener("visibilitychange", onBusyVisibilityResume);
|