magic-agent 2.4.3 → 2.6.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/CLIENT-CONTRACT.md +284 -30
- package/README.md +15 -14
- package/client-ui/pc/app.css +32 -0
- package/client-ui/pc/js/04-task-list.js +5 -1
- package/client-ui/pc/js/07-chat-stream-render.js +3 -3
- package/client-ui/pc/js/90-glue.js +37 -2
- package/client-ui/pc/js/92-composer.js +19 -4
- package/client-ui/pc/js/93-rail.js +16 -5
- package/client-ui/shared/conv/02-media-kinds.js +2 -2
- package/client-ui/shared/conv/03-chat-message.js +241 -23
- package/client-ui/shared/conv/06-ask-answer.js +75 -1
- package/client-ui/shared/conv/09-stream-apply.js +63 -7
- package/client-ui/shared/conv/19-attachments.js +157 -6
- package/client-ui/shared/conv/27-conv-ledger.js +5 -2
- package/client-ui/shared/conv/29-msg-variants.js +81 -0
- package/client-ui/shared/conv/conv.css +79 -19
- package/client-ui/shared/conv/dom-map.md +1 -0
- package/client-ui/shared/conversation.css +13 -2
- package/client-ui/shared/conversation.js +336 -50
- package/client-ui/shared/transport-http.js +14 -0
- package/client-ui-mobile/index.html +2932 -333
- package/client-ui-mobile/reqs.css +102 -0
- package/client-ui-mobile/reqs.js +485 -70
- package/client-ui-mobile/reqshot.js +505 -0
- package/client-ui-mobile/scripts.css +21 -0
- package/client-ui-mobile/scripts.js +539 -0
- package/client-ui-mobile/vendor/cose-base.js +3214 -0
- package/client-ui-mobile/vendor/cytoscape-fcose.js +1549 -0
- package/client-ui-mobile/vendor/cytoscape.min.js +32 -0
- package/client-ui-mobile/vendor/layout-base.js +5230 -0
- package/cmd/magic-client/browsectl.go +199 -0
- package/cmd/magic-client/clientctl.go +33 -9
- package/cmd/magic-client/main.go +57 -26
- package/internal/agent/ask.go +45 -0
- package/internal/agent/ask_test.go +39 -0
- package/internal/agent/attachments_test.go +6 -2
- package/internal/agent/capability.go +7 -1
- package/internal/agent/claude.go +20 -15
- package/internal/agent/codebuddy.go +125 -34
- package/internal/agent/codebuddy_account_test.go +54 -0
- package/internal/agent/codebuddy_stream_test.go +103 -0
- package/internal/agent/credits.go +713 -0
- package/internal/agent/credits_minimax_test.go +97 -0
- package/internal/agent/credits_test.go +211 -0
- package/internal/agent/custom_models.go +155 -0
- package/internal/agent/custom_models_test.go +125 -0
- package/internal/agent/dsh.go +123 -49
- package/internal/agent/dsh_resume.go +13 -2
- package/internal/agent/dsh_test.go +108 -6
- package/internal/agent/engine.go +37 -1
- package/internal/agent/engine_base.go +21 -17
- package/internal/agent/engine_base_test.go +2 -2
- package/internal/agent/engine_test.go +47 -86
- package/internal/agent/fallback.go +313 -0
- package/internal/agent/fallback_test.go +340 -0
- package/internal/agent/launch.go +43 -1
- package/internal/agent/llmhttp.go +46 -4
- package/internal/agent/llmhttp_test.go +157 -0
- package/internal/agent/models.go +53 -1
- package/internal/agent/models_test.go +20 -5
- package/internal/agent/pidalive_windows_test.go +40 -0
- package/internal/agent/runcmd.go +26 -0
- package/internal/agent/runcmd_windows.go +36 -5
- package/internal/agent/stream.go +136 -6
- package/internal/agent/stream_test.go +25 -38
- package/internal/agent/stream_timeline_test.go +2 -37
- package/internal/agent/streamjson.go +108 -6
- package/internal/agent/tools_suffix_test.go +6 -3
- package/internal/agent/trae.go +50 -243
- package/internal/agent/trae_exec.go +433 -0
- package/internal/agent/trae_exec_test.go +181 -0
- package/internal/agent/wraplaunch_test.go +113 -0
- package/internal/cli/ask.go +160 -37
- package/internal/cli/ask_test.go +1 -1
- package/internal/cli/checkin.go +77 -0
- package/internal/cli/clientcmd.go +3 -2
- package/internal/cli/control.go +46 -5
- package/internal/cli/control_test.go +65 -0
- package/internal/cli/events.go +4 -0
- package/internal/cli/quotas.go +125 -0
- package/internal/cli/requirements.go +51 -0
- package/internal/cli/root.go +122 -5
- package/internal/cli/session.go +6 -0
- package/internal/cli/suggest.go +7 -5
- package/internal/cli/team.go +37 -9
- package/internal/cli/team_test.go +39 -0
- package/internal/client/ab.go +372 -0
- package/internal/client/ab_test.go +287 -0
- package/internal/client/browse.go +84 -36
- package/internal/client/browse_test.go +107 -9
- package/internal/client/browseinput.go +481 -0
- package/internal/client/browseinput_test.go +216 -0
- package/internal/client/corecmd.go +23 -0
- package/internal/client/corecmd_other.go +8 -0
- package/internal/client/corecmd_windows.go +24 -0
- package/internal/client/daemon.go +90 -17
- package/internal/client/daemon_test.go +33 -0
- package/internal/client/engines_checkin_test.go +57 -0
- package/internal/client/fallback_flags_test.go +64 -0
- package/internal/client/jobs.go +294 -21
- package/internal/client/jobs_test.go +299 -2
- package/internal/client/knowledge.go +854 -0
- package/internal/client/knowledge_test.go +510 -0
- package/internal/client/lan.go +3 -3
- package/internal/client/quotas_merge_test.go +107 -0
- package/internal/client/requirements.go +198 -1
- package/internal/client/requirements_shots_test.go +200 -0
- package/internal/client/restart.go +134 -0
- package/internal/client/restart_test.go +151 -0
- package/internal/client/scripts.go +1047 -0
- package/internal/client/scripts_decode_other.go +27 -0
- package/internal/client/scripts_decode_test.go +125 -0
- package/internal/client/scripts_decode_windows.go +206 -0
- package/internal/client/scripts_test.go +726 -0
- package/internal/client/service.go +322 -18
- package/internal/client/service_test.go +51 -0
- package/internal/client/suggest.go +105 -2
- package/internal/client/suggest_test.go +86 -0
- package/internal/client/team_flags.go +25 -11
- package/internal/client/team_flags_test.go +39 -0
- package/internal/requirements/bootstrap.go +16 -0
- package/internal/requirements/consolidate.go +100 -9
- package/internal/requirements/doc.go +154 -3
- package/internal/requirements/graph.go +138 -0
- package/internal/requirements/memory.go +7 -0
- package/internal/requirements/modules.go +312 -0
- package/internal/requirements/modules_test.go +373 -0
- package/internal/requirements/relations.go +155 -0
- package/internal/requirements/relations_test.go +231 -0
- package/internal/requirements/requirements.go +10 -0
- package/internal/requirements/shots.go +418 -0
- package/internal/requirements/shots_test.go +387 -0
- package/internal/session/session.go +92 -0
- package/internal/suggest/suggest.go +23 -1
- package/internal/team/event.go +3 -1
- package/internal/team/execute.go +21 -6
- package/internal/team/fallback_route_test.go +80 -0
- package/internal/team/planner.go +15 -3
- package/internal/team/route.go +89 -17
- package/internal/team/team.go +18 -4
- package/npm/apk/magic-agent-mobile-release.apk +0 -0
- package/npm/apk/version.json +1 -5
- package/npm/dist/darwin-arm64/magic-agent +0 -0
- package/npm/dist/darwin-arm64/magic-client +0 -0
- package/npm/dist/darwin-x64/magic-agent +0 -0
- package/npm/dist/darwin-x64/magic-client +0 -0
- package/npm/dist/linux-arm64/magic-agent +0 -0
- package/npm/dist/linux-arm64/magic-client +0 -0
- package/npm/dist/linux-x64/magic-agent +0 -0
- package/npm/dist/linux-x64/magic-client +0 -0
- package/npm/dist/win32-x64/magic-agent.exe +0 -0
- package/npm/dist/win32-x64/magic-client.exe +0 -0
- package/package.json +8 -2
|
@@ -62,6 +62,52 @@ function attErrText(err){
|
|
|
62
62
|
/** 缩略图地址:落盘后用真实 URL,落盘途中用本地 objectURL */
|
|
63
63
|
function attThumbUrl(a){ return a.remoteUrl || a.objectUrl || ""; }
|
|
64
64
|
|
|
65
|
+
/* ── 历史附件(2026-10-25 用户报障:「点击历史会话时发送的参考图没了」)──────────
|
|
66
|
+
根因:core 落盘的附件只有 **path + mime** 两个字段(internal/agent/attachments.go 的
|
|
67
|
+
Attachment),既没有 remoteUrl 也没有 objectUrl —— attThumbUrl 对它恒回空,
|
|
68
|
+
qaRenderMine 里「拿不到地址就跳过」的分支把整排缩略图全吞了。
|
|
69
|
+
这里给历史那档一条**独立的取址链**,与实时附件互不干扰:
|
|
70
|
+
① 实时字段(remoteUrl / objectUrl)优先 —— 两档同形时行为不变;
|
|
71
|
+
② 宿主回读通道(desk.fileUrl / desk.mediaUrl。组件宿主在 installDeskProbe 里把
|
|
72
|
+
mediaUrl 挂成 transport.fileUrl = /desk/media?path=…,按绝对路径回读,上传目录
|
|
73
|
+
内外的本机媒体文件都通);
|
|
74
|
+
③ qaShotSrc(file:// 壳内直接拼 file://;产物库路径走内置后端);
|
|
75
|
+
④ 都没有 → 空串,上层跳过 —— 宁可没有图,也不画一个必然裂的框(与 qaShotSrc
|
|
76
|
+
预览态的口径一致)。 */
|
|
77
|
+
/** 附件显示名:实时那档有 name;历史附件只有 path → 取 basename,并去掉上传时
|
|
78
|
+
* 服务端加的 <UnixNano>- 前缀,还原成用户上传时的文件名(与组件宿主 attNode 同一口径)。 */
|
|
79
|
+
function attHistName(a){
|
|
80
|
+
const n = a && a.name ? String(a.name) : "";
|
|
81
|
+
if(n) return n;
|
|
82
|
+
const p = String((a && a.path) || "");
|
|
83
|
+
const base = p.split(/[\\/]/).pop() || "";
|
|
84
|
+
return base.replace(/^\d{13,}-/, "") || "截图";
|
|
85
|
+
}
|
|
86
|
+
/** 这条附件是不是图(历史附件只有 mime/path;QA_IMG_EXT 与缩略图同一份后缀表)。 */
|
|
87
|
+
function attHistIsImage(a){
|
|
88
|
+
if(!a) return false;
|
|
89
|
+
const mime = String(a.mime || "");
|
|
90
|
+
if(mime) return /^image\//i.test(mime);
|
|
91
|
+
return QA_IMG_EXT.test(attHistName(a));
|
|
92
|
+
}
|
|
93
|
+
/** 历史附件 → 可显示地址(取址链见上)。实时附件请继续走 attThumbUrl。 */
|
|
94
|
+
function attHistUrl(a){
|
|
95
|
+
if(!a) return "";
|
|
96
|
+
if(a.remoteUrl) return a.remoteUrl;
|
|
97
|
+
if(a.objectUrl) return a.objectUrl;
|
|
98
|
+
const p = String(a.path || "").trim();
|
|
99
|
+
if(!p) return "";
|
|
100
|
+
const via = (name)=>{
|
|
101
|
+
const f = qaDeskFn(name);
|
|
102
|
+
if(typeof f !== "function") return "";
|
|
103
|
+
try{ return f(p) || ""; }catch(e){ return ""; }
|
|
104
|
+
};
|
|
105
|
+
const u = via("fileUrl") || via("mediaUrl");
|
|
106
|
+
if(u) return u;
|
|
107
|
+
if(typeof qaShotSrc === "function"){ try{ return qaShotSrc(p) || ""; }catch(e){} }
|
|
108
|
+
return "";
|
|
109
|
+
}
|
|
110
|
+
|
|
65
111
|
/** 落盘返回的 url → 浏览器可直接用的绝对地址。
|
|
66
112
|
* 壳内与预览态都拿得到 prdBackendBase(同一个内置后端);
|
|
67
113
|
* file:// 直开且后端未就绪时返回空 —— 那种情况继续用 objectURL,
|
|
@@ -393,6 +439,9 @@ function qaArtKind(p){
|
|
|
393
439
|
if(QA_IMG_EXT.test(s)) return "image";
|
|
394
440
|
if(/\.(mp4|mov|m4v|webm|mkv|avi)$/i.test(s)) return "video";
|
|
395
441
|
if(/\.(mp3|wav|m4a|aac|ogg|flac)$/i.test(s)) return "audio";
|
|
442
|
+
if(/\.(?:html?|xhtml)$/i.test(s)) return "html"; // HTML 产物:产物卡里嵌 iframe 预览
|
|
443
|
+
//(是否「自包含可预览」由 qaHtmlStandalone
|
|
444
|
+
// 按内容判,见 qaArtMount 的换卡逻辑)
|
|
396
445
|
if(QA_ART_EXT.test(s)) return "file";
|
|
397
446
|
return "";
|
|
398
447
|
}
|
|
@@ -608,10 +657,34 @@ function qaArtCollect(text){
|
|
|
608
657
|
return out.slice(0, 12); // 一次最多画 12 张(多了就不是「产物」了)
|
|
609
658
|
}
|
|
610
659
|
|
|
611
|
-
/**
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
660
|
+
/** HTML 产物是否「自包含、可独立预览」(2026-10-24 用户:「index.html这种非静态页面要排除
|
|
661
|
+
* 不需要预览」「按文件名区分不行」→ 改**内容**判据):
|
|
662
|
+
* 读文件头(`/desk/path-read`,512KB 上限)扫 `<script src>` / `<link href>` ——
|
|
663
|
+
* 引用了**外部 js / css**(相对 / 绝对路径,非 data: / 远端)的就是应用入口或页面壳,
|
|
664
|
+
* 单塞一个 iframe 里预览出来是白板或半个页面 → 返 false,退回普通文件卡。
|
|
665
|
+
* ⚠️ 通道拿不到 / 读失败 / 太大读不了 → 返 true(宁可照画,不凭猜把卡降级)。 */
|
|
666
|
+
function qaHtmlStandalone(p){
|
|
667
|
+
const rd = qaDeskFn("pathRead");
|
|
668
|
+
if(!rd) return Promise.resolve(true);
|
|
669
|
+
return Promise.resolve(rd({ path: String(p), base: qaArtBase() })).then((r)=>{
|
|
670
|
+
if(!r || !r.ok || typeof r.text !== "string") return false; // tooBig / 读不到:多半是应用入口
|
|
671
|
+
const t = r.text.slice(0, 65536).toLowerCase();
|
|
672
|
+
const re = /(?:src|href)\s*=\s*["']([^"']+)["']/g;
|
|
673
|
+
let m;
|
|
674
|
+
while((m = re.exec(t))){
|
|
675
|
+
const u = m[1].trim();
|
|
676
|
+
if(!u || /^(?:data:|https?:|\/\/|#|mailto:)/i.test(u)) continue;
|
|
677
|
+
if(/\.(?:m?js|css)(?:[?#]|$)/i.test(u)) return false; // 引了外部脚本 / 样式 → 不自包含
|
|
678
|
+
}
|
|
679
|
+
return true;
|
|
680
|
+
}).catch(()=> true);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/** 一张产物卡(按类型分流:图 / 视频 / 音频 / HTML / 其它文件)。
|
|
684
|
+
* `force`(2026-10-24)= 强制按这一档画(HTML 换卡逻辑用:入口页降级成普通文件卡)。 */
|
|
685
|
+
function qaArtNode(it, force){
|
|
686
|
+
const kind = force || qaArtKind(it.abs) || "file";
|
|
687
|
+
const name = String((it.abs || "").split(/[\\/]/).pop() || "文件");
|
|
615
688
|
const card = document.createElement("div");
|
|
616
689
|
card.className = "qa-art qa-art-" + kind;
|
|
617
690
|
card.dataset.path = it.abs || "";
|
|
@@ -627,6 +700,15 @@ function qaArtNode(it){
|
|
|
627
700
|
if(kind === "video") m.playsInline = true;
|
|
628
701
|
card.appendChild(m);
|
|
629
702
|
}
|
|
703
|
+
if(kind === "html"){ // HTML:iframe 就地预览(2026-10-24 用户:
|
|
704
|
+
const f = document.createElement("iframe"); // 「会话产物如果有html 现在没显示预览效果」)
|
|
705
|
+
f.src = qaArtUrl(it.abs);
|
|
706
|
+
f.title = name;
|
|
707
|
+
f.setAttribute("sandbox", "allow-scripts allow-same-origin allow-popups allow-forms");
|
|
708
|
+
f.setAttribute("loading", "lazy");
|
|
709
|
+
f.setAttribute("draggable", "false");
|
|
710
|
+
card.appendChild(f);
|
|
711
|
+
}
|
|
630
712
|
const cap = document.createElement("div");
|
|
631
713
|
cap.className = "qa-art-cap";
|
|
632
714
|
if(kind === "file"){ // 其它文件:后缀徽标 + 名字 + 大小
|
|
@@ -726,7 +808,7 @@ function qaArtMount(host, paths, opts){
|
|
|
726
808
|
if(!buckets.has(k)) buckets.set(k, []);
|
|
727
809
|
buckets.get(k).push(x);
|
|
728
810
|
});
|
|
729
|
-
["image", "video", "audio", "file"].forEach((k)=>{
|
|
811
|
+
["image", "video", "audio", "html", "file"].forEach((k)=>{
|
|
730
812
|
const arr = buckets.get(k);
|
|
731
813
|
if(!arr || !arr.length) return;
|
|
732
814
|
const row = document.createElement("div");
|
|
@@ -734,6 +816,22 @@ function qaArtMount(host, paths, opts){
|
|
|
734
816
|
arr.forEach((x)=> row.appendChild(qaArtNode(x)));
|
|
735
817
|
host.appendChild(row);
|
|
736
818
|
});
|
|
819
|
+
/* HTML 产物卡是**异步定形**的:先按 html 画(iframe 预览),随后读文件头判「自包含」,
|
|
820
|
+
引用了外部 js / css 的(index.html 这类应用入口)降级成普通文件卡 —— 内容判据,
|
|
821
|
+
不按文件名(2026-10-24 用户:「index.html这种非静态页面要排除」「按文件名区分不行」)。 */
|
|
822
|
+
hit.forEach((x)=>{
|
|
823
|
+
if(qaArtKind(x.abs) !== "html") return;
|
|
824
|
+
qaHtmlStandalone(x.abs).then((ok)=>{
|
|
825
|
+
if(ok) return;
|
|
826
|
+
const cards = host.querySelectorAll(".qa-art-html");
|
|
827
|
+
for(const c of cards){
|
|
828
|
+
if(qaArtCanon(c.dataset.path) === qaArtCanon(x.abs)){
|
|
829
|
+
c.parentNode.replaceChild(qaArtNode(x, "file"), c);
|
|
830
|
+
break;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
}).catch(()=>{ /* 换不了就保持原样 */ });
|
|
834
|
+
});
|
|
737
835
|
return hit.length;
|
|
738
836
|
}).catch(()=> 0); // 探针失败 = 当作「都不是产物」,正文照旧
|
|
739
837
|
}
|
|
@@ -803,6 +901,59 @@ function qaArtDrop(card){
|
|
|
803
901
|
}, true);
|
|
804
902
|
})();
|
|
805
903
|
|
|
904
|
+
/* ── HTML 产物 · 点击打开(2026-10-24 用户:「预览是缩略效果了 要支持点击查看」
|
|
905
|
+
→ 「现在有内置浏览器啊 点击跳转打开啊」)────────────────────────────────
|
|
906
|
+
点卡片:宿主有内置浏览器(`MagicHost.openBrowser`,移动端已挂)就**跳内置浏览器**;
|
|
907
|
+
没有的宿主退回**内核自画的全屏预览层**(不依赖宿主能力,PC / magic-test 通吃),
|
|
908
|
+
Esc / 关闭按钮退出。
|
|
909
|
+
⚠️ 缩略 iframe 设 `pointer-events:none`(CSS):iframe 内部的点击不会冒泡到卡片,
|
|
910
|
+
不设的话「点卡片」只有边条能点中;代价是缩略态不能滚里面内容 —— 反正要看就点开。
|
|
911
|
+
⚠️ 页面级一次性挂载,与上面 error 收尾同一个形制。 */
|
|
912
|
+
(function(){
|
|
913
|
+
if(typeof document === "undefined" || window.__qaArtHtmlPeek) return;
|
|
914
|
+
window.__qaArtHtmlPeek = true;
|
|
915
|
+
let layer = null;
|
|
916
|
+
const close = ()=>{
|
|
917
|
+
if(layer){ layer.parentNode && layer.parentNode.removeChild(layer); layer = null; }
|
|
918
|
+
document.removeEventListener("keydown", onKey);
|
|
919
|
+
};
|
|
920
|
+
function onKey(e){ if(e && e.key === "Escape") close(); }
|
|
921
|
+
function openPeek(p, u){
|
|
922
|
+
close();
|
|
923
|
+
layer = document.createElement("div");
|
|
924
|
+
layer.className = "qa-art-html-peek";
|
|
925
|
+
const bar = document.createElement("div");
|
|
926
|
+
bar.className = "qa-art-html-peek-bar";
|
|
927
|
+
const t = document.createElement("span");
|
|
928
|
+
t.className = "qa-art-html-peek-t";
|
|
929
|
+
t.textContent = String(p).split(/[\\/]/).pop() || p;
|
|
930
|
+
const x = document.createElement("button");
|
|
931
|
+
x.type = "button"; x.className = "qa-art-html-peek-x"; x.textContent = "关闭";
|
|
932
|
+
x.addEventListener("click", close);
|
|
933
|
+
bar.appendChild(t); bar.appendChild(x);
|
|
934
|
+
const f = document.createElement("iframe");
|
|
935
|
+
f.src = u; f.title = t.textContent;
|
|
936
|
+
f.setAttribute("sandbox", "allow-scripts allow-same-origin allow-popups allow-forms");
|
|
937
|
+
layer.appendChild(bar); layer.appendChild(f);
|
|
938
|
+
document.body.appendChild(layer);
|
|
939
|
+
document.addEventListener("keydown", onKey);
|
|
940
|
+
}
|
|
941
|
+
document.addEventListener("click", function(e){
|
|
942
|
+
const card = e.target && e.target.closest ? e.target.closest(".qa-art-html") : null;
|
|
943
|
+
if(!card || layer) return;
|
|
944
|
+
const p = String(card.dataset.path || "").trim();
|
|
945
|
+
if(!p || typeof qaArtUrl !== "function") return;
|
|
946
|
+
const u = qaArtUrl(p);
|
|
947
|
+
if(!u) return; // 拼不出地址(预览态)→ 不动
|
|
948
|
+
e.preventDefault();
|
|
949
|
+
try{
|
|
950
|
+
const ob = (typeof window !== "undefined" && window.MagicHost) ? window.MagicHost.openBrowser : null;
|
|
951
|
+
if(typeof ob === "function"){ ob(u); return; } // 有内置浏览器 → 跳浏览器
|
|
952
|
+
}catch(err){ /* 宿主通道不通 → 退全屏层 */ }
|
|
953
|
+
openPeek(p, u);
|
|
954
|
+
}, false);
|
|
955
|
+
})();
|
|
956
|
+
|
|
806
957
|
/* ── 会话产物 · 消息末尾的「本轮产物」(2026-10-15)─────────────────────────
|
|
807
958
|
用户原话:「会话有新的图片、视频文件产生 应该在消息结束时附加在末尾作为会话产物显示预览」。
|
|
808
959
|
正文里写出来的产物路径早就有渲染(qaShotsMount / qaArtMount:图变缩略图、视频变就地播放器),
|
|
@@ -819,7 +970,7 @@ function qaArtDrop(card){
|
|
|
819
970
|
⚠️ 与正文里已经画过的**不重复**(按绝对路径与文件名双判):用户要的是「正文没提的也看得见」,
|
|
820
971
|
正文里已经有的再画一遍就成了同一张图出现两次。
|
|
821
972
|
⚠️ 只**收**不摘、只**补**不改:正文与工具输出原文一个字都不动。 */
|
|
822
|
-
const QA_TURN_ART_KIND = /^(?:image|video|file)$/;
|
|
973
|
+
const QA_TURN_ART_KIND = /^(?:image|video|file|html)$/;
|
|
823
974
|
/** 记一条候选路径进**这一轮**的产物集(去重;留图 / 视频 / 文件三档)。 */
|
|
824
975
|
function qaTurnArtsNote(run, p){
|
|
825
976
|
const s = String(p == null ? "" : p).trim();
|
|
@@ -313,7 +313,10 @@ function qaSessionPlayer(){
|
|
|
313
313
|
}catch(e){ /* 收尾失败不影响回放 */ }
|
|
314
314
|
h = null;
|
|
315
315
|
}else if(arts){ arts.flush(null); } // 没有答复卡的一轮:清掉残留,别串到下一轮
|
|
316
|
-
|
|
316
|
+
/* 历史附件(参考图)随 user 事件落盘({path, mime}):透传给内核的 attHistUrl
|
|
317
|
+
按路径换回读地址 —— 与 93-rail 同一口径(2026-10-25 用户报障修复)。 */
|
|
318
|
+
qaRenderMine(String(ev.text || ""),
|
|
319
|
+
Array.isArray(ev.attachments) ? ev.attachments : [], { hosted: !!ev.hosted });
|
|
317
320
|
t0 = atMs(ev);
|
|
318
321
|
continue;
|
|
319
322
|
}
|
|
@@ -349,7 +352,7 @@ function qaSessionPlayer(){
|
|
|
349
352
|
qaRenderDone(!failed && !stopped,
|
|
350
353
|
failed ? "调用失败 · " + String(ev.reason || ev.error || "未知错误").slice(0, 200)
|
|
351
354
|
: stopped ? "已停止" : "已完成",
|
|
352
|
-
null, turnMs || null);
|
|
355
|
+
null, turnMs || null, failed ? ev : null);
|
|
353
356
|
if(arts) arts.flush(h); // 终态之后这一轮的产物补在消息末尾(与实时 onDone 同位)
|
|
354
357
|
t0 = 0; // 这一轮收尾了:下一轮(若没有 user 事件)重新起算
|
|
355
358
|
/* ⚠️ 句柄必须一起置空(2026-10-21 用户:「同一轮对话 显示成了两个」):
|
|
@@ -255,6 +255,87 @@ function qaMarkerNode(opts){
|
|
|
255
255
|
return wrap;
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
+
/* ---------- 错误显示卡(终态失败的统一落笔)----------
|
|
259
|
+
用户 2026-10-23:「没有设计错误显示卡 现在是普通的 json 文本」—— 失败那一轮原来只落一行
|
|
260
|
+
`.qa-done.err` 等宽灰字(「调用失败 · <reason>」),没有卡片形态。这里补一张**真正的卡**:
|
|
261
|
+
朱砂描边 + 淡红底的卡片面,抬头是告警图标 + 「调用失败」标题 + 「失败」状态徽章,
|
|
262
|
+
主体是**根因**(reason,没被截断,人话那一句),底部可展开「错误详情」(完整错误链 /
|
|
263
|
+
JSON,走 qaCodeFrameNode,可复制)。
|
|
264
|
+
|
|
265
|
+
⚠️ 与标记行(qaMarkerNode)的分工:control_error / command / notice 那种**过程性**的失败
|
|
266
|
+
仍落标记行(它们不是这一轮的终态,只是过程里的一次失败通知);这张卡只给**这一轮真的
|
|
267
|
+
以失败收场**的终态(error / turn_failed / 桥不可用 / 回放失败)。两条形态不重叠。
|
|
268
|
+
⚠️ 快照重建安全:整张卡的状态(开合)都靠 class / data-*,点击走 #qa-stream 事件委托
|
|
269
|
+
(见 07-stream-delegation 的 `.qa-mk-row` 分支)—— 展开详情那块直接复用 `.qa-mk`
|
|
270
|
+
那套词汇,所以委托不用为这张卡单独加逻辑,快照恢复出来的卡照样能点开。
|
|
271
|
+
⚠️ 根节点**保留 `.qa-done` 类**:收尾行契约(`tests/check-conv-shared-host.mjs` 数 `.qa-done`)
|
|
272
|
+
只认这个类,改类名会让那条断言失守。卡片形态靠 `.qa-errcard` 额外类实现。 */
|
|
273
|
+
function qaErrorCardNode(opts){
|
|
274
|
+
const o = opts || {};
|
|
275
|
+
const t = o.elapsedMs || "";
|
|
276
|
+
const det = o.detail;
|
|
277
|
+
let reason = "";
|
|
278
|
+
let chain = null;
|
|
279
|
+
/* 优先从结构化错误信封取根因与完整链(reason 是最内层原因、error 是带引擎包装的整条链);
|
|
280
|
+
没有信封(桥异常 / 抛错)就退回 label 里那段,再不行给「未知错误」。 */
|
|
281
|
+
if(det && typeof det === "object"){
|
|
282
|
+
reason = String(det.reason || "").trim();
|
|
283
|
+
chain = det.error != null && String(det.error).trim() ? det.error
|
|
284
|
+
: det.message != null && String(det.message).trim() ? det.message
|
|
285
|
+
: null;
|
|
286
|
+
}
|
|
287
|
+
if(!reason && typeof o.label === "string"){
|
|
288
|
+
reason = String(o.label)
|
|
289
|
+
.replace(/^调用失败\s*·\s*/, "")
|
|
290
|
+
.replace(/\s*·\s*总耗时[\s\S]*$/, "")
|
|
291
|
+
.trim();
|
|
292
|
+
}
|
|
293
|
+
if(!reason) reason = "未知错误";
|
|
294
|
+
|
|
295
|
+
const wrap = document.createElement("div");
|
|
296
|
+
wrap.className = "qa-done err qa-errcard";
|
|
297
|
+
|
|
298
|
+
const hd = document.createElement("div");
|
|
299
|
+
hd.className = "qa-ec-hd";
|
|
300
|
+
hd.innerHTML =
|
|
301
|
+
'<span class="qa-ec-ico">' + (QA_V_ICONS.alert || "") + '</span>'
|
|
302
|
+
+ '<span class="qa-ec-tt">调用失败</span>'
|
|
303
|
+
+ qaStatusBadgeHtml("failed");
|
|
304
|
+
wrap.appendChild(hd);
|
|
305
|
+
|
|
306
|
+
const bd = document.createElement("div");
|
|
307
|
+
bd.className = "qa-ec-bd";
|
|
308
|
+
const rp = document.createElement("p");
|
|
309
|
+
rp.className = "qa-ec-reason";
|
|
310
|
+
rp.textContent = reason;
|
|
311
|
+
bd.appendChild(rp);
|
|
312
|
+
if(t){
|
|
313
|
+
const mt = document.createElement("div");
|
|
314
|
+
mt.className = "qa-ec-meta";
|
|
315
|
+
mt.textContent = "总耗时 " + t;
|
|
316
|
+
bd.appendChild(mt);
|
|
317
|
+
}
|
|
318
|
+
/* 完整错误链:复用 `.qa-mk` 词汇(行 + 明细),点击开合走委托,快照安全 */
|
|
319
|
+
if(chain != null && String(chain).trim()){
|
|
320
|
+
const mk = document.createElement("div");
|
|
321
|
+
mk.className = "qa-mk";
|
|
322
|
+
const row = document.createElement("div");
|
|
323
|
+
row.className = "qa-mk-row";
|
|
324
|
+
row.dataset.tog = "1";
|
|
325
|
+
row.innerHTML = '<span class="qa-mk-chev">' + (QA_V_ICONS.chev || "") + '</span>'
|
|
326
|
+
+ '<span class="qa-mk-tt">错误详情</span>';
|
|
327
|
+
mk.appendChild(row);
|
|
328
|
+
const mkbd = document.createElement("div");
|
|
329
|
+
mkbd.className = "qa-mk-bd";
|
|
330
|
+
const isStr = typeof chain === "string";
|
|
331
|
+
mkbd.appendChild(qaCodeFrameNode("error", isStr ? String(chain) : JSON.stringify(chain, null, 2)));
|
|
332
|
+
mk.appendChild(mkbd);
|
|
333
|
+
bd.appendChild(mk);
|
|
334
|
+
}
|
|
335
|
+
wrap.appendChild(bd);
|
|
336
|
+
return wrap;
|
|
337
|
+
}
|
|
338
|
+
|
|
258
339
|
/* ---------- ④ 分隔条(anywhere Marker variant="separator")----------
|
|
259
340
|
用在「横切整条会话」的事件上 —— anywhere 那边是**会话压缩**(`MarkerContentKind="compact"`,
|
|
260
341
|
进行中/已完成两种文案)。分隔条不是谁说的话,形状本身就在说「这里是一条分界」。
|
|
@@ -465,12 +465,12 @@
|
|
|
465
465
|
气泡照常可读(用户要能确认自己写的是什么),只把底色压淡 + 虚线边 + 底下补一行
|
|
466
466
|
「排队中 · 上一轮结束后自动发送」—— **不隐藏、不挪走**,否则就是这次报障的
|
|
467
467
|
「追问没在对话区显示」。虚线边框同时说明它不是「已送达」那种实心状态。 */
|
|
468
|
-
.qa-msg.qa-mine.is-queued .qa-bd{border-style:dashed;border-color:var(--queued
|
|
468
|
+
.qa-msg.qa-mine.is-queued .qa-bd{border-style:dashed;border-color:var(--queued);
|
|
469
469
|
background:linear-gradient(180deg,#FAF7FF,#F3EEFF)}
|
|
470
470
|
.qa-msg.qa-mine.is-queued .qa-bd::after{content:"排队中 · 上一轮结束后自动发送";
|
|
471
|
-
display:block;margin-top:6px;font-size:11.5px;line-height:15px;color:var(--queued
|
|
471
|
+
display:block;margin-top:6px;font-size:11.5px;line-height:15px;color:var(--queued)}
|
|
472
472
|
[data-theme="dark"] .qa-msg.qa-mine.is-queued .qa-bd{background:#241E33;border-color:#7C5CE0}
|
|
473
|
-
[data-theme="dark"] .qa-msg.qa-mine.is-queued .qa-bd::after{color
|
|
473
|
+
[data-theme="dark"] .qa-msg.qa-mine.is-queued .qa-bd::after{color:var(--queued)}
|
|
474
474
|
/* 排队期间被取消(`is-cancelled`):气泡留着当回执,但明确说它不会被执行。
|
|
475
475
|
用灰色虚线而不是红色 —— 用户自己撤的,不是失败,别用报错的颜色吓人。 */
|
|
476
476
|
.qa-msg.qa-mine.is-cancelled .qa-bd{border-style:dashed;border-color:var(--line);
|
|
@@ -549,6 +549,33 @@
|
|
|
549
549
|
|
|
550
550
|
.qa-art-video video{display:block;max-width:280px;max-height:190px;background:#000}
|
|
551
551
|
|
|
552
|
+
/* HTML 产物卡:iframe 就地预览(2026-10-24)—— 固定尺寸与视频同一档,可滚动看整页。
|
|
553
|
+
缩略 iframe 关掉指针事件(点击不冒泡出 iframe,关了才能「点卡片看大图」),光标用放大镜。 */
|
|
554
|
+
.qa-art-html{cursor:zoom-in}
|
|
555
|
+
|
|
556
|
+
.qa-art-html iframe{display:block;width:280px;height:190px;border:0;background:#fff;pointer-events:none}
|
|
557
|
+
|
|
558
|
+
.qa-arts-html{display:grid;grid-template-columns:repeat(auto-fill,280px);gap:8px}
|
|
559
|
+
|
|
560
|
+
.qa-arts-html .qa-art-html{width:280px}
|
|
561
|
+
|
|
562
|
+
.qa-arts-html .qa-art-cap{max-width:none}
|
|
563
|
+
|
|
564
|
+
/* HTML 产物全屏预览层(点击卡片弹出,内核自画,见 19-attachments 的 __qaArtHtmlPeek):
|
|
565
|
+
深蒙层 + 顶条(文件名 / 关闭)+ 撑满余下的 iframe。 */
|
|
566
|
+
.qa-art-html-peek{position:fixed;inset:0;z-index:9999;display:flex;flex-direction:column;
|
|
567
|
+
gap:8px;padding:20px;background:rgba(28,25,23,.62)}
|
|
568
|
+
|
|
569
|
+
.qa-art-html-peek-bar{display:flex;align-items:center;gap:10px}
|
|
570
|
+
|
|
571
|
+
.qa-art-html-peek-t{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
|
|
572
|
+
font-family:var(--mono);font-size:12px;color:#F5F4F3}
|
|
573
|
+
|
|
574
|
+
.qa-art-html-peek-x{flex:0 0 auto;padding:3px 12px;border-radius:8px;border:1px solid rgba(245,244,243,.35);
|
|
575
|
+
background:transparent;color:#F5F4F3;font-size:12px;cursor:pointer}
|
|
576
|
+
|
|
577
|
+
.qa-art-html-peek iframe{flex:1 1 auto;width:100%;border:0;border-radius:10px;background:#fff}
|
|
578
|
+
|
|
552
579
|
.qa-art audio{display:block;width:260px;margin:8px}
|
|
553
580
|
|
|
554
581
|
.qa-art-cap{display:flex;align-items:center;gap:7px;padding:6px 9px;max-width:280px;
|
|
@@ -864,7 +891,7 @@
|
|
|
864
891
|
stroke-width:1.9;stroke-linecap:round;stroke-linejoin:round}
|
|
865
892
|
|
|
866
893
|
.qa-ask-tag{font-family:var(--mono);font-size:9px;letter-spacing:.06em;padding:1.5px 7px;
|
|
867
|
-
border-radius:99px;border:1px solid
|
|
894
|
+
border-radius:99px;border:1px solid color-mix(in srgb, var(--indigo) 28%, transparent);color:var(--indigo);background:var(--halo-indigo)}
|
|
868
895
|
|
|
869
896
|
.qa-ask-ttl{font-family:var(--mono);font-size:9px;letter-spacing:.1em;color:var(--paper-mute);margin-left:auto}
|
|
870
897
|
|
|
@@ -883,7 +910,7 @@
|
|
|
883
910
|
padding:7px 9px;border:1px solid var(--line);border-radius:8px;background:#fff;font:inherit;
|
|
884
911
|
cursor:pointer;transition:border-color .18s,background .18s}
|
|
885
912
|
|
|
886
|
-
.qa-ask-opt:hover{border-color:
|
|
913
|
+
.qa-ask-opt:hover{border-color:color-mix(in srgb, var(--indigo) 45%, transparent);background:var(--halo-indigo)}
|
|
887
914
|
|
|
888
915
|
.qa-ask-opt .mk{width:15px;height:15px;flex:none;margin-top:1px;border-radius:5px;
|
|
889
916
|
border:1.5px solid var(--line);display:grid;place-items:center;color:var(--paper-mute)}
|
|
@@ -910,18 +937,18 @@
|
|
|
910
937
|
.qa-ask-other input{flex:1;min-width:0;padding:6px 9px;border:1px solid var(--line);
|
|
911
938
|
border-radius:8px;background:#fff;font-family:var(--serif);font-size:12px;color:var(--paper)}
|
|
912
939
|
|
|
913
|
-
.qa-ask-other input:focus{outline:none;border-color:
|
|
940
|
+
.qa-ask-other input:focus{outline:none;border-color:color-mix(in srgb, var(--indigo) 50%, transparent)}
|
|
914
941
|
|
|
915
942
|
.qa-ask-other button{flex:none;padding:6px 12px;border:1px solid var(--line);border-radius:8px;
|
|
916
943
|
background:#fff;font-family:var(--serif);font-size:12px;color:var(--paper-dim);cursor:pointer}
|
|
917
944
|
|
|
918
|
-
.qa-ask-other button:hover{border-color:
|
|
945
|
+
.qa-ask-other button:hover{border-color:color-mix(in srgb, var(--indigo) 45%, transparent);color:var(--indigo)}
|
|
919
946
|
|
|
920
947
|
.qa-ask-ft{display:flex;align-items:center;gap:8px;margin-top:8px}
|
|
921
948
|
|
|
922
949
|
.qa-ask-ft[hidden],.qa-ask-ans[hidden]{display:none}
|
|
923
950
|
|
|
924
|
-
.qa-ask-sub{flex:none;padding:5px 14px;border:1px solid
|
|
951
|
+
.qa-ask-sub{flex:none;padding:5px 14px;border:1px solid color-mix(in srgb, var(--indigo) 40%, transparent);border-radius:99px;
|
|
925
952
|
background:var(--halo-indigo);font-family:var(--serif);font-size:12px;color:var(--indigo);cursor:pointer}
|
|
926
953
|
|
|
927
954
|
.qa-ask-sub:disabled{opacity:.4;cursor:default;border-color:var(--line);background:transparent;
|
|
@@ -988,6 +1015,39 @@
|
|
|
988
1015
|
|
|
989
1016
|
.qa-done.err{color:var(--cinnabar-hi)}
|
|
990
1017
|
|
|
1018
|
+
/* 错误显示卡(终态失败):朱砂描边 + 淡红底的卡片面(见 29-msg-variants 的 qaErrorCardNode)。
|
|
1019
|
+
⚠️ 根节点带 `.qa-done` 类(收尾行契约要数它),这里把它从「等宽灰字行」翻成「块级卡片」:
|
|
1020
|
+
display:block 盖掉 `.qa-done` 的 flex 行布局;颜色交给子元素,根只留卡片面。 */
|
|
1021
|
+
.qa-errcard{display:block;margin:8px 0 4px;padding:10px 12px;
|
|
1022
|
+
border:1px solid rgba(214,64,42,.38);border-radius:var(--r-md);background:rgba(214,64,42,.06)}
|
|
1023
|
+
|
|
1024
|
+
.qa-errcard + .qa-errcard{margin-top:0}
|
|
1025
|
+
|
|
1026
|
+
.qa-ec-hd{display:flex;align-items:center;gap:8px}
|
|
1027
|
+
|
|
1028
|
+
.qa-ec-ico{width:16px;height:16px;flex:none;display:grid;place-items:center;color:var(--cinnabar-hi)}
|
|
1029
|
+
|
|
1030
|
+
.qa-ec-ico svg{width:15px;height:15px;display:block;stroke:currentColor;fill:none;
|
|
1031
|
+
stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
|
|
1032
|
+
|
|
1033
|
+
.qa-ec-tt{flex:1;min-width:0;font-family:var(--font);font-size:13px;font-weight:600;color:var(--cinnabar-hi)}
|
|
1034
|
+
|
|
1035
|
+
.qa-ec-bd{margin-top:8px}
|
|
1036
|
+
|
|
1037
|
+
.qa-ec-reason{margin:0;font-size:12.5px;line-height:1.6;color:var(--paper-dim);
|
|
1038
|
+
white-space:pre-wrap;word-break:break-word}
|
|
1039
|
+
|
|
1040
|
+
.qa-ec-meta{margin-top:6px;font-family:var(--mono);font-size:10px;letter-spacing:.04em;color:var(--paper-mute)}
|
|
1041
|
+
|
|
1042
|
+
/* 卡片里的可展开详情(复用 .qa-mk 词汇);把它的左缩进让出来,贴着卡片内边距更顺 */
|
|
1043
|
+
.qa-errcard .qa-mk{margin:8px 0 0}
|
|
1044
|
+
|
|
1045
|
+
.qa-errcard .qa-mk-bd{padding-left:2px}
|
|
1046
|
+
|
|
1047
|
+
[data-theme="dark"] .qa-errcard{background:rgba(248,113,113,.10);border-color:rgba(248,113,113,.42)}
|
|
1048
|
+
|
|
1049
|
+
[data-theme="dark"] .qa-ec-reason{color:#E7E5E4}
|
|
1050
|
+
|
|
991
1051
|
.qa-reqacts{display:flex;flex-wrap:wrap;gap:8px;padding:10px 2px 2px;justify-content:flex-start}
|
|
992
1052
|
|
|
993
1053
|
.qa-reqacts button{font-family:var(--mono);font-size:11px;letter-spacing:.04em;padding:5px 12px;
|
|
@@ -1104,7 +1164,7 @@
|
|
|
1104
1164
|
|
|
1105
1165
|
[data-theme="dark"] .qa-ask-hd{color:#E5E5E5}
|
|
1106
1166
|
|
|
1107
|
-
[data-theme="dark"] .qa-ask-tag{color
|
|
1167
|
+
[data-theme="dark"] .qa-ask-tag{color:var(--indigo);border-color:color-mix(in srgb, var(--indigo) 40%, transparent);background:color-mix(in srgb, var(--indigo) 10%, transparent)}
|
|
1108
1168
|
|
|
1109
1169
|
[data-theme="dark"] .qa-ask-ttl{color:#525252}
|
|
1110
1170
|
|
|
@@ -1114,7 +1174,7 @@
|
|
|
1114
1174
|
|
|
1115
1175
|
[data-theme="dark"] .qa-ask-opt{border-color:#3B3835;background:transparent}
|
|
1116
1176
|
|
|
1117
|
-
[data-theme="dark"] .qa-ask-opt:hover{border-color:
|
|
1177
|
+
[data-theme="dark"] .qa-ask-opt:hover{border-color:color-mix(in srgb, var(--indigo) 45%, transparent);background:color-mix(in srgb, var(--indigo) 8%, transparent)}
|
|
1118
1178
|
|
|
1119
1179
|
[data-theme="dark"] .qa-ask-opt .lb{color:#E5E5E5}
|
|
1120
1180
|
|
|
@@ -1122,7 +1182,7 @@
|
|
|
1122
1182
|
|
|
1123
1183
|
[data-theme="dark"] .qa-ask-opt .mk{border-color:#525252;background:transparent}
|
|
1124
1184
|
|
|
1125
|
-
[data-theme="dark"] .qa-ask-opt.sel{border-color
|
|
1185
|
+
[data-theme="dark"] .qa-ask-opt.sel{border-color:var(--indigo);background:color-mix(in srgb, var(--indigo) 10%, transparent)}
|
|
1126
1186
|
|
|
1127
1187
|
[data-theme="dark"] .qa-ask-opt.sel .lb{color:#F5F4F3}
|
|
1128
1188
|
|
|
@@ -1130,9 +1190,9 @@
|
|
|
1130
1190
|
|
|
1131
1191
|
[data-theme="dark"] .qa-ask-other button{background:#1C1C1E;border-color:#3B3835;color:#A8A29E}
|
|
1132
1192
|
|
|
1133
|
-
[data-theme="dark"] .qa-ask-sub{background:
|
|
1193
|
+
[data-theme="dark"] .qa-ask-sub{background:color-mix(in srgb, var(--indigo) 10%, transparent);border-color:color-mix(in srgb, var(--indigo) 40%, transparent);color:var(--indigo)}
|
|
1134
1194
|
|
|
1135
|
-
[data-theme="dark"] .qa-ask-sub:hover:not(:disabled){background:
|
|
1195
|
+
[data-theme="dark"] .qa-ask-sub:hover:not(:disabled){background:color-mix(in srgb, var(--indigo) 18%, transparent)}
|
|
1136
1196
|
|
|
1137
1197
|
[data-theme="dark"] .qa-ask-hint{color:#525252}
|
|
1138
1198
|
|
|
@@ -1154,7 +1214,7 @@
|
|
|
1154
1214
|
|
|
1155
1215
|
[data-theme="dark"] .qa-run::after{background:#1C1C1E}
|
|
1156
1216
|
|
|
1157
|
-
[data-theme="dark"] .qa-spinner{border-color:#3B3835;border-top-color
|
|
1217
|
+
[data-theme="dark"] .qa-spinner{border-color:#3B3835;border-top-color:var(--indigo)}
|
|
1158
1218
|
|
|
1159
1219
|
[data-theme="dark"] .qa-run-pill{background:rgba(245,244,243,.05);border-color:#3B3835}
|
|
1160
1220
|
|
|
@@ -1515,7 +1575,7 @@
|
|
|
1515
1575
|
|
|
1516
1576
|
.qa-diff-row.del .s{color:#9E3120}
|
|
1517
1577
|
|
|
1518
|
-
.qa-diff-row.hunk{background:
|
|
1578
|
+
.qa-diff-row.hunk{background:color-mix(in srgb, var(--indigo) 7%, transparent);color:var(--indigo)}
|
|
1519
1579
|
|
|
1520
1580
|
.qa-diff-row.file{background:rgba(28,25,23,.035);color:var(--paper-mute)}
|
|
1521
1581
|
|
|
@@ -1596,7 +1656,7 @@
|
|
|
1596
1656
|
|
|
1597
1657
|
[data-theme="dark"] .qa-diff-row.del .s{color:#FCA5A5}
|
|
1598
1658
|
|
|
1599
|
-
[data-theme="dark"] .qa-diff-row.hunk{background:
|
|
1659
|
+
[data-theme="dark"] .qa-diff-row.hunk{background:color-mix(in srgb, var(--indigo) 12%, transparent);color:var(--indigo)}
|
|
1600
1660
|
|
|
1601
1661
|
[data-theme="dark"] .qa-diff-row.file{background:rgba(245,244,243,.045);color:#A8A29E}
|
|
1602
1662
|
|
|
@@ -1624,9 +1684,9 @@
|
|
|
1624
1684
|
/* 排队气泡上的「取消排队」:这里要的是**文字**按钮(用户得看懂点它会发生什么),
|
|
1625
1685
|
所以不复用上面 22×22 的图标尺寸,单开一档。 */
|
|
1626
1686
|
.qa-msg.qa-mine .qa-acts .qa-act--cancel{width:auto;height:22px;padding:0 8px;border-radius:var(--r-full);
|
|
1627
|
-
font-size:11.5px;line-height:22px;color:var(--queued
|
|
1628
|
-
.qa-msg.qa-mine .qa-acts .qa-act--cancel:hover{background:
|
|
1629
|
-
[data-theme="dark"] .qa-msg.qa-mine .qa-acts .qa-act--cancel{color
|
|
1687
|
+
font-size:11.5px;line-height:22px;color:var(--queued);white-space:nowrap}
|
|
1688
|
+
.qa-msg.qa-mine .qa-acts .qa-act--cancel:hover{background:color-mix(in srgb, var(--queued) 12%, transparent);color:var(--queued)}
|
|
1689
|
+
[data-theme="dark"] .qa-msg.qa-mine .qa-acts .qa-act--cancel{color:var(--queued)}
|
|
1630
1690
|
|
|
1631
1691
|
.qa-act svg{width:13px;height:13px;display:block;stroke:currentColor;fill:none;
|
|
1632
1692
|
stroke-width:1.9;stroke-linecap:round;stroke-linejoin:round}
|
|
@@ -130,6 +130,7 @@ setWho setRunLabel setResult // 角色标识 / 轮次标
|
|
|
130
130
|
| `command`(斜杠命令生命周期) | 以前走 default | **已补**:`case "command"` → 标记行(图标 terminal + 进行中/已完成/失败 + 可展开原始载荷) |
|
|
131
131
|
| `control_error` | 以前走 default(话说反了) | **已补**:`case "control_error"` → 「控制失败 · …」失败标记行 |
|
|
132
132
|
| `notice` | 已有 `case "notice"` ✓ | 无需动 |
|
|
133
|
+
| `fallback`(主备降级,2026-10-26) | 以前没有这条 kind(core 也不发) | **已补**:`case "fallback"` → 标记行「已由备用引擎「…」接上」(icon sep + 可展开的 from/to 载荷) |
|
|
133
134
|
| `answered` / `answer_queued` | 在内核的**刻意忽略**清单里 ✓(画面由收尾那条表达) | 无需动 |
|
|
134
135
|
| `user`(历史里用户那条) | 不在内核:宿主在回放时调 `qaRenderMine`(magic-test 就是这么做的) | 归宿主 |
|
|
135
136
|
| `interrupted` / `stopped` | 不在内核:收尾那条 `onDone({stopped:true})` → `qaRenderDone(…, "已停止")` | 归宿主 |
|
|
@@ -484,15 +484,21 @@
|
|
|
484
484
|
.pick__cols{display:flex;gap:0;height:min(56vh,56dvh,440px);border:1px solid var(--border);
|
|
485
485
|
border-radius:var(--r);overflow:hidden}
|
|
486
486
|
|
|
487
|
-
|
|
487
|
+
/* 引擎列窄、模型列宽:引擎名短(codebuddy-ai 已是极值),模型名长得多
|
|
488
|
+
(deepseek-v4.1-flash-1017 + 倍率),均分会让右列全被省略号截掉
|
|
489
|
+
(2026-10-24 用户:「减少引擎宽度增加模型宽度 让模型显示全」)。 */
|
|
490
|
+
.pick__col{flex:0 0 36%;min-width:0;overflow-y:auto;-webkit-overflow-scrolling:touch;padding:4px}
|
|
488
491
|
|
|
489
|
-
.pick__col--r{flex:1
|
|
492
|
+
.pick__col--r{flex:1 1 auto;border-left:1px solid var(--border)}
|
|
490
493
|
|
|
491
494
|
.pick__citem{display:flex;flex-direction:column;gap:1px;width:100%;padding:9px 10px;
|
|
492
495
|
border-radius:var(--r);text-align:left}
|
|
493
496
|
|
|
494
497
|
.pick__citem.is-on{background:var(--activity)}
|
|
495
498
|
|
|
499
|
+
/* 历史会话锁:引擎项置灰不可点(模型列不受影响) */
|
|
500
|
+
.pick__citem[disabled]{opacity:.55;cursor:not-allowed}
|
|
501
|
+
|
|
496
502
|
.pick__cl{font-size:14px;line-height:20px;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
497
503
|
|
|
498
504
|
.pick__citem.is-on .pick__cl{font-weight:600}
|
|
@@ -509,6 +515,11 @@
|
|
|
509
515
|
font-variant-numeric:tabular-nums;white-space:nowrap}
|
|
510
516
|
|
|
511
517
|
.pick__cd{font-size:11px;line-height:15px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
518
|
+
/* 账号剩余积分(codebuddy 系,--engines 的 credit_left):单独一行、用强调色,
|
|
519
|
+
与「N 个模型」区分开 —— 它会随对话消耗变化,是选择引擎时的第二个判据。 */
|
|
520
|
+
.pick__cd--credit{color:var(--accent)}
|
|
521
|
+
/* 「领取每日积分」:不是导航项,点它只领积分(阻止冒泡到引擎按钮)。 */
|
|
522
|
+
.pick__claim{cursor:pointer;text-decoration:underline;text-underline-offset:2px}
|
|
512
523
|
|
|
513
524
|
@keyframes pick-fade{from{opacity:0}to{opacity:1}}
|
|
514
525
|
|