cc-viewer 1.6.293 → 1.6.294
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/dist/assets/App-C66LoBEz.js +2 -0
- package/dist/assets/App-eFrjLzF_.css +1 -0
- package/dist/assets/{MdxEditorPanel-Cf01KF6Z.js → MdxEditorPanel-B8xrlDZJ.js} +1 -1
- package/dist/assets/{Mobile-BJlGkvAP.js → Mobile-fsi8-Lpb.js} +1 -1
- package/dist/assets/{_baseUniq-CPUnJ5bQ.js → _baseUniq-r3p3rodd.js} +1 -1
- package/dist/assets/{arc-WhuJ-oY5.js → arc-CjTV5gxc.js} +1 -1
- package/dist/assets/{architectureDiagram-Q4EWVU46-CWx77Yhd.js → architectureDiagram-Q4EWVU46-BqzjXpCq.js} +1 -1
- package/dist/assets/{blockDiagram-DXYQGD6D-D7AQLCoj.js → blockDiagram-DXYQGD6D-CLyFfeHh.js} +1 -1
- package/dist/assets/{c4Diagram-AHTNJAMY-BoPHNqCF.js → c4Diagram-AHTNJAMY-BaO-0tuc.js} +1 -1
- package/dist/assets/{channel-B9Ja6Xkc.js → channel-yOyhvOLV.js} +1 -1
- package/dist/assets/{chunk-4BX2VUAB-B-b0RYab.js → chunk-4BX2VUAB-CMTnvZkS.js} +1 -1
- package/dist/assets/{chunk-4TB4RGXK-BK_V34yf.js → chunk-4TB4RGXK-QI41m9WP.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-D-kMbu-2.js → chunk-55IACEB6-C4ZO8bM3.js} +1 -1
- package/dist/assets/{chunk-EDXVE4YY-CEtSkZzd.js → chunk-EDXVE4YY-Bo8P4o65.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-BXa_7Pn3.js → chunk-FMBD7UC4-CTHLGcHh.js} +1 -1
- package/dist/assets/{chunk-OYMX7WX6-tvM_OApS.js → chunk-OYMX7WX6-D0OHxKGd.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-DrEmcVHf.js → chunk-QZHKN3VN-CoYnjUpS.js} +1 -1
- package/dist/assets/{chunk-YZCP3GAM-D2M9T_R5.js → chunk-YZCP3GAM-BY71mTXM.js} +1 -1
- package/dist/assets/classDiagram-6PBFFD2Q-C9o5ip5q.js +1 -0
- package/dist/assets/classDiagram-v2-HSJHXN6E-C9o5ip5q.js +1 -0
- package/dist/assets/clone-GDqN3kwT.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-H7bkwu5F.js → cose-bilkent-S5V4N54A-DUNsA_MT.js} +1 -1
- package/dist/assets/{dagre-KV5264BT-DKXEGN18.js → dagre-KV5264BT-BzlT2Exr.js} +1 -1
- package/dist/assets/{diagram-5BDNPKRD-DZFhwpI3.js → diagram-5BDNPKRD-CiqQK3Ci.js} +1 -1
- package/dist/assets/{diagram-G4DWMVQ6-Crg9GlIk.js → diagram-G4DWMVQ6-BciK18tQ.js} +1 -1
- package/dist/assets/{diagram-MMDJMWI5-B8Qn1fKP.js → diagram-MMDJMWI5-C1WH1vfU.js} +1 -1
- package/dist/assets/{diagram-TYMM5635-BHE1LjtY.js → diagram-TYMM5635-CR5RzJ6u.js} +1 -1
- package/dist/assets/{erDiagram-SMLLAGMA-BaEqFWLd.js → erDiagram-SMLLAGMA-NJQKXu51.js} +1 -1
- package/dist/assets/{flowDiagram-DWJPFMVM-b2ukTawV.js → flowDiagram-DWJPFMVM-Cjx5t_1H.js} +1 -1
- package/dist/assets/{ganttDiagram-T4ZO3ILL-D5quyFgK.js → ganttDiagram-T4ZO3ILL-YFTDBBiU.js} +1 -1
- package/dist/assets/{gitGraphDiagram-UUTBAWPF-BE1H5_fN.js → gitGraphDiagram-UUTBAWPF-C2muKahz.js} +1 -1
- package/dist/assets/{graph-D_JLoOax.js → graph-I1olozIg.js} +1 -1
- package/dist/assets/{index-Cx8bk0Tp.js → index-7vxIrUNA.js} +1 -1
- package/dist/assets/index-BTZqk5O5.js +2 -0
- package/dist/assets/{index-BDUs32pN.css → index-Be9T-kDq.css} +1 -1
- package/dist/assets/{index-CtrY6gFZ.js → index-C1RNAzAB.js} +1 -1
- package/dist/assets/{index-CQrdpZQb.js → index-Cf4FBg-V.js} +1 -1
- package/dist/assets/{index-B8UmlA4F.js → index-D-HPuqxB.js} +1 -1
- package/dist/assets/{index-k0AH8cvI.js → index-D2QUxu18.js} +1 -1
- package/dist/assets/{index-DiZ9CErG.js → index-DhzoJ5wE.js} +1 -1
- package/dist/assets/{index-CWjqMDrs.js → index-fhI0i2p3.js} +1 -1
- package/dist/assets/{infoDiagram-42DDH7IO-DQKlrVkw.js → infoDiagram-42DDH7IO-C9bza97c.js} +1 -1
- package/dist/assets/{ishikawaDiagram-UXIWVN3A-BchFlpPc.js → ishikawaDiagram-UXIWVN3A-BtZGipfW.js} +1 -1
- package/dist/assets/{journeyDiagram-VCZTEJTY-Dg1mt4df.js → journeyDiagram-VCZTEJTY-CKTp590c.js} +1 -1
- package/dist/assets/{jszip.min-LIb2SFoK.js → jszip.min-DDU-_oA-.js} +1 -1
- package/dist/assets/{kanban-definition-6JOO6SKY-226va2PS.js → kanban-definition-6JOO6SKY-BHLNWfr5.js} +1 -1
- package/dist/assets/{layout-rSa8rcPi.js → layout-DBmqcl9N.js} +1 -1
- package/dist/assets/{linear-BeARi8nH.js → linear-Br9n7mCI.js} +1 -1
- package/dist/assets/{mermaid.core-CDgdx9l7.js → mermaid.core-BV3ugHFm.js} +2 -2
- package/dist/assets/{min-B9yebCuj.js → min-D-YA3MGY.js} +1 -1
- package/dist/assets/{mindmap-definition-QFDTVHPH-C3apVbdg.js → mindmap-definition-QFDTVHPH-CzrYj3cB.js} +1 -1
- package/dist/assets/{pieDiagram-DEJITSTG-xjOQoQeL.js → pieDiagram-DEJITSTG-BAvtfiT3.js} +1 -1
- package/dist/assets/{quadrantDiagram-34T5L4WZ-Dq8x_VN2.js → quadrantDiagram-34T5L4WZ-i4zhnBJq.js} +1 -1
- package/dist/assets/{requirementDiagram-MS252O5E-CLmO1Gai.js → requirementDiagram-MS252O5E-Cb2wX9Sk.js} +1 -1
- package/dist/assets/{sankeyDiagram-XADWPNL6-BuUP1Eqq.js → sankeyDiagram-XADWPNL6-CcpbP6z5.js} +1 -1
- package/dist/assets/seqResourceLoaders-6k4uXcNn.js +2 -0
- package/dist/assets/{seqResourceLoaders-DWKAvGtj.css → seqResourceLoaders-De_-fYhE.css} +2 -2
- package/dist/assets/{sequenceDiagram-FGHM5R23-B18koU20.js → sequenceDiagram-FGHM5R23-BcbUxMmI.js} +1 -1
- package/dist/assets/{stateDiagram-FHFEXIEX-Cj57OCcO.js → stateDiagram-FHFEXIEX-CpIa1qoO.js} +1 -1
- package/dist/assets/{stateDiagram-v2-QKLJ7IA2-C01a2p--.js → stateDiagram-v2-QKLJ7IA2-d3GoyW9S.js} +1 -1
- package/dist/assets/{timeline-definition-GMOUNBTQ-cOlsEN_F.js → timeline-definition-GMOUNBTQ-BfQPSOuT.js} +1 -1
- package/dist/assets/{vendor-antd-DqFS7Zj9.js → vendor-antd-Bur5ZxWE.js} +1 -1
- package/dist/assets/{vendor-codemirror-B_pF4DrA.js → vendor-codemirror-Si44UqBp.js} +1 -1
- package/dist/assets/{vendor-mdxeditor-B_IrHcWH.js → vendor-mdxeditor-Cco3AQJS.js} +2 -2
- package/dist/assets/{vendor-qrcode-C4PneAS5.js → vendor-qrcode-Dn3GYC4l.js} +1 -1
- package/dist/assets/{vendor-virtuoso-CEGeJyDP.js → vendor-virtuoso-CW9EqKMt.js} +1 -1
- package/dist/assets/{vennDiagram-DHZGUBPP-BCjdwiDk.js → vennDiagram-DHZGUBPP-hTgiYDQL.js} +1 -1
- package/dist/assets/{wardley-RL74JXVD-CRmLlBwn.js → wardley-RL74JXVD-ByDpAPp1.js} +1 -1
- package/dist/assets/{wardleyDiagram-NUSXRM2D-BJYVDJ4F.js → wardleyDiagram-NUSXRM2D-D7LJTuWq.js} +1 -1
- package/dist/assets/{xychartDiagram-5P7HB3ND-el5C4S1Z.js → xychartDiagram-5P7HB3ND-MW_KOomO.js} +1 -1
- package/dist/index.html +5 -5
- package/package.json +1 -1
- package/server/lib/adapters/dingtalk-adapter.js +7 -0
- package/server/lib/adapters/discord-adapter.js +9 -1
- package/server/lib/adapters/feishu-adapter.js +19 -0
- package/server/lib/adapters/wecom-adapter.js +4 -0
- package/server/lib/im-bridge-core.js +59 -7
- package/server/lib/im-claude-md.js +37 -1
- package/server/lib/im-senders.js +73 -0
- package/server/routes/im.js +117 -3
- package/server/routes/skills.js +180 -165
- package/dist/assets/App-DRvRd96X.css +0 -1
- package/dist/assets/App-OM2oqZRW.js +0 -1
- package/dist/assets/classDiagram-6PBFFD2Q-CCwGJXEA.js +0 -1
- package/dist/assets/classDiagram-v2-HSJHXN6E-CCwGJXEA.js +0 -1
- package/dist/assets/clone-BuQbTPQO.js +0 -1
- package/dist/assets/index-CnWSVlWW.js +0 -2
- package/dist/assets/seqResourceLoaders-BZ6M3Jb-.js +0 -2
package/server/routes/skills.js
CHANGED
|
@@ -44,16 +44,177 @@ function skillsToggle(req, res) {
|
|
|
44
44
|
});
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
// Skill
|
|
48
|
-
//
|
|
49
|
-
// ·
|
|
50
|
-
// ·
|
|
51
|
-
// ·
|
|
52
|
-
// ·
|
|
53
|
-
|
|
54
|
-
|
|
47
|
+
// ─── Skill 上传/导入:可复用核心(供用户级 /api/skills/import 与 IM 级 /api/im/:platform/skills/import 共用)───
|
|
48
|
+
// 设计要点(与原实现一致):
|
|
49
|
+
// · 扩展名白名单只放 zip / md(忽略大小写);其他类型 415;
|
|
50
|
+
// · zip 内必须含 SKILL.md(任意子目录、忽略大小写),取最浅的那个所在目录作为 skill 根;
|
|
51
|
+
// · skill 名优先取 SKILL.md frontmatter 的 name,回落 zip 根目录名 / 文件名(去扩展名);
|
|
52
|
+
// · zip bomb 防护:单文件 ≤50MB,总解压 ≤200MB;拒绝 symlink entry。
|
|
53
|
+
|
|
54
|
+
const parseNameFromMd = (text) => {
|
|
55
|
+
const m = /^---\s*\n([\s\S]*?)\n---/.exec(text);
|
|
56
|
+
if (!m) return null;
|
|
57
|
+
const nm = /^name\s*:\s*(.*)$/m.exec(m[1]);
|
|
58
|
+
if (!nm) return null;
|
|
59
|
+
return nm[1].trim().replace(/^["']|["']$/g, '');
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const fallbackBaseName = (filename, stripExt) => {
|
|
63
|
+
let n = filename.replace(/^.*[\\/]/, '');
|
|
64
|
+
if (stripExt) n = n.replace(/\.[^.]+$/, '');
|
|
65
|
+
return n;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 解析一段 multipart body(已组装好的 Buffer)→ { skillName, files: [{relPath, data}] }。
|
|
70
|
+
* 抛 { status, code } 形式的错误(与原 skillsImport 一致),由调用方映射 HTTP。
|
|
71
|
+
*/
|
|
72
|
+
export async function parseSkillUpload(buf, boundary, windowsReservedRe) {
|
|
73
|
+
const headerEnd = buf.indexOf('\r\n\r\n');
|
|
74
|
+
if (headerEnd === -1) throw Object.assign(new Error('Malformed multipart'), { status: 400 });
|
|
75
|
+
const headerStr = buf.slice(0, headerEnd).toString();
|
|
76
|
+
const nameMatch = headerStr.match(/filename="([^"]+)"/);
|
|
77
|
+
if (!nameMatch) throw Object.assign(new Error('No filename'), { status: 400 });
|
|
78
|
+
// NFKC 规范化 + 控制字符/路径分隔符过滤 + 零宽和方向覆盖字符过滤(防止 homoglyph / RLO 混淆)
|
|
79
|
+
const originalName = nameMatch[1]
|
|
80
|
+
.normalize('NFKC')
|
|
81
|
+
.replace(/[\x00-\x1f/\\]/g, '_')
|
|
82
|
+
.replace(/[--]/g, '');
|
|
83
|
+
// Windows 保留设备名守卫。
|
|
84
|
+
{
|
|
85
|
+
const base = originalName.split('.')[0].trim().toLowerCase();
|
|
86
|
+
if (windowsReservedRe && windowsReservedRe.test(base)) {
|
|
87
|
+
throw Object.assign(new Error('Reserved filename not allowed'), { status: 400 });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const lower = originalName.toLowerCase();
|
|
91
|
+
const isZip = lower.endsWith('.zip');
|
|
92
|
+
const isMd = lower.endsWith('.md');
|
|
93
|
+
if (!isZip && !isMd) {
|
|
94
|
+
throw Object.assign(new Error('Unsupported file type'), { status: 415, code: 'INVALID_TYPE' });
|
|
95
|
+
}
|
|
96
|
+
const bodyStart = headerEnd + 4;
|
|
97
|
+
const closingBoundary = Buffer.from('\r\n--' + boundary);
|
|
98
|
+
const bodyEnd = buf.indexOf(closingBoundary, bodyStart);
|
|
99
|
+
const fileData = bodyEnd !== -1 ? buf.slice(bodyStart, bodyEnd) : buf.slice(bodyStart);
|
|
100
|
+
|
|
101
|
+
let skillName = null;
|
|
102
|
+
let skillFiles = []; // { relPath, data }
|
|
103
|
+
|
|
104
|
+
if (isMd) {
|
|
105
|
+
const text = fileData.toString('utf8');
|
|
106
|
+
skillName = parseNameFromMd(text) || fallbackBaseName(originalName, true);
|
|
107
|
+
skillFiles = [{ relPath: 'SKILL.md', data: fileData }];
|
|
108
|
+
} else {
|
|
109
|
+
const AdmZip = (await import('adm-zip')).default;
|
|
110
|
+
let zip;
|
|
111
|
+
try {
|
|
112
|
+
zip = new AdmZip(fileData);
|
|
113
|
+
} catch {
|
|
114
|
+
throw Object.assign(new Error('Invalid zip archive'), { status: 400, code: 'INVALID_ZIP' });
|
|
115
|
+
}
|
|
116
|
+
const entries = zip.getEntries();
|
|
117
|
+
const MAX_PER_FILE = 50 * 1024 * 1024;
|
|
118
|
+
const MAX_TOTAL_UNCOMPRESSED = 200 * 1024 * 1024;
|
|
119
|
+
let totalUncompressed = 0;
|
|
120
|
+
for (const e of entries) {
|
|
121
|
+
if (e.isDirectory) continue;
|
|
122
|
+
const unixMode = (e.attr >>> 16) & 0xffff;
|
|
123
|
+
if ((unixMode & 0o170000) === 0o120000) {
|
|
124
|
+
throw Object.assign(new Error('Symlinks not allowed in zip'), { status: 400, code: 'INVALID_ZIP' });
|
|
125
|
+
}
|
|
126
|
+
const sizeRaw = e.header?.size || 0;
|
|
127
|
+
if (sizeRaw > MAX_PER_FILE) {
|
|
128
|
+
throw Object.assign(new Error('File too large in archive'), { status: 400, code: 'ZIP_BOMB' });
|
|
129
|
+
}
|
|
130
|
+
totalUncompressed += sizeRaw;
|
|
131
|
+
if (totalUncompressed > MAX_TOTAL_UNCOMPRESSED) {
|
|
132
|
+
throw Object.assign(new Error('Archive expands too large'), { status: 400, code: 'ZIP_BOMB' });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
let bestSkillEntry = null;
|
|
136
|
+
let bestDepth = Infinity;
|
|
137
|
+
for (const e of entries) {
|
|
138
|
+
if (e.isDirectory) continue;
|
|
139
|
+
const en = e.entryName;
|
|
140
|
+
const base = en.split('/').pop() || '';
|
|
141
|
+
if (base.toLowerCase() === 'skill.md') {
|
|
142
|
+
const depth = en.split('/').length;
|
|
143
|
+
if (depth < bestDepth) { bestDepth = depth; bestSkillEntry = e; }
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (!bestSkillEntry) {
|
|
147
|
+
throw Object.assign(new Error('SKILL.md not found in zip'), { status: 400, code: 'MISSING_SKILL_MD' });
|
|
148
|
+
}
|
|
149
|
+
const lastSlash = bestSkillEntry.entryName.lastIndexOf('/');
|
|
150
|
+
const skillRootPrefix = lastSlash >= 0 ? bestSkillEntry.entryName.slice(0, lastSlash + 1) : '';
|
|
151
|
+
const skillMdText = bestSkillEntry.getData().toString('utf8');
|
|
152
|
+
skillName = parseNameFromMd(skillMdText)
|
|
153
|
+
|| (skillRootPrefix ? skillRootPrefix.replace(/\/$/, '').split('/').pop() : null)
|
|
154
|
+
|| fallbackBaseName(originalName, true);
|
|
155
|
+
|
|
156
|
+
// 二次校验:header.size 来自 zip 中央目录是攻击者可控的(可谎报 size=0),用真实 data.length 复核。
|
|
157
|
+
let actualTotal = 0;
|
|
158
|
+
for (const e of entries) {
|
|
159
|
+
if (e.isDirectory) continue;
|
|
160
|
+
if (skillRootPrefix && !e.entryName.startsWith(skillRootPrefix)) continue;
|
|
161
|
+
const rel = skillRootPrefix ? e.entryName.slice(skillRootPrefix.length) : e.entryName;
|
|
162
|
+
if (!rel || rel.includes('..')) continue;
|
|
163
|
+
const data = e.getData();
|
|
164
|
+
if (data.length > MAX_PER_FILE) {
|
|
165
|
+
throw Object.assign(new Error('File actual size too large'), { status: 400, code: 'ZIP_BOMB' });
|
|
166
|
+
}
|
|
167
|
+
actualTotal += data.length;
|
|
168
|
+
if (actualTotal > MAX_TOTAL_UNCOMPRESSED) {
|
|
169
|
+
throw Object.assign(new Error('Archive actual size too large'), { status: 400, code: 'ZIP_BOMB' });
|
|
170
|
+
}
|
|
171
|
+
const finalRel = rel.split('/').pop().toLowerCase() === 'skill.md'
|
|
172
|
+
? rel.replace(/[^/]*$/, 'SKILL.md')
|
|
173
|
+
: rel;
|
|
174
|
+
skillFiles.push({ relPath: finalRel, data });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (!validateSkillName(skillName)) {
|
|
179
|
+
throw Object.assign(new Error(`Invalid skill name: ${skillName}`), { status: 400, code: 'INVALID_NAME' });
|
|
180
|
+
}
|
|
181
|
+
return { skillName, files: skillFiles };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* 把解析出的 skill 文件写入 <skillsRoot>/<skillName>/。原子 mkdir(已存在→409 EXISTS)+ 路径包含校验。
|
|
186
|
+
* @returns {string} targetDir
|
|
187
|
+
*/
|
|
188
|
+
export function writeSkillFiles(skillsRoot, skillName, files) {
|
|
189
|
+
mkdirSync(skillsRoot, { recursive: true });
|
|
190
|
+
const targetDir = join(skillsRoot, skillName);
|
|
191
|
+
// 原子创建:不带 recursive 让 mkdir 在已存在时直接抛 EEXIST,消除 TOCTOU 竞争窗口。
|
|
192
|
+
try {
|
|
193
|
+
mkdirSync(targetDir);
|
|
194
|
+
} catch (err) {
|
|
195
|
+
if (err.code === 'EEXIST') {
|
|
196
|
+
throw Object.assign(new Error(`Skill already exists: ${skillName}`), { status: 409, code: 'EXISTS' });
|
|
197
|
+
}
|
|
198
|
+
throw err;
|
|
199
|
+
}
|
|
200
|
+
// 二次防御:resolved + sep 后缀比较,防止 prefix 攻击。
|
|
201
|
+
const resolvedTarget = resolve(targetDir) + sep;
|
|
202
|
+
for (const f of files) {
|
|
203
|
+
const dest = join(targetDir, f.relPath);
|
|
204
|
+
if (!resolve(dest).startsWith(resolvedTarget)) continue;
|
|
205
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
206
|
+
writeFileSync(dest, f.data);
|
|
207
|
+
}
|
|
208
|
+
return targetDir;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* 处理一次 skill 上传请求(流式读 body + 大小守卫 + 解析 + 写入 + 应答)。
|
|
213
|
+
* 用户级与 IM 级 import 共用,差异只在 skillsRoot。
|
|
214
|
+
*/
|
|
215
|
+
export function importSkillTo(req, res, { skillsRoot, windowsReserved }) {
|
|
55
216
|
const contentType = req.headers['content-type'] || '';
|
|
56
|
-
// boundary 用 [^;]+
|
|
217
|
+
// boundary 用 [^;]+ 终止避免吞掉后续参数;长度封顶 200 防止超长串撑爆 buffer 比对。
|
|
57
218
|
const boundaryMatch = contentType.match(/boundary=([^;]+)/);
|
|
58
219
|
if (!boundaryMatch || boundaryMatch[1].length > 200) {
|
|
59
220
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
@@ -85,162 +246,8 @@ function skillsImport(req, res, parsedUrl, isLocal, deps) {
|
|
|
85
246
|
req.on('end', async () => {
|
|
86
247
|
if (aborted) return;
|
|
87
248
|
try {
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
if (headerEnd === -1) throw Object.assign(new Error('Malformed multipart'), { status: 400 });
|
|
91
|
-
const headerStr = buf.slice(0, headerEnd).toString();
|
|
92
|
-
const nameMatch = headerStr.match(/filename="([^"]+)"/);
|
|
93
|
-
if (!nameMatch) throw Object.assign(new Error('No filename'), { status: 400 });
|
|
94
|
-
// NFKC 规范化 + 控制字符/路径分隔符过滤 + 零宽和方向覆盖字符过滤(防止 homoglyph / RLO 混淆)
|
|
95
|
-
const originalName = nameMatch[1]
|
|
96
|
-
.normalize('NFKC')
|
|
97
|
-
.replace(/[\x00-\x1f/\\]/g, '_')
|
|
98
|
-
.replace(/[--]/g, '');
|
|
99
|
-
// Windows 保留设备名守卫(见 WINDOWS_RESERVED_NAMES 注释)。
|
|
100
|
-
{
|
|
101
|
-
const base = originalName.split('.')[0].trim().toLowerCase();
|
|
102
|
-
if (deps.WINDOWS_RESERVED_NAMES.test(base)) {
|
|
103
|
-
throw Object.assign(new Error('Reserved filename not allowed'), { status: 400 });
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
const lower = originalName.toLowerCase();
|
|
107
|
-
const isZip = lower.endsWith('.zip');
|
|
108
|
-
const isMd = lower.endsWith('.md');
|
|
109
|
-
if (!isZip && !isMd) {
|
|
110
|
-
throw Object.assign(new Error('Unsupported file type'), { status: 415, code: 'INVALID_TYPE' });
|
|
111
|
-
}
|
|
112
|
-
const bodyStart = headerEnd + 4;
|
|
113
|
-
const closingBoundary = Buffer.from('\r\n--' + boundary);
|
|
114
|
-
const bodyEnd = buf.indexOf(closingBoundary, bodyStart);
|
|
115
|
-
const fileData = bodyEnd !== -1 ? buf.slice(bodyStart, bodyEnd) : buf.slice(bodyStart);
|
|
116
|
-
|
|
117
|
-
const skillsRoot = join(getClaudeConfigDir(), 'skills');
|
|
118
|
-
mkdirSync(skillsRoot, { recursive: true });
|
|
119
|
-
|
|
120
|
-
// 简单 frontmatter name 解析:抓 `name: xxx` 单行(与 skills-api.js 的 description 解析风格保持一致,最小实现)
|
|
121
|
-
const parseNameFromMd = (text) => {
|
|
122
|
-
const m = /^---\s*\n([\s\S]*?)\n---/.exec(text);
|
|
123
|
-
if (!m) return null;
|
|
124
|
-
const nm = /^name\s*:\s*(.*)$/m.exec(m[1]);
|
|
125
|
-
if (!nm) return null;
|
|
126
|
-
return nm[1].trim().replace(/^["']|["']$/g, '');
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
const fallbackBaseName = (filename, stripExt) => {
|
|
130
|
-
let n = filename.replace(/^.*[\\/]/, '');
|
|
131
|
-
if (stripExt) n = n.replace(/\.[^.]+$/, '');
|
|
132
|
-
return n;
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
let skillName = null;
|
|
136
|
-
let skillFiles = []; // { relPath, data }
|
|
137
|
-
|
|
138
|
-
if (isMd) {
|
|
139
|
-
const text = fileData.toString('utf8');
|
|
140
|
-
skillName = parseNameFromMd(text) || fallbackBaseName(originalName, true);
|
|
141
|
-
skillFiles = [{ relPath: 'SKILL.md', data: fileData }];
|
|
142
|
-
} else {
|
|
143
|
-
// zip:用 adm-zip 解压到内存,校验 SKILL.md 存在(忽略大小写),找最浅的 SKILL.md 所在目录作为 skill 根
|
|
144
|
-
const AdmZip = (await import('adm-zip')).default;
|
|
145
|
-
let zip;
|
|
146
|
-
try {
|
|
147
|
-
zip = new AdmZip(fileData);
|
|
148
|
-
} catch {
|
|
149
|
-
throw Object.assign(new Error('Invalid zip archive'), { status: 400, code: 'INVALID_ZIP' });
|
|
150
|
-
}
|
|
151
|
-
const entries = zip.getEntries();
|
|
152
|
-
// zip bomb 防护:单文件 ≤50MB,总解压 ≤200MB;以及拒绝 symlink entry
|
|
153
|
-
// adm-zip 不直接暴露 isSymbolicLink,需用 attr 高 16 位的 unix mode 判断 0o120000
|
|
154
|
-
const MAX_PER_FILE = 50 * 1024 * 1024;
|
|
155
|
-
const MAX_TOTAL_UNCOMPRESSED = 200 * 1024 * 1024;
|
|
156
|
-
let totalUncompressed = 0;
|
|
157
|
-
for (const e of entries) {
|
|
158
|
-
if (e.isDirectory) continue;
|
|
159
|
-
const unixMode = (e.attr >>> 16) & 0xffff;
|
|
160
|
-
if ((unixMode & 0o170000) === 0o120000) {
|
|
161
|
-
throw Object.assign(new Error('Symlinks not allowed in zip'), { status: 400, code: 'INVALID_ZIP' });
|
|
162
|
-
}
|
|
163
|
-
const sizeRaw = e.header?.size || 0;
|
|
164
|
-
if (sizeRaw > MAX_PER_FILE) {
|
|
165
|
-
throw Object.assign(new Error('File too large in archive'), { status: 400, code: 'ZIP_BOMB' });
|
|
166
|
-
}
|
|
167
|
-
totalUncompressed += sizeRaw;
|
|
168
|
-
if (totalUncompressed > MAX_TOTAL_UNCOMPRESSED) {
|
|
169
|
-
throw Object.assign(new Error('Archive expands too large'), { status: 400, code: 'ZIP_BOMB' });
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
// 找所有 SKILL.md(忽略大小写)的 entry,挑最浅的
|
|
173
|
-
let bestSkillEntry = null;
|
|
174
|
-
let bestDepth = Infinity;
|
|
175
|
-
for (const e of entries) {
|
|
176
|
-
if (e.isDirectory) continue;
|
|
177
|
-
const en = e.entryName;
|
|
178
|
-
const base = en.split('/').pop() || '';
|
|
179
|
-
if (base.toLowerCase() === 'skill.md') {
|
|
180
|
-
const depth = en.split('/').length;
|
|
181
|
-
if (depth < bestDepth) { bestDepth = depth; bestSkillEntry = e; }
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
if (!bestSkillEntry) {
|
|
185
|
-
throw Object.assign(new Error('SKILL.md not found in zip'), { status: 400, code: 'MISSING_SKILL_MD' });
|
|
186
|
-
}
|
|
187
|
-
// skill 根 = SKILL.md 所在目录(如果在 zip 根则空字符串)
|
|
188
|
-
const lastSlash = bestSkillEntry.entryName.lastIndexOf('/');
|
|
189
|
-
const skillRootPrefix = lastSlash >= 0 ? bestSkillEntry.entryName.slice(0, lastSlash + 1) : '';
|
|
190
|
-
const skillMdText = bestSkillEntry.getData().toString('utf8');
|
|
191
|
-
skillName = parseNameFromMd(skillMdText)
|
|
192
|
-
|| (skillRootPrefix ? skillRootPrefix.replace(/\/$/, '').split('/').pop() : null)
|
|
193
|
-
|| fallbackBaseName(originalName, true);
|
|
194
|
-
|
|
195
|
-
// 收集 skill 根下的所有文件,规范化 relPath(去掉 root prefix)
|
|
196
|
-
// 二次校验:header.size 来自 zip 中央目录是攻击者可控的(可谎报 size=0),
|
|
197
|
-
// 上面 zip bomb 检查已用 header.size 做"廉价初检"快速拒绝;这里 getData() 后用真实 data.length 复核,
|
|
198
|
-
// 防止恶意 zip 在 header 上撒谎绕过总大小限制。任一阶段超额都抛 ZIP_BOMB。
|
|
199
|
-
let actualTotal = 0;
|
|
200
|
-
for (const e of entries) {
|
|
201
|
-
if (e.isDirectory) continue;
|
|
202
|
-
if (skillRootPrefix && !e.entryName.startsWith(skillRootPrefix)) continue;
|
|
203
|
-
const rel = skillRootPrefix ? e.entryName.slice(skillRootPrefix.length) : e.entryName;
|
|
204
|
-
if (!rel || rel.includes('..')) continue;
|
|
205
|
-
const data = e.getData();
|
|
206
|
-
if (data.length > MAX_PER_FILE) {
|
|
207
|
-
throw Object.assign(new Error('File actual size too large'), { status: 400, code: 'ZIP_BOMB' });
|
|
208
|
-
}
|
|
209
|
-
actualTotal += data.length;
|
|
210
|
-
if (actualTotal > MAX_TOTAL_UNCOMPRESSED) {
|
|
211
|
-
throw Object.assign(new Error('Archive actual size too large'), { status: 400, code: 'ZIP_BOMB' });
|
|
212
|
-
}
|
|
213
|
-
// SKILL.md 统一规范化大小写
|
|
214
|
-
const finalRel = rel.split('/').pop().toLowerCase() === 'skill.md'
|
|
215
|
-
? rel.replace(/[^/]*$/, 'SKILL.md')
|
|
216
|
-
: rel;
|
|
217
|
-
skillFiles.push({ relPath: finalRel, data });
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
if (!validateSkillName(skillName)) {
|
|
222
|
-
throw Object.assign(new Error(`Invalid skill name: ${skillName}`), { status: 400, code: 'INVALID_NAME' });
|
|
223
|
-
}
|
|
224
|
-
const targetDir = join(skillsRoot, skillName);
|
|
225
|
-
// 原子创建:不带 recursive 让 mkdir 在已存在时直接抛 EEXIST,消除 existsSync 与 mkdirSync 之间的 TOCTOU 竞争窗口。
|
|
226
|
-
// skillsRoot 已在前面 mkdirSync(skillsRoot, { recursive: true }) 兜底创建,所以这里 join 出的 parent 一定存在。
|
|
227
|
-
try {
|
|
228
|
-
mkdirSync(targetDir);
|
|
229
|
-
} catch (err) {
|
|
230
|
-
if (err.code === 'EEXIST') {
|
|
231
|
-
throw Object.assign(new Error(`Skill already exists: ${skillName}`), { status: 409, code: 'EXISTS' });
|
|
232
|
-
}
|
|
233
|
-
throw err;
|
|
234
|
-
}
|
|
235
|
-
// 二次防御:resolved + sep 后缀比较,防止 prefix 攻击
|
|
236
|
-
// (e.g. dest=/skills/my-skill-evil/x 不能以 /skills/my-skill 单独 startsWith 通过)
|
|
237
|
-
const resolvedTarget = resolve(targetDir) + sep;
|
|
238
|
-
for (const f of skillFiles) {
|
|
239
|
-
const dest = join(targetDir, f.relPath);
|
|
240
|
-
if (!resolve(dest).startsWith(resolvedTarget)) continue;
|
|
241
|
-
mkdirSync(dirname(dest), { recursive: true });
|
|
242
|
-
writeFileSync(dest, f.data);
|
|
243
|
-
}
|
|
249
|
+
const { skillName, files } = await parseSkillUpload(Buffer.concat(chunks), boundary, windowsReserved);
|
|
250
|
+
const targetDir = writeSkillFiles(skillsRoot, skillName, files);
|
|
244
251
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
245
252
|
res.end(JSON.stringify({ ok: true, name: skillName, path: targetDir }));
|
|
246
253
|
} catch (err) {
|
|
@@ -254,6 +261,14 @@ function skillsImport(req, res, parsedUrl, isLocal, deps) {
|
|
|
254
261
|
});
|
|
255
262
|
}
|
|
256
263
|
|
|
264
|
+
// Skill 上传/导入 API —— 接受 .zip 或 SKILL.md(忽略大小写),写入用户级 ~/.claude/skills/{name}/
|
|
265
|
+
function skillsImport(req, res, parsedUrl, isLocal, deps) {
|
|
266
|
+
importSkillTo(req, res, {
|
|
267
|
+
skillsRoot: join(getClaudeConfigDir(), 'skills'),
|
|
268
|
+
windowsReserved: deps.WINDOWS_RESERVED_NAMES,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
257
272
|
export const skillsRoutes = [
|
|
258
273
|
{ method: 'GET', match: 'exact', path: '/api/skills', handler: skillsList },
|
|
259
274
|
{ method: 'POST', match: 'exact', path: '/api/skills/toggle', handler: skillsToggle },
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
._liveTag_sg7sp_3{position:relative;display:inline-flex;align-items:center;justify-content:flex-start;border-radius:999px;border:1px solid;border-color:var(--ctx-color);color:var(--ctx-color);padding:0 10px;height:100%;font-size:12px;line-height:1;overflow:hidden;transition:border-color .3s,color .3s;white-space:nowrap;background:var(--bg-base-pure)}._liveTagFill_sg7sp_23{position:absolute;left:0;top:0;bottom:0;width:var(--ctx-percent, 0);background-color:var(--ctx-color);opacity:.35;transition:width .5s ease,background-color .3s;pointer-events:none}._liveTagContent_sg7sp_36{position:relative;z-index:1;display:inline-flex;align-items:center}._liveTagHistory_sg7sp_44{background:var(--bg-surface);border-color:var(--border-light);color:var(--text-primary)}._liveTagText_sg7sp_51{margin-left:4px;font-variant-numeric:tabular-nums}._cachePopoverPlaceholder_sg7sp_57{min-width:300px}._editButton_31hdb_10{display:inline-flex;align-items:center;justify-content:center;vertical-align:middle;background:transparent;border:none;padding:0 2px;margin-left:4px;cursor:pointer;font-size:inherit;line-height:1;color:var(--text-secondary, #888);opacity:0;border-radius:3px;transition:opacity .12s ease,color .12s ease,background-color .12s ease}._editButton_31hdb_10 .anticon{display:inline-flex;align-items:center;line-height:1;position:relative;top:-1px}._editButton_31hdb_10:hover,._editButton_31hdb_10:focus-visible{opacity:1;color:var(--text-primary, #333);background-color:var(--bg-hover, rgba(0, 0, 0, .04));outline:none}._editButton_31hdb_10:focus-visible{box-shadow:0 0 0 2px var(--focus-ring, rgba(64, 158, 255, .4))}._footer_31hdb_55{display:flex;justify-content:space-between;align-items:center;gap:8px}._footerLeft_31hdb_61,._footerRight_31hdb_65{display:flex;gap:8px}._projectNameRow_31hdb_70{display:flex;align-items:center;gap:8px;margin-bottom:12px;padding:6px 10px;background:var(--bg-secondary, rgba(0, 0, 0, .03));border-radius:4px;font-size:12px}._projectNameLabel_31hdb_80{color:var(--text-secondary, #888)}._projectNameValue_31hdb_83{color:var(--text-primary, #333);font-family:var(--font-mono, ui-monospace, SFMono-Regular, monospace);word-break:break-all}._panel_ziacd_1{display:flex;flex-direction:column;gap:14px}._required_ziacd_7{margin-left:2px;color:var(--color-error-light, #ff7b7b)}._optional_ziacd_12{margin-left:6px;font-size:12px;font-weight:400;color:var(--text-secondary)}._row_ziacd_19{display:flex;align-items:center;justify-content:space-between;gap:12px}._label_ziacd_26{font-size:14px;font-weight:500}._control_ziacd_31{display:inline-flex;align-items:center;gap:10px}._field_ziacd_37{display:flex;flex-direction:column;gap:6px}._fieldLabel_ziacd_43{font-size:13px;color:var(--text-secondary)}._help_ziacd_48{font-size:12px;color:var(--text-secondary);line-height:1.4}._warn_ziacd_54{font-size:12px;line-height:1.5;color:var(--color-error-light, #ff7b7b)}._hint_ziacd_60{font-size:12px;line-height:1.5;color:var(--text-secondary)}._detailsToggle_ziacd_66{display:inline-flex;align-items:center;gap:5px;align-self:flex-start;padding:0;border:none;background:none;cursor:pointer;font-size:12px;color:var(--text-secondary)}._detailsToggle_ziacd_66:hover{color:var(--text-primary, var(--text-secondary))}._details_ziacd_66{display:flex;flex-direction:column;gap:10px}._actions_ziacd_89{display:flex;justify-content:flex-end;gap:10px;margin-top:4px}._tabRow_1q32a_4{display:flex;align-items:flex-end;gap:6px;padding:0 8px;flex-wrap:nowrap;overflow-x:auto;overflow-y:hidden;scrollbar-width:none;-ms-overflow-style:none}._tabRow_1q32a_4::-webkit-scrollbar{display:none}._tabBtn_1q32a_24{display:inline-flex;align-items:center;gap:7px;padding:8px 16px;font-size:14px;line-height:1.5;border:1px solid transparent;border-radius:10px;background:transparent;color:var(--text-secondary);cursor:pointer;transition:color .18s,border-color .18s,background .18s;white-space:nowrap;flex-shrink:0;-webkit-user-select:none;user-select:none}._tabBtn_1q32a_24:hover{color:var(--color-primary)}._tabBtn_1q32a_24._tabBtnActive_1q32a_52,._tabBtn_1q32a_24._tabBtnActive_1q32a_52:hover{background:var(--bg-container);color:var(--color-primary);border-color:var(--border-primary);border-bottom:2px solid var(--bg-container);border-radius:10px 10px 0 0;font-weight:500;margin-bottom:-1px;position:relative;z-index:2}._toolBody_1q32a_8{border:1px solid var(--border-primary);border-top:none;border-radius:8px;background:var(--bg-container);padding:18px 20px;min-width:0}[data-theme=light] ._toolBody_1q32a_8{box-shadow:0 3px 8px #00000014}[data-theme=light] ._tabBtnActive_1q32a_52{box-shadow:0 -3px 8px #0000000f}[data-theme=dark] ._toolBody_1q32a_8 .ant-input,[data-theme=dark] ._toolBody_1q32a_8 .ant-input-affix-wrapper,[data-theme=dark] ._toolBody_1q32a_8 .ant-select-selector{background-color:var(--bg-elevated)}._scrollBody_1c36l_1{max-height:70vh;overflow-y:auto;overflow-x:hidden;scrollbar-gutter:stable;padding:4px 2px;background:var(--bg-container);border-radius:8px}._headerBar_1c36l_14{display:flex;align-items:center;gap:8px}._titleIcon_1c36l_20{display:inline-flex;align-items:center;width:18px;height:18px}._refreshBtn_1c36l_27{flex:0 0 auto}._center_1c36l_32{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:40px 0;color:var(--text-secondary)}._hint_1c36l_42{color:var(--text-secondary);font-size:13px}._chip_bvaji_4{position:relative;line-height:0;cursor:pointer;-webkit-user-select:none;user-select:none;transition:opacity .15s}._chip_bvaji_4:hover{opacity:.75}._logo_bvaji_16{display:block;transition:color .15s}._connecting_bvaji_22{opacity:.5}._dotError_bvaji_27{position:absolute;right:-2px;top:-2px;width:6px;height:6px;border-radius:50%;background:var(--color-error, #ff4d4f);pointer-events:none}._modelCard_1gpju_3{border:1px solid var(--border-secondary);border-radius:6px;padding:8px 10px;background:var(--bg-container)}._modelName_1gpju_9{font-size:13px;font-weight:600;color:var(--text-primary);margin-bottom:8px;padding-bottom:4px;border-bottom:1px solid var(--border-secondary)}._statsTable_1gpju_17{width:100%;border-collapse:collapse}._th_1gpju_21{padding:2px 12px;font-size:12px;font-family:monospace;white-space:nowrap;color:var(--text-tertiary);font-weight:400;text-align:right}._td_1gpju_30{padding:2px 12px;font-size:12px;font-family:monospace;white-space:nowrap;color:var(--text-primary);text-align:right}._label_1gpju_38{padding:2px 12px;font-size:12px;font-family:monospace;white-space:nowrap;color:var(--text-light);font-weight:400;text-align:left}._rowBorder_1gpju_47{border-bottom:1px solid var(--border-primary)}._rebuildTotalRow_1gpju_50{border-top:1px solid var(--border-light)}._rebuildTotalRow_1gpju_50 td{font-weight:600}._cachePopoverEmpty_1gpju_56{padding:8px 4px;color:var(--text-tertiary);font-size:13px}._toolChipGrid_1gpju_61{display:flex;flex-wrap:wrap;gap:4px;padding:2px 0 6px 2px}._cacheToolChip_1gpju_67{font-size:11px;padding:0 6px;border-radius:3px;background:var(--bg-surface);color:var(--text-secondary);line-height:18px;max-width:280px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;border:1px solid var(--border-primary);cursor:help}._titleIcon_1gpju_81{margin-right:8px}._detailMarkdownCard_1gpju_86{border:1px solid var(--border-secondary);border-radius:6px;padding:8px 10px;background:var(--bg-container)}._memoryMarkdown_1gpju_93{font-size:12.5px;line-height:1.55;color:var(--text-primary);word-break:break-word}._memoryMarkdown_1gpju_93 p{margin:0 0 6px}._memoryMarkdown_1gpju_93 ul,._memoryMarkdown_1gpju_93 ol{margin:4px 0 6px;padding-left:20px}._memoryMarkdown_1gpju_93 li{margin:2px 0}._memoryMarkdown_1gpju_93 h1,._memoryMarkdown_1gpju_93 h2,._memoryMarkdown_1gpju_93 h3,._memoryMarkdown_1gpju_93 h4{font-size:13px;font-weight:600;margin:8px 0 4px;color:var(--text-primary)}._memoryMarkdown_1gpju_93 h1{font-size:14px}._memoryMarkdown_1gpju_93 a{color:var(--primary-color, #1677ff);text-decoration:none;cursor:pointer}._memoryMarkdown_1gpju_93 a:hover{text-decoration:underline}._memoryMarkdown_1gpju_93 code{font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12px;padding:1px 4px;border-radius:3px;background:var(--bg-surface);color:var(--text-primary)}._memoryMarkdown_1gpju_93 pre{margin:6px 0;padding:8px 10px;border-radius:4px;background:var(--bg-surface);overflow-x:auto}._memoryMarkdown_1gpju_93 pre code{padding:0;background:transparent;font-size:12px}._memoryMarkdown_1gpju_93 blockquote{margin:4px 0;padding:2px 8px;border-left:3px solid var(--border-hover);color:var(--text-secondary)}._memoryMarkdown_1gpju_93 hr{margin:8px 0;border:none;border-top:1px solid var(--border-secondary)}._headerBar_uzc7r_2{display:flex;align-items:center;justify-content:space-between;width:100%;height:100%}._logoWrap_uzc7r_10{display:inline-flex;align-items:center;position:relative;margin-top:14px}._logoWrapActive_uzc7r_17:after{content:"";position:absolute;top:-10px;bottom:-10px;left:0;right:-200px}._logoImage_uzc7r_26{height:24px;width:24px;border-radius:3px;vertical-align:middle;opacity:.75;transition:opacity .2s;cursor:pointer}._logoImageActive_uzc7r_36{opacity:1}._compactBtn_uzc7r_43{font-size:12px;height:30px;display:inline-flex;align-items:center;justify-content:center}._compactBtnNoBorder_uzc7r_52{width:30px;height:30px;min-width:30px;padding:0;border:none;font-size:18px;line-height:1;display:inline-flex;align-items:center;justify-content:center}._compactBtnNoBorder_uzc7r_52 .anticon{display:inline-flex;align-items:center;justify-content:center;line-height:0}._headerProjectName_uzc7r_79{font-size:12px;color:inherit;white-space:nowrap}._headerProjectName_uzc7r_79:hover [data-alias-edit-trigger],._headerProjectName_uzc7r_79:focus-within [data-alias-edit-trigger]{opacity:.55}._countdownStrong_uzc7r_96{font-variant-numeric:tabular-nums}._qrcodePopover_uzc7r_101{display:flex;flex-direction:column;align-items:center;padding:8px}._qrcodeSection_uzc7r_108{display:flex;flex-direction:column;align-items:center;padding:16px;margin-bottom:12px;border:1px solid var(--border-secondary);border-radius:8px;background:var(--bg-container)}._qrcodeTitle_uzc7r_119{font-size:14px;font-weight:600;color:var(--text-primary);margin-bottom:12px}._qrcodeUrlInput_uzc7r_126{margin-top:12px;font-size:12px;font-family:Menlo,Monaco,monospace}._qrcodeUrlCopy_uzc7r_132{cursor:pointer;color:var(--text-tertiary);transition:color .2s}._qrcodeUrlCopy_uzc7r_132:hover{color:var(--color-primary-light)}._authSection_uzc7r_143{display:flex;flex-direction:column;align-items:stretch;box-sizing:border-box;width:100%;margin-top:12px;padding:12px;border:1px solid var(--border-secondary);border-radius:8px;background:var(--bg-container);gap:8px}._authHeaderRow_uzc7r_157{display:flex;align-items:center;justify-content:space-between}._authTitle_uzc7r_163{font-size:13px;font-weight:600;color:var(--text-primary)}._authPasswordLabel_uzc7r_169{font-size:12px;color:var(--text-secondary)}._authPasswordInput_uzc7r_174{font-size:12px;font-family:Menlo,Monaco,monospace}._authSaveBtn_uzc7r_179{align-self:flex-end}._authEmptyWarn_uzc7r_183{font-size:12px;color:var(--color-error-light);line-height:1.4}._settingsGroupBox_uzc7r_191{border:1px solid var(--border-secondary);border-radius:8px;background:var(--bg-container);padding:4px 16px;margin-bottom:12px}._settingsGroupTitle_uzc7r_199{font-size:14px;font-weight:600;color:var(--text-primary);padding:12px 0 4px;border-bottom:1px solid var(--border-secondary)}._settingsItem_uzc7r_208{display:flex;justify-content:space-between;align-items:center;padding:12px 0}._settingsLabel_uzc7r_215{font-size:14px}._settingsHelpIcon_uzc7r_220{font-size:16px;color:var(--text-disabled);cursor:help;margin-left:4px}._settingsDivider_uzc7r_227{border-top:1px solid var(--border-primary);margin:12px 0}._logDirInput_uzc7r_232{margin-top:8px;background:var(--bg-base-alt);border-color:var(--border-light);color:var(--text-primary);font-family:monospace;font-size:13px}._tokenStatsEmpty_uzc7r_242{padding:8px 4px;color:var(--text-tertiary);font-size:13px}._tokenStatsContainer_uzc7r_249{display:flex;gap:12px;align-items:flex-start}._tokenStatsColumn_uzc7r_255{min-width:240px}._toolStatsColumn_uzc7r_259{min-width:180px}._modelCardSpaced_uzc7r_265{margin-bottom:10px}._rebuildCard_uzc7r_274{border:1px solid var(--border-secondary);border-radius:6px;padding:8px 10px;margin-top:10px;background:var(--bg-container)}._promptExportBar_uzc7r_283{margin-bottom:12px}._promptScrollArea_uzc7r_287{max-height:500px;overflow:auto}._promptEmpty_uzc7r_292{color:var(--text-tertiary);padding:12px}._promptTimestamp_uzc7r_298{color:var(--text-muted);font-size:12px;margin:12px 0 4px;padding-bottom:6px}._textPromptCard_uzc7r_306{margin:4px 0;background:var(--bg-container);border-radius:6px;border:1px solid var(--border-secondary);padding:10px 14px}._preText_uzc7r_315{white-space:pre-wrap;word-break:break-word;font-size:13px;line-height:1.6;color:var(--text-primary);margin:4px 0}._systemCollapse_uzc7r_325{margin:4px 0;background:var(--bg-elevated);border:1px solid var(--border-secondary);border-radius:6px}._systemLabel_uzc7r_332{color:var(--text-tertiary);font-size:12px}._preSys_uzc7r_337{white-space:pre-wrap;word-break:break-word;font-size:12px;line-height:1.5;color:var(--text-tertiary);margin:0}._promptTextarea_uzc7r_347{box-sizing:border-box;background:var(--bg-base-pure);width:100%;min-height:400px;color:var(--text-primary);font-family:monospace;font-size:13px;line-height:1.6;border:none;resize:vertical;padding:10px 14px;outline:none}._projectStatsCenter_uzc7r_363{display:flex;justify-content:center;padding:40px 0}._projectStatsEmpty_uzc7r_369{color:var(--text-tertiary);padding:40px 0;text-align:center;font-size:13px}._projectStatsContent_uzc7r_376{display:flex;flex-direction:column;gap:16px}._projectStatsUpdated_uzc7r_382{color:var(--text-muted);font-size:12px;text-align:right}._projectStatsSummary_uzc7r_388{display:grid;grid-template-columns:1fr 1fr;gap:10px}._projectStatCard_uzc7r_394{background:var(--bg-container);border:1px solid var(--border-secondary);border-radius:8px;padding:14px 12px;text-align:center}._projectStatValue_uzc7r_402{font-size:22px;font-weight:700;color:var(--text-primary);font-family:monospace;font-variant-numeric:tabular-nums}._projectStatLabel_uzc7r_410{font-size:12px;color:var(--text-tertiary);margin-top:4px}._projectStatsSection_uzc7r_416{display:flex;flex-direction:column;gap:10px}._projectStatsSectionTitle_uzc7r_422{font-size:14px;font-weight:600;color:var(--text-secondary);padding-bottom:4px;border-bottom:1px solid var(--border-secondary)}._projectStatsModelCard_uzc7r_430{background:var(--bg-container);border:1px solid var(--border-secondary);border-radius:6px;padding:10px 12px}._projectStatsModelHeader_uzc7r_437{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;padding-bottom:4px;border-bottom:1px solid var(--border-secondary)}._projectStatsModelName_uzc7r_446{font-size:13px;font-weight:600;color:var(--text-primary)}._projectStatsModelCount_uzc7r_452{font-size:12px;color:var(--text-tertiary);font-family:monospace}._cacheCopyBtn_uzc7r_460{font-size:14px;color:var(--text-tertiary);cursor:pointer;transition:color .2s;margin-left:8px}._cacheCopyBtn_uzc7r_460:hover{color:var(--text-primary)}._cacheTokenInfo_uzc7r_472{display:flex;align-items:center;font-size:12px;font-family:monospace;color:var(--text-light);margin-bottom:8px}._cacheCodeBlock_uzc7r_485{white-space:pre-wrap;word-break:break-word;font-size:12px;line-height:1.5;color:var(--text-secondary);background:var(--bg-container);border:1px solid var(--border-primary);border-radius:4px;padding:8px;margin:4px 0;font-family:Menlo,Monaco,monospace}._cacheCodeBlockSystem_uzc7r_499{white-space:pre-wrap;word-break:break-word;font-size:12px;line-height:1.5;color:var(--text-secondary);background:var(--bg-code-system);border:1px solid var(--border-code-system);border-radius:4px;padding:8px;margin:4px 0;font-family:Menlo,Monaco,monospace}._cacheNavBtn_uzc7r_513{margin-left:auto;font-size:11px;color:var(--color-primary);cursor:pointer;border:1px solid var(--color-primary);border-radius:3px;padding:1px 6px;white-space:nowrap}._cacheNavBtn_uzc7r_513:hover{background:var(--color-primary-bg-light)}._cacheNavList_uzc7r_528{width:600px;max-height:300px;overflow-y:auto}._cacheNavItem_uzc7r_534{padding:4px 8px;font-size:12px;color:var(--text-secondary);cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;border-radius:3px}._cacheNavItem_uzc7r_534:hover{background:var(--color-primary-bg-lighter);color:var(--text-white)}._cacheBlockHighlight_uzc7r_550{box-shadow:0 0 10px var(--color-primary-shadow);transition:box-shadow .2s ease-in}._cacheBlockHighlightFading_uzc7r_555{box-shadow:0 0 10px transparent;transition:box-shadow 3s ease-out}._cacheBorderSvg_uzc7r_560{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;pointer-events:none;overflow:visible}._cacheBorderSvgFading_uzc7r_569{opacity:0;transition:opacity 3s ease-out}._cacheBorderRect_uzc7r_574{animation:_cacheDashRotate_uzc7r_1 4s linear infinite}@keyframes _cacheDashRotate_uzc7r_1{0%{stroke-dashoffset:0}to{stroke-dashoffset:-100}}._thLeft_uzc7r_584{text-align:left}._cacheWriteToken_uzc7r_599{color:var(--color-code-orange)}._cacheReadToken_uzc7r_603{color:var(--color-success)}._cacheCtxPercent_uzc7r_607{color:var(--text-tertiary);margin-left:6px}._qrcodeIcon_uzc7r_627{width:30px;height:30px;padding:6px;box-sizing:border-box;color:var(--text-secondary);cursor:pointer;border-radius:6px;transition:color .15s ease,background-color .15s ease;display:inline-flex;align-items:center;justify-content:center;vertical-align:middle;background:none;border:none}._qrcodeIcon_uzc7r_627:hover{color:var(--text-primary);background:var(--bg-hover, rgba(0, 0, 0, .04))}._qrcodeIcon_uzc7r_627:focus-visible{outline:2px solid var(--primary-color, #1677ff);outline-offset:1px}._approvalBell_uzc7r_653{position:relative;width:30px;height:30px;padding:6px;box-sizing:border-box;color:var(--color-warning, #faad14);cursor:pointer;border-radius:6px;transition:color .15s ease,background-color .15s ease;display:inline-flex;align-items:center;justify-content:center;vertical-align:middle;background:none;border:none}._approvalBell_uzc7r_653:hover{color:var(--text-primary);background:var(--bg-hover, rgba(0, 0, 0, .04))}._approvalBell_uzc7r_653:focus-visible{outline:2px solid var(--primary-color, #1677ff);outline-offset:1px}._approvalBellBadge_uzc7r_678{position:absolute;top:0;right:0;min-width:14px;height:14px;padding:0 3px;box-sizing:border-box;background:var(--color-error, #ff4d4f);color:#fff;font-size:9px;line-height:14px;border-radius:7px;text-align:center;font-weight:600}._proxySwapIcon_uzc7r_695{margin-right:4px;font-size:11px}._proxyProfileTag_uzc7r_701{border-radius:12px;background:var(--border-secondary);border:1px solid var(--border-light);color:var(--text-tertiary);font-size:12px;cursor:pointer;transition:color .2s,border-color .2s}._proxyProfileTag_uzc7r_701:hover{color:var(--text-secondary);border-color:var(--text-disabled)}._pinnedShortcut_uzc7r_717{height:24px;margin-top:11px;display:inline-flex;align-items:center;justify-content:center;font-size:16px;line-height:1;color:var(--text-tertiary);opacity:.75;cursor:pointer;transition:opacity .2s,color .2s}._pinnedShortcut_uzc7r_717:hover{opacity:1;color:var(--text-secondary)}._pinnedShortcut_uzc7r_717 .anticon{display:inline-flex;align-items:center;justify-content:center;line-height:0}._themeToggle_uzc7r_746{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;vertical-align:middle;width:30px;height:30px;padding:0;border:none;border-radius:6px;background:transparent;color:var(--text-secondary);cursor:pointer;transition:background-color .2s ease,color .2s ease;flex-shrink:0;outline:none}._themeToggle_uzc7r_746:hover{background:var(--color-bg-hover, rgba(127, 127, 127, .12));color:var(--text-primary)}._themeToggle_uzc7r_746:focus-visible{box-shadow:0 0 0 2px var(--primary-color, #1677ff)}._themeToggleIcon_uzc7r_772{display:block}._headerRightRow_uzc7r_777 .ant-space-item{display:inline-flex;align-items:center}._headerCountdownTag_uzc7r_783{height:30px;margin:0;padding:0 10px;display:inline-flex;align-items:center;background:var(--bg-surface);border:1px solid var(--border-hover);border-radius:6px;line-height:1}._centerEmpty_midza_1{display:flex;align-items:center;justify-content:center;height:100%}._scrollContainer_midza_8{overflow:auto;height:100%;-webkit-overflow-scrolling:touch;will-change:scroll-position}._listItem_midza_15{cursor:pointer;padding:8px 12px;border-left:6px solid transparent;border-right:1px solid var(--border-primary);border-top:1px solid transparent;border-bottom:1px solid var(--border-primary);transition:background .15s}._listItem_midza_15:hover{border-left-color:var(--border-hover)}._listItemActive_midza_29{background:var(--color-primary-bg-faint);border-left-color:var(--color-primary-light);border-right:1px solid var(--color-primary-light);border-top:1px solid var(--color-primary-light);border-bottom:1px solid var(--color-primary-light)}._listItemActive_midza_29,._listItemActive_midza_29:hover{background:var(--color-primary-bg-faint);border-left-color:var(--color-primary-light);border-right-color:var(--color-primary-light);border-top-color:var(--color-primary-light);border-bottom-color:var(--color-primary-light)}._itemContent_midza_46{width:100%;min-width:0}._itemHeader_midza_51{display:flex;align-items:center;gap:6px;margin-bottom:4px;font-size:12px}._tagNoMargin_midza_59{margin:0;font-size:12px}._modelName_midza_64{font-size:12px;color:var(--text-tertiary)}._modelNameMain_midza_69{color:var(--color-code-orange)}._time_midza_73{font-size:12px;color:var(--text-gray);margin-left:auto}._detailRow_midza_79{display:flex;gap:8px;font-size:12px;align-items:center}._urlText_midza_86{color:var(--text-disabled);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}._duration_midza_95{color:var(--text-gray);flex-shrink:0}._statusOk_midza_100{color:var(--color-success);opacity:.5;flex-shrink:0}._statusErr_midza_106{color:var(--color-error);flex-shrink:0}._statusDefault_midza_111{color:var(--text-tertiary);flex-shrink:0}._usageBox_midza_116{background:var(--bg-container);border-radius:4px;padding:3px 6px;margin-top:4px;font-size:12px;color:var(--text-gray);line-height:1.6}._cacheDot_midza_126{display:inline-block;width:6px;height:6px;border-radius:50%;margin:0 3px;vertical-align:middle}._cacheDotLoss_midza_135{background-color:var(--color-red-dark-bg);cursor:help}._cacheDotNormal_midza_140{background-color:var(--border-hover)}._tagMainAgent_midza_144{color:var(--color-code-orange);border-color:var(--color-code-orange-border);background:var(--color-code-orange-bg)}._tagPlan_midza_150{color:var(--color-error-muted);border-color:var(--color-error-muted);background-color:var(--bg-base-pure)}._tagMuted_midza_156{color:var(--text-muted);border-color:var(--border-light);background-color:var(--bg-base-pure)}._tooltipPreLine_midza_162{white-space:pre-line}._GzYRV{line-height:1.2;white-space:pre-wrap;white-space:-moz-pre-wrap;white-space:-pre-wrap;white-space:-o-pre-wrap;word-wrap:break-word}._3eOF8{margin-right:5px;font-weight:700}._3eOF8+._3eOF8{margin-left:-5px}._1MFti{cursor:pointer}._f10Tu{font-size:1.2em;margin-right:5px;-webkit-user-select:none;-moz-user-select:none;user-select:none}._1UmXx:after{content:"▸"}._1LId0:after{content:"▾"}._1pNG9{margin-right:5px}._1pNG9:after{content:"...";font-size:.8em}._2IvMF{background:#eee}._2bkNM{margin:0;padding:0 10px}._1BXBN{margin:0;padding:0}._1MGIk{font-weight:600;margin-right:5px;color:#000}._3uHL6{color:#000}._2T6PJ,._1Gho6{color:#df113a}._vGjyY{color:#2a3f3c}._1bQdo{color:#0b75f5}._3zQKs{color:#469038}._1xvuR{color:#43413d}._oLqym,._2AXVT,._2KJWg{color:#000}._11RoI{background:#002b36}._17H2C,._3QHg2,._3fDAz{color:#fdf6e3}._2bSDX{font-weight:bolder;margin-right:5px;color:#fdf6e3}._gsbQL{color:#fdf6e3}._LaAZe,._GTKgm{color:#81b5ac}._Chy1W{color:#cb4b16}._2bveF{color:#d33682}._2vRm-{color:#ae81ff}._1prJR{color:#268bd2}._container_qeuid_1{background:var(--bg-container);border-radius:6px;border:1px solid var(--border-primary);padding:12px;font-size:13px;font-family:monospace;overflow:auto}._root_17dqd_1{display:flex;height:100%;min-height:0;gap:0}._sidebar_17dqd_9{width:220px;flex-shrink:0;border-right:1px solid var(--border-primary);overflow-y:auto;padding:4px 0;-webkit-overflow-scrolling:touch}._section_17dqd_18{-webkit-user-select:none;user-select:none}._sectionHeader_17dqd_22{display:flex;align-items:center;gap:6px;width:100%;padding:6px 10px;cursor:pointer;color:var(--text-primary);font-size:12px;font-weight:600;transition:background .15s;background:none;border:0;text-align:left;font:inherit}._sectionHeader_17dqd_22:hover{background:var(--overlay-light-faint)}._sectionHeader_17dqd_22:focus-visible{outline:1px solid var(--color-primary-outline);outline-offset:-1px}._arrow_17dqd_48{font-size:10px;color:var(--text-tertiary);flex-shrink:0}._sectionTitle_17dqd_54{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}._sectionCount_17dqd_62{font-size:11px;color:var(--text-muted);background:var(--bg-elevated);border-radius:10px;padding:0 6px;line-height:18px}._sectionBody_17dqd_71{padding:2px 0}._historyToggle_17dqd_76{display:flex;align-items:center;gap:6px;width:100%;padding:4px 10px 4px 14px;cursor:pointer;color:var(--text-muted);font-size:11px;transition:color .15s,background .15s;background:none;border:0;text-align:left;font:inherit}._historyToggle_17dqd_76:hover{color:var(--text-tertiary);background:var(--overlay-light-faint)}._historyToggle_17dqd_76:focus-visible{outline:1px solid var(--color-primary-outline);outline-offset:-1px}._historyToggleLabel_17dqd_102{flex:1}._item_17dqd_107{display:flex;align-items:center;justify-content:space-between;width:100%;padding:4px 9px 4px 23px;font-size:12px;color:var(--text-tertiary);cursor:pointer;transition:background .15s,color .15s;background:none;border:1px solid transparent;border-radius:4px;box-sizing:border-box;text-align:left;font:inherit}._item_17dqd_107:hover{background:var(--overlay-light-faint);color:var(--text-primary)}._item_17dqd_107:focus-visible{outline:1px solid var(--color-primary-outline);outline-offset:-1px}._itemActive_17dqd_135,._itemActive_17dqd_135:hover{background:var(--color-primary-bg-faint);color:var(--color-primary);border-color:var(--color-primary-light)}._itemContent_17dqd_142{flex:1;min-width:0;overflow:hidden}._itemLabel_17dqd_148{font-family:monospace;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:block}._itemSublabel_17dqd_156{font-size:10px;color:var(--text-disabled);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:1px}._itemTime_17dqd_165{font-size:9px;color:var(--text-disabled);flex-shrink:0;margin-left:4px;font-family:monospace}._content_17dqd_174{flex:1;min-width:0;overflow:auto;padding:12px 16px;-webkit-overflow-scrolling:touch}._contentEmpty_17dqd_182{height:100%;display:flex;align-items:center;justify-content:center}._contentInner_17dqd_189{padding-bottom:20px}._emptyWrap_17dqd_193{display:flex;align-items:center;justify-content:center;height:200px}._roleHeader_17dqd_201{display:flex;align-items:center;gap:8px;margin-bottom:8px;flex-wrap:nowrap}._roleBadge_17dqd_209{font-size:10px;font-weight:600;letter-spacing:.04em;padding:2px 7px;border-radius:4px;flex-shrink:0}._role_user_17dqd_218{background:var(--color-primary-bg-lighter);color:var(--color-primary-lighter);border:1px solid var(--color-primary-bg-medium)}._role_assistant_17dqd_224{background:var(--color-purple-bg);color:var(--color-code-purple);border:1px solid var(--color-purple-border)}._roleLabel_17dqd_230{font-size:11px;color:var(--text-muted);flex:1;min-width:0}._contentTime_17dqd_237{margin-left:auto;font-size:10px;color:var(--text-disabled);font-family:monospace;flex-shrink:0}._turnDivider_17dqd_245{border:none;border-top:1px solid var(--border-primary);margin:14px 0}._textBlock_17dqd_252{margin-bottom:8px;border:1px solid var(--border-primary);border-radius:6px;overflow:hidden}._textBlockBar_17dqd_259{display:flex;align-items:center;gap:6px;padding:4px 10px;background:var(--bg-container);border-bottom:1px solid var(--border-primary)}._textBlockBody_17dqd_268{padding:10px 12px;font-size:13px;line-height:1.7;color:var(--text-primary);word-break:break-word}._textBlockCompact_17dqd_276{position:relative;padding:8px 10px;font-size:12px;color:var(--text-light)}._textBlockCompactFloat_17dqd_283{float:right;margin-left:6px;margin-bottom:2px}._thinkingBlock_17dqd_290{margin-bottom:8px;border:1px solid var(--color-thinking-border);border-radius:6px;overflow:hidden;background:var(--color-thinking-bg)}._thinkingHeader_17dqd_298{display:flex;align-items:center;gap:6px;padding:5px 10px;cursor:pointer;font-size:12px;color:var(--text-tertiary);transition:background .15s}._thinkingHeader_17dqd_298:hover{background:var(--overlay-light-faint)}._thinkingPreview_17dqd_313{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text-muted);font-size:11px}._thinkingBody_17dqd_323{padding:8px 12px;border-top:1px solid var(--color-thinking-border)}._toolBlock_17dqd_329{margin-bottom:8px;border:1px solid var(--border-primary);border-radius:6px;overflow:hidden}._toolBlockResult_17dqd_336{border-color:var(--color-green-border)}._toolBlockError_17dqd_340{border-color:var(--color-red-dark-border)}._toolBlockHeader_17dqd_344{display:flex;align-items:center;gap:8px;padding:5px 10px;background:var(--bg-container);border-bottom:1px solid var(--border-primary);font-size:12px;flex-wrap:wrap}._toolBlockBody_17dqd_355{padding:8px 10px;font-size:12px}._toolName_17dqd_360{color:var(--text-primary);font-weight:500;font-family:monospace}._toolId_17dqd_366{color:var(--text-disabled);font-size:10px;font-family:monospace;margin-left:auto}._errorLabel_17dqd_373{font-size:10px;color:var(--color-error-light);background:var(--color-error-bg-light);border:1px solid var(--color-error-border);border-radius:3px;padding:1px 5px}._blockTag_17dqd_383{font-size:9px;font-weight:600;letter-spacing:.05em;text-transform:uppercase;padding:1px 5px;border-radius:3px;background:var(--bg-elevated);color:var(--text-muted);border:1px solid var(--border-primary);flex-shrink:0}._blockTagText_17dqd_396{background:var(--color-primary-bg-faint);color:var(--color-primary-lighter);border-color:var(--color-primary-bg-lighter)}._blockTagThinking_17dqd_402{background:var(--color-warning-bg-faint);color:var(--color-warning);border-color:var(--color-warning-border-light)}._blockTagResult_17dqd_408{background:var(--color-green-dark-bg);color:var(--color-success);border-color:var(--color-green-dark-border)}._blockTagError_17dqd_414{background:var(--color-error-bg-faint);color:var(--color-error-light);border-color:var(--color-error-border-light)}._jsonBlock_17dqd_421{margin-bottom:8px;border:1px solid var(--border-primary);border-radius:6px;overflow:hidden}._jsonBlockLabel_17dqd_428{font-size:10px;color:var(--text-muted);padding:3px 10px;background:var(--bg-container);border-bottom:1px solid var(--border-primary);font-family:monospace}._blockSeparator_17dqd_438{border:none;border-top:1px solid var(--border-primary);margin:16px 0}._markdownBody_17dqd_445{font-size:13px;line-height:1.7;color:var(--text-primary);word-break:break-word}._container_rg6mx_1{height:100%;overflow:hidden;padding:0 16px;display:flex;flex-direction:column;background:var(--bg-base)}._emptyState_rg6mx_10{display:flex;align-items:center;justify-content:center;height:100%}._urlSection_rg6mx_17{padding:12px 0;border-bottom:1px solid var(--border-primary);display:flex;align-items:flex-start;flex-shrink:0}._urlLeft_rg6mx_25{flex:1;min-width:0}._tokenStatsBox_rg6mx_30{flex-shrink:0;padding-left:12px;display:flex;align-items:center}._tokenGrid_rg6mx_37{display:flex;border:1px solid var(--border-secondary);border-radius:6px;overflow:hidden;min-width:360px;font-size:11px;line-height:1.6}._tokenRows_rg6mx_47{flex:1}._tokenRow_rg6mx_47{display:flex}._tokenRowBorder_rg6mx_55{border-top:1px solid var(--border-secondary)}._tokenLabel_rg6mx_59{color:var(--text-tertiary);padding:4px 8px;white-space:nowrap;font-weight:600}._tokenTd_rg6mx_66{flex:1;color:var(--text-primary);text-align:right;padding:4px 8px;font-family:monospace;white-space:nowrap}._tokenHitRate_rg6mx_75{display:flex;flex-direction:column;align-items:center;justify-content:center;color:var(--text-primary);padding:4px 8px;font-family:monospace;white-space:nowrap;border-left:1px solid var(--border-secondary);min-width:100px}._tokenHitRateLabel_rg6mx_88{color:var(--text-tertiary);font-size:10px;font-family:sans-serif}._tokenRowBorder_rg6mx_55 td{border-top:1px solid var(--border-secondary)}._urlText_rg6mx_98{color:var(--text-primary);font-size:13px;margin-bottom:8px;word-break:break-all}._metaText_rg6mx_105,._headersContainer_rg6mx_109{font-size:12px}._headerRow_rg6mx_113{display:flex;padding:4px 0;border-bottom:1px solid var(--border-primary)}._headerKey_rg6mx_119{min-width:200px;flex-shrink:0}._headerValue_rg6mx_124{word-break:break-all;margin-left:8px}._streamingBox_rg6mx_129{padding:20px;background:var(--bg-elevated);border-radius:6px;border:1px solid var(--border-primary)}._bodyToolbar_rg6mx_136{display:flex;gap:8px;margin-bottom:8px}._rawTextPre_rg6mx_142{background:var(--bg-code-dark);border:1px solid var(--border-primary);border-radius:6px;padding:12px;font-size:12px;color:var(--text-primary);overflow:auto;max-height:600px;white-space:pre-wrap;word-break:break-all}._tabContent_rg6mx_155{padding:16px 0 0;height:100%;overflow-y:auto;-webkit-overflow-scrolling:touch}._collapseSpacing_rg6mx_163{margin-bottom:16px}._bodyLabel_rg6mx_167{margin:0}._bodyHeader_rg6mx_171{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}._diffSection_rg6mx_178{margin-bottom:16px}._diffToggle_rg6mx_182{display:inline-block;margin-bottom:8px;cursor:pointer}._diffIcon_rg6mx_188{font-size:12px;margin-left:4px}._viewInChatBtn_rg6mx_193{display:inline-flex;align-items:center;height:26px;border-radius:13px;border:1px solid var(--border-light);background:#0000;color:var(--text-tertiary);cursor:pointer;font-size:12px;transition:all .2s;padding:0 10px;white-space:nowrap}._viewInChatBtn_rg6mx_193:hover{border-color:var(--text-muted);color:var(--text-primary);background:var(--overlay-light-faint)}._reminderSelect_rg6mx_214{min-width:140px;font-size:12px}._reminderSelect_rg6mx_214 .ant-select-selector.ant-select-selector{border-radius:2px;border-color:var(--border-light);background:#0000;min-height:26px;height:auto;padding:0 8px;font-family:monospace}._reminderSelect_rg6mx_214 .ant-select-selection-placeholder{font-size:11px}._diffHeaderRow_rg6mx_233{display:flex;align-items:center;gap:8px}._diffSpaceRight_rg6mx_239{margin-left:auto}._reminderFilterWrapper_rg6mx_243{display:inline-flex;align-items:center;gap:4px}._reminderLabel_rg6mx_249{color:var(--text-tertiary);font-size:12px;font-family:monospace}._cacheTabContent_rg6mx_255{padding-top:0;overflow:hidden}._userPromptList_rg6mx_260{width:600px;max-height:300px;overflow-y:auto}._userPromptItem_rg6mx_266{padding:4px 8px;font-size:12px;color:var(--text-secondary);cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;border-radius:3px}._userPromptNavBtn_rg6mx_277{margin-left:auto;font-size:11px;color:var(--color-primary);cursor:pointer;border:1px solid var(--color-primary);border-radius:3px;padding:1px 6px;white-space:nowrap}._cacheContent_rg6mx_288{padding:8px 0;height:100%;display:flex;flex-direction:column}._cacheTokenBar_rg6mx_295{display:flex;align-items:center;font-size:12px;font-family:monospace;color:var(--text-light);margin-bottom:12px;flex-shrink:0}._cacheTokenWrite_rg6mx_305{color:var(--color-code-orange)}._cacheTokenRead_rg6mx_309{color:var(--color-success)}._cacheCopyIcon_rg6mx_313{margin-left:8px;cursor:pointer;color:var(--text-tertiary);transition:color .2s}._cacheScrollArea_rg6mx_320{flex:1;overflow-y:auto;min-height:0}._cacheSectionBlock_rg6mx_326{margin-bottom:12px}._cacheSectionHeader_rg6mx_330{font-size:13px;font-weight:600;color:var(--text-primary);margin-bottom:6px;cursor:pointer;-webkit-user-select:none;user-select:none;display:flex;align-items:center;gap:4px}._cacheCollapseArrow_rg6mx_342{display:inline-block;transition:transform .2s;font-size:10px}._cachePre_rg6mx_348{white-space:pre-wrap;word-break:break-word;font-size:12px;line-height:1.5;color:var(--text-secondary);background:var(--bg-container);border:1px solid var(--border-primary);border-radius:4px;padding:8px;margin:4px 0;font-family:Menlo,Monaco,monospace}._cacheHighlightSvg_rg6mx_362{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;pointer-events:none;overflow:visible}._cachePreSystem_rg6mx_371{white-space:pre-wrap;word-break:break-word;font-size:12px;line-height:1.5;color:var(--text-secondary);background:var(--bg-code-system);border:1px solid var(--border-code-system);border-radius:4px;padding:8px;margin:4px 0;font-family:Menlo,Monaco,monospace}._container_rg6mx_1 .ant-tabs>.ant-tabs-nav{margin-bottom:0}._container_rg6mx_1 .ant-tabs{flex:1;min-height:0;display:flex;flex-direction:column}._container_rg6mx_1 .ant-tabs>.ant-tabs-content-holder{flex:1;min-height:0}._container_rg6mx_1 .ant-tabs-content,._container_rg6mx_1 .ant-tabs-tabpane-active{height:100%}._resizer_pzn4b_1{width:6px;cursor:col-resize;background:var(--bg-elevated);flex-shrink:0;transition:background .2s}._resizer_pzn4b_1:hover{background:var(--color-primary-light)}._flag_1q4ri_3{display:inline-flex;align-items:center;justify-content:center;font-size:13px;line-height:1;cursor:help;-webkit-user-select:none;user-select:none;height:14px;background:none;border:none;padding:0;color:inherit;font-family:inherit}._flag_1q4ri_3:focus-visible{outline:2px solid var(--primary-color, #1677ff);outline-offset:2px;border-radius:2px}._popover_1q4ri_27{color:var(--text-secondary);font-size:13px;line-height:22px}._meta_1q4ri_33{color:var(--text-tertiary);font-size:12px}._usagePill_srdlo_4{position:relative;display:inline-flex;align-items:center;justify-content:flex-start;border-radius:999px;border:1px solid;border-color:var(--text-disabled);color:var(--text-disabled);padding:0 7px;height:15px;font-size:11px;line-height:1;overflow:hidden;white-space:nowrap;cursor:default;background:var(--bg-base-pure)}._usageFill_srdlo_25{position:absolute;left:0;top:0;bottom:0;width:var(--usage-percent, 0);background-color:var(--text-disabled);opacity:.25;transition:width .5s ease;pointer-events:none}._usageContent_srdlo_37{position:relative;z-index:1;display:inline-flex;align-items:center}._usageText_srdlo_44{font-variant-numeric:tabular-nums}._muted_srdlo_49{border-color:var(--border-light);color:var(--text-disabled);background:var(--bg-surface)}._pop_srdlo_56{min-width:220px;font-size:12px;color:var(--text-primary)}._popTitle_srdlo_62{font-weight:600;margin-bottom:6px}._popTable_srdlo_68{border-collapse:collapse}._popTable_srdlo_68 td{padding-top:3px;padding-bottom:3px;vertical-align:middle}._tdName_srdlo_73{color:var(--text-secondary);padding-right:5px;white-space:nowrap}._tdBar_srdlo_73{padding-right:5px;white-space:nowrap}._tdReset_srdlo_91{color:var(--text-secondary);font-variant-numeric:tabular-nums;white-space:nowrap}._bar_srdlo_98{position:relative;display:inline-flex;align-items:center;justify-content:center;width:100px;height:14px;border-radius:999px;border:1px solid var(--text-disabled);overflow:hidden;background:var(--bg-base-pure);vertical-align:middle}._barFill_srdlo_112{position:absolute;left:0;top:0;bottom:0;background-color:var(--text-disabled);opacity:.25;pointer-events:none}._barText_srdlo_122{position:relative;z-index:1;font-size:11px;line-height:1;font-variant-numeric:tabular-nums;color:var(--text-primary)}
|