cc-viewer 1.6.291 → 1.6.293
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/cli.js +91 -8
- package/dist/assets/App-DRvRd96X.css +1 -0
- package/dist/assets/App-OM2oqZRW.js +1 -0
- package/dist/assets/{MdxEditorPanel-Csjtf_gU.js → MdxEditorPanel-Cf01KF6Z.js} +1 -1
- package/dist/assets/{Mobile-rcPSQ2e5.js → Mobile-BJlGkvAP.js} +1 -1
- package/dist/assets/{_baseUniq-CY7wER8M.js → _baseUniq-CPUnJ5bQ.js} +1 -1
- package/dist/assets/{arc-DifwFfjI.js → arc-WhuJ-oY5.js} +1 -1
- package/dist/assets/{architectureDiagram-Q4EWVU46-vxisGk93.js → architectureDiagram-Q4EWVU46-CWx77Yhd.js} +1 -1
- package/dist/assets/{blockDiagram-DXYQGD6D-1Z1EuByB.js → blockDiagram-DXYQGD6D-D7AQLCoj.js} +1 -1
- package/dist/assets/{c4Diagram-AHTNJAMY-DtlxU5jH.js → c4Diagram-AHTNJAMY-BoPHNqCF.js} +1 -1
- package/dist/assets/{channel-nzM7I2W4.js → channel-B9Ja6Xkc.js} +1 -1
- package/dist/assets/{chunk-4BX2VUAB-C2UZLxjY.js → chunk-4BX2VUAB-B-b0RYab.js} +1 -1
- package/dist/assets/{chunk-4TB4RGXK-oqTPIHTb.js → chunk-4TB4RGXK-BK_V34yf.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-JcdHyRbR.js → chunk-55IACEB6-D-kMbu-2.js} +1 -1
- package/dist/assets/{chunk-EDXVE4YY-BbbLi1a3.js → chunk-EDXVE4YY-CEtSkZzd.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-DndrHMoU.js → chunk-FMBD7UC4-BXa_7Pn3.js} +1 -1
- package/dist/assets/{chunk-OYMX7WX6-DXUVMfBg.js → chunk-OYMX7WX6-tvM_OApS.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-BMzbbCV2.js → chunk-QZHKN3VN-DrEmcVHf.js} +1 -1
- package/dist/assets/{chunk-YZCP3GAM-CnosXLiO.js → chunk-YZCP3GAM-D2M9T_R5.js} +1 -1
- package/dist/assets/classDiagram-6PBFFD2Q-CCwGJXEA.js +1 -0
- package/dist/assets/classDiagram-v2-HSJHXN6E-CCwGJXEA.js +1 -0
- package/dist/assets/clone-BuQbTPQO.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-C0k93G8C.js → cose-bilkent-S5V4N54A-H7bkwu5F.js} +1 -1
- package/dist/assets/{dagre-KV5264BT-DlvseVFx.js → dagre-KV5264BT-DKXEGN18.js} +1 -1
- package/dist/assets/{diagram-5BDNPKRD-CpeP8gCZ.js → diagram-5BDNPKRD-DZFhwpI3.js} +1 -1
- package/dist/assets/{diagram-G4DWMVQ6-nTZRlkUW.js → diagram-G4DWMVQ6-Crg9GlIk.js} +1 -1
- package/dist/assets/{diagram-MMDJMWI5-CZmxRJHr.js → diagram-MMDJMWI5-B8Qn1fKP.js} +1 -1
- package/dist/assets/{diagram-TYMM5635-D1oFwDYt.js → diagram-TYMM5635-BHE1LjtY.js} +1 -1
- package/dist/assets/{erDiagram-SMLLAGMA-CN56CLXd.js → erDiagram-SMLLAGMA-BaEqFWLd.js} +1 -1
- package/dist/assets/{flowDiagram-DWJPFMVM-DYFYyiT1.js → flowDiagram-DWJPFMVM-b2ukTawV.js} +1 -1
- package/dist/assets/{ganttDiagram-T4ZO3ILL-Cod-6sTs.js → ganttDiagram-T4ZO3ILL-D5quyFgK.js} +1 -1
- package/dist/assets/{gitGraphDiagram-UUTBAWPF-CmOM3xUD.js → gitGraphDiagram-UUTBAWPF-BE1H5_fN.js} +1 -1
- package/dist/assets/{graph-CF0TX7yu.js → graph-D_JLoOax.js} +1 -1
- package/dist/assets/{index-DNOG0Ft9.js → index-B8UmlA4F.js} +1 -1
- package/dist/assets/{index-Ca6ekIeT.css → index-BDUs32pN.css} +1 -1
- package/dist/assets/{index-ZaaYk8_N.js → index-CQrdpZQb.js} +1 -1
- package/dist/assets/{index-Bw6THA8X.js → index-CWjqMDrs.js} +1 -1
- package/dist/assets/index-CnWSVlWW.js +2 -0
- package/dist/assets/{index-BPPzAUWO.js → index-CtrY6gFZ.js} +1 -1
- package/dist/assets/{index-qteuBiB9.js → index-Cx8bk0Tp.js} +1 -1
- package/dist/assets/{index-WPRXfvav.js → index-DiZ9CErG.js} +1 -1
- package/dist/assets/{index-C29CuRSN.js → index-k0AH8cvI.js} +1 -1
- package/dist/assets/{infoDiagram-42DDH7IO-DqXG1YqF.js → infoDiagram-42DDH7IO-DQKlrVkw.js} +1 -1
- package/dist/assets/{ishikawaDiagram-UXIWVN3A-BYqlvzbM.js → ishikawaDiagram-UXIWVN3A-BchFlpPc.js} +1 -1
- package/dist/assets/{journeyDiagram-VCZTEJTY-1p7xwE9T.js → journeyDiagram-VCZTEJTY-Dg1mt4df.js} +1 -1
- package/dist/assets/{jszip.min-56GCbwhg.js → jszip.min-LIb2SFoK.js} +1 -1
- package/dist/assets/{kanban-definition-6JOO6SKY-CyBzEFcP.js → kanban-definition-6JOO6SKY-226va2PS.js} +1 -1
- package/dist/assets/{layout-DU3NNWDD.js → layout-rSa8rcPi.js} +1 -1
- package/dist/assets/{linear-D4VO1BqY.js → linear-BeARi8nH.js} +1 -1
- package/dist/assets/{mermaid.core-Cf-7b6gy.js → mermaid.core-CDgdx9l7.js} +2 -2
- package/dist/assets/{min-CugqfU35.js → min-B9yebCuj.js} +1 -1
- package/dist/assets/{mindmap-definition-QFDTVHPH-C0KcWI5g.js → mindmap-definition-QFDTVHPH-C3apVbdg.js} +1 -1
- package/dist/assets/{pieDiagram-DEJITSTG-CJRW4PvK.js → pieDiagram-DEJITSTG-xjOQoQeL.js} +1 -1
- package/dist/assets/{quadrantDiagram-34T5L4WZ-DhC4nG16.js → quadrantDiagram-34T5L4WZ-Dq8x_VN2.js} +1 -1
- package/dist/assets/{requirementDiagram-MS252O5E-CxREJrbW.js → requirementDiagram-MS252O5E-CLmO1Gai.js} +1 -1
- package/dist/assets/{sankeyDiagram-XADWPNL6-SUJo_IcF.js → sankeyDiagram-XADWPNL6-BuUP1Eqq.js} +1 -1
- package/dist/assets/seqResourceLoaders-BZ6M3Jb-.js +2 -0
- package/dist/assets/{seqResourceLoaders-8TtVkzZF.css → seqResourceLoaders-DWKAvGtj.css} +1 -1
- package/dist/assets/{sequenceDiagram-FGHM5R23-CPXAiSCQ.js → sequenceDiagram-FGHM5R23-B18koU20.js} +1 -1
- package/dist/assets/{stateDiagram-FHFEXIEX-BLVV2eFt.js → stateDiagram-FHFEXIEX-Cj57OCcO.js} +1 -1
- package/dist/assets/{stateDiagram-v2-QKLJ7IA2-C9H3sIEI.js → stateDiagram-v2-QKLJ7IA2-C01a2p--.js} +1 -1
- package/dist/assets/{timeline-definition-GMOUNBTQ-CL2-3Y2a.js → timeline-definition-GMOUNBTQ-cOlsEN_F.js} +1 -1
- package/dist/assets/{vendor-antd-CtM90v5R.js → vendor-antd-DqFS7Zj9.js} +1 -1
- package/dist/assets/{vendor-codemirror-BrlhoIRC.js → vendor-codemirror-B_pF4DrA.js} +1 -1
- package/dist/assets/{vendor-mdxeditor-ERvI0V3G.js → vendor-mdxeditor-B_IrHcWH.js} +2 -2
- package/dist/assets/{vendor-qrcode-DFSKTNnw.js → vendor-qrcode-C4PneAS5.js} +1 -1
- package/dist/assets/{vendor-virtuoso-CvUq0YV8.js → vendor-virtuoso-CEGeJyDP.js} +1 -1
- package/dist/assets/{vennDiagram-DHZGUBPP-BiST9SzL.js → vennDiagram-DHZGUBPP-BCjdwiDk.js} +1 -1
- package/dist/assets/{wardley-RL74JXVD-BdMwjpLp.js → wardley-RL74JXVD-CRmLlBwn.js} +1 -1
- package/dist/assets/{wardleyDiagram-NUSXRM2D-Zqm2aFeN.js → wardleyDiagram-NUSXRM2D-BJYVDJ4F.js} +1 -1
- package/dist/assets/{xychartDiagram-5P7HB3ND-CWKV6dzf.js → xychartDiagram-5P7HB3ND-el5C4S1Z.js} +1 -1
- package/dist/index.html +5 -5
- package/package.json +1 -1
- package/server/i18n.js +60 -0
- package/server/interceptor.js +7 -0
- package/server/lib/im-bridge-core.js +9 -4
- package/server/lib/im-claude-md.js +82 -0
- package/server/lib/im-deny.js +100 -0
- package/server/lib/im-lock.js +184 -0
- package/server/lib/im-process-manager.js +161 -0
- package/server/lib/interceptor-core.js +3 -1
- package/server/lib/perm-bridge.js +17 -0
- package/server/pty-manager.js +24 -3
- package/server/routes/dingtalk.js +38 -13
- package/server/routes/im.js +121 -36
- package/server/server.js +49 -21
- package/dist/assets/App-BIHUxfib.css +0 -1
- package/dist/assets/App-CFikzBui.js +0 -1
- package/dist/assets/classDiagram-6PBFFD2Q-CUXkafJT.js +0 -1
- package/dist/assets/classDiagram-v2-HSJHXN6E-CUXkafJT.js +0 -1
- package/dist/assets/clone-BWXYQRFP.js +0 -1
- package/dist/assets/index-CxAdibqo.js +0 -2
- package/dist/assets/seqResourceLoaders-DSYaGUe4.js +0 -2
|
@@ -18,13 +18,19 @@ function readBody(req, deps, cb) {
|
|
|
18
18
|
req.on('end', () => cb(body));
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
function dingtalkStatus(req, res, parsedUrl, isLocal, deps) {
|
|
22
|
-
|
|
21
|
+
async function dingtalkStatus(req, res, parsedUrl, isLocal, deps) {
|
|
22
|
+
// 与 /api/im/:platform/status 一致:worker 报自身在进程适配器状态(manager 据此探活);
|
|
23
|
+
// 主进程经 manager 报 detached worker 的进程/连接状态。
|
|
24
|
+
let conn;
|
|
25
|
+
let processInfo = null;
|
|
26
|
+
if (deps.dingtalk.isWorker) {
|
|
27
|
+
conn = deps.dingtalk.getBridgeStatus();
|
|
28
|
+
} else {
|
|
29
|
+
processInfo = await deps.dingtalk.getProcessStatus();
|
|
30
|
+
conn = { running: processInfo.running, connected: processInfo.connected };
|
|
31
|
+
}
|
|
23
32
|
res.writeHead(200, JSON_HEADERS);
|
|
24
33
|
if (!isLocal) {
|
|
25
|
-
// Loopback gate: a token-authorized LAN client must not see the appKey, the staffId
|
|
26
|
-
// allowlist, the bound conversation id, or raw error strings. Expose only the minimum the
|
|
27
|
-
// header status chip needs. (config/test are already loopback-only.)
|
|
28
34
|
res.end(JSON.stringify({
|
|
29
35
|
enabled: loadDingTalkState().enabled,
|
|
30
36
|
hasSecret: loadDingTalkState().hasSecret,
|
|
@@ -32,9 +38,14 @@ function dingtalkStatus(req, res, parsedUrl, isLocal, deps) {
|
|
|
32
38
|
}));
|
|
33
39
|
return;
|
|
34
40
|
}
|
|
35
|
-
// 本机(127.0.0.1)= admin
|
|
36
|
-
|
|
37
|
-
|
|
41
|
+
// 本机(127.0.0.1)= admin / manager 探活:附带明文 appSecret 与 pid(供身份匹配)。
|
|
42
|
+
res.end(JSON.stringify({
|
|
43
|
+
...loadDingTalkState(),
|
|
44
|
+
appSecret: loadDingTalkConfig().appSecret,
|
|
45
|
+
connection: conn,
|
|
46
|
+
process: processInfo,
|
|
47
|
+
pid: deps.dingtalk.isWorker ? process.pid : (processInfo?.pid ?? null),
|
|
48
|
+
}));
|
|
38
49
|
}
|
|
39
50
|
|
|
40
51
|
function dingtalkConfigPost(req, res, parsedUrl, isLocal, deps) {
|
|
@@ -44,7 +55,7 @@ function dingtalkConfigPost(req, res, parsedUrl, isLocal, deps) {
|
|
|
44
55
|
res.end(JSON.stringify({ error: 'Loopback only' }));
|
|
45
56
|
return;
|
|
46
57
|
}
|
|
47
|
-
readBody(req, deps, (body) => {
|
|
58
|
+
readBody(req, deps, async (body) => {
|
|
48
59
|
let incoming;
|
|
49
60
|
try { incoming = JSON.parse(body); }
|
|
50
61
|
catch {
|
|
@@ -52,8 +63,17 @@ function dingtalkConfigPost(req, res, parsedUrl, isLocal, deps) {
|
|
|
52
63
|
res.end(JSON.stringify({ error: 'Invalid JSON' }));
|
|
53
64
|
return;
|
|
54
65
|
}
|
|
66
|
+
// 发送者白名单为非必填:启用时若 allowStaffIds 为空不再硬拦截(前端弹安全警告)。worker 以
|
|
67
|
+
// --dangerously-skip-permissions 运行,空白名单运行期退化为 bind-first-conversation。打一条
|
|
68
|
+
// 服务端审计(curl/headless 启用走不到前端 toast);PreToolUse permissions.deny 硬拦截仍生效。
|
|
69
|
+
const staff = Array.isArray(incoming.allowStaffIds)
|
|
70
|
+
? incoming.allowStaffIds.filter((s) => typeof s === 'string' && s.trim())
|
|
71
|
+
: [];
|
|
72
|
+
if (incoming.enabled && staff.length === 0) {
|
|
73
|
+
console.warn('[CC Viewer] IM dingtalk enabled with EMPTY allowlist — bind-first-conversation; the first conversation to message can drive this --dangerously-skip-permissions session');
|
|
74
|
+
}
|
|
55
75
|
// saveDingTalkConfig preserves the stored secret when appSecret is empty/omitted.
|
|
56
|
-
saveDingTalkConfig({
|
|
76
|
+
const saved = saveDingTalkConfig({
|
|
57
77
|
enabled: incoming.enabled,
|
|
58
78
|
appKey: incoming.appKey,
|
|
59
79
|
appSecret: incoming.appSecret,
|
|
@@ -61,10 +81,15 @@ function dingtalkConfigPost(req, res, parsedUrl, isLocal, deps) {
|
|
|
61
81
|
maxChunkChars: incoming.maxChunkChars,
|
|
62
82
|
blockOnSkipPermissions: incoming.blockOnSkipPermissions,
|
|
63
83
|
});
|
|
64
|
-
//
|
|
65
|
-
|
|
84
|
+
// 驱动进程管理器(替代旧的在进程 reloadBridge):启用→重启 worker,停用→停 worker。
|
|
85
|
+
try {
|
|
86
|
+
if (saved?.enabled ?? incoming.enabled) await deps.dingtalk.restartProcess();
|
|
87
|
+
else await deps.dingtalk.stopProcess();
|
|
88
|
+
} catch (e) {
|
|
89
|
+
console.error('[CC Viewer] IM config apply failed for dingtalk:', e?.message || e);
|
|
90
|
+
}
|
|
66
91
|
res.writeHead(200, JSON_HEADERS);
|
|
67
|
-
res.end(JSON.stringify({ ...loadDingTalkState(), connection:
|
|
92
|
+
res.end(JSON.stringify({ ...loadDingTalkState(), connection: { running: !!(saved?.enabled ?? incoming.enabled), connected: false } }));
|
|
68
93
|
});
|
|
69
94
|
}
|
|
70
95
|
|
package/server/routes/im.js
CHANGED
|
@@ -1,16 +1,25 @@
|
|
|
1
|
-
// Generic multi-IM bridge config API.
|
|
1
|
+
// Generic multi-IM bridge config + process-control API. Platform-parametric (keyed by descriptor id).
|
|
2
2
|
//
|
|
3
|
-
// GET /api/im/:platform/status
|
|
4
|
-
//
|
|
5
|
-
// POST /api/im/:platform/config
|
|
6
|
-
//
|
|
3
|
+
// GET /api/im/:platform/status — public; remote callers get only enabled+hasSecret+connection,
|
|
4
|
+
// the local (admin) caller additionally gets plaintext secrets + process info.
|
|
5
|
+
// POST /api/im/:platform/config — loopback-only; save creds, then drive the process manager
|
|
6
|
+
// (enable→stop+spawn worker, disable→stop). Enabling requires a
|
|
7
|
+
// non-empty allowlist (the worker runs with --dangerously-skip-permissions).
|
|
8
|
+
// POST /api/im/:platform/test — loopback-only; validate creds (fetch an access token).
|
|
9
|
+
// POST /api/im/:platform/process — loopback-only; {action:start|stop|restart} the detached worker.
|
|
10
|
+
// GET /api/im/:platform/logs — resolve the worker's latest .jsonl (for the records popup).
|
|
7
11
|
//
|
|
8
|
-
// :
|
|
9
|
-
//
|
|
12
|
+
// Architecture: IM adapters no longer run in the main ccv. Each enabled IM runs as an independent
|
|
13
|
+
// detached ccv worker (im-process-manager). In the MAIN process, status/process routes go through the
|
|
14
|
+
// manager (lock + loopback probe of the worker). In a WORKER process (CCV_IM_PLATFORM set), status
|
|
15
|
+
// reports its own in-process adapter (deps.im.getBridgeStatus) — that's what the manager probes.
|
|
10
16
|
import { getDescriptor, loadConfig, loadState, saveConfig } from '../lib/im-config.js';
|
|
17
|
+
import { findRecentLog } from '../lib/interceptor-core.js';
|
|
18
|
+
import { LOG_DIR } from '../../findcc.js';
|
|
19
|
+
import { join, basename } from 'node:path';
|
|
11
20
|
|
|
12
21
|
const JSON_HEADERS = { 'Content-Type': 'application/json' };
|
|
13
|
-
const IM_RE = /^\/api\/im\/([a-z0-9_-]+)\/(status|config|test)$/;
|
|
22
|
+
const IM_RE = /^\/api\/im\/([a-z0-9_-]+)\/(status|config|test|process|logs)$/;
|
|
14
23
|
|
|
15
24
|
/** Resolve a known platform id from the URL, or null (→ 404) for an unknown one. */
|
|
16
25
|
function platformOf(url) {
|
|
@@ -31,6 +40,10 @@ function notFound(res) {
|
|
|
31
40
|
res.writeHead(404, JSON_HEADERS);
|
|
32
41
|
res.end(JSON.stringify({ error: 'Unknown IM platform' }));
|
|
33
42
|
}
|
|
43
|
+
function loopbackOnly(res) {
|
|
44
|
+
res.writeHead(403, JSON_HEADERS);
|
|
45
|
+
res.end(JSON.stringify({ error: 'Loopback only' }));
|
|
46
|
+
}
|
|
34
47
|
|
|
35
48
|
function secretKeys(id) {
|
|
36
49
|
return getDescriptor(id).fields.filter((f) => f.type === 'secret').map((f) => f.key);
|
|
@@ -45,39 +58,51 @@ function readBody(req, deps, cb) {
|
|
|
45
58
|
req.on('end', () => cb(body));
|
|
46
59
|
}
|
|
47
60
|
|
|
48
|
-
function imStatus(req, res, parsedUrl, isLocal, deps) {
|
|
61
|
+
async function imStatus(req, res, parsedUrl, isLocal, deps) {
|
|
49
62
|
const id = platformOf(parsedUrl.pathname);
|
|
50
63
|
if (!id) { notFound(res); return; }
|
|
51
|
-
const conn = deps.im.getBridgeStatus(id);
|
|
52
64
|
const state = loadState(id);
|
|
65
|
+
|
|
66
|
+
let connection;
|
|
67
|
+
let processInfo = null;
|
|
68
|
+
if (deps.im.isWorker) {
|
|
69
|
+
// WORKER: report its own in-process adapter status — this is exactly what the main process's
|
|
70
|
+
// manager probes over loopback to learn whether the bot is actually connected.
|
|
71
|
+
connection = deps.im.getBridgeStatus(id);
|
|
72
|
+
} else {
|
|
73
|
+
// MAIN: the adapter runs in a detached worker, not here. Resolve process+connection via manager.
|
|
74
|
+
processInfo = await deps.im.getProcessStatus(id);
|
|
75
|
+
connection = { running: processInfo.running, connected: processInfo.connected };
|
|
76
|
+
}
|
|
77
|
+
|
|
53
78
|
res.writeHead(200, JSON_HEADERS);
|
|
54
79
|
if (!isLocal) {
|
|
55
|
-
// Loopback gate: a token-authorized LAN client
|
|
56
|
-
// bound conversation id, or raw error strings. Expose only what the header status chip needs.
|
|
80
|
+
// Loopback gate: a token-authorized LAN client sees only what the header chip needs.
|
|
57
81
|
res.end(JSON.stringify({
|
|
58
82
|
enabled: state.enabled,
|
|
59
83
|
hasSecret: state.hasSecret,
|
|
60
|
-
connection: { running:
|
|
84
|
+
connection: { running: connection.running, connected: connection.connected },
|
|
61
85
|
}));
|
|
62
86
|
return;
|
|
63
87
|
}
|
|
64
|
-
// 本机(127.0.0.1)= admin
|
|
88
|
+
// 本机(127.0.0.1)= admin / 或 manager 探活:附带明文密钥与 pid(供身份匹配),镜像旧策略。
|
|
65
89
|
const cfg = loadConfig(id);
|
|
66
90
|
const secrets = {};
|
|
67
91
|
for (const k of secretKeys(id)) secrets[k] = cfg[k];
|
|
68
|
-
res.end(JSON.stringify({
|
|
92
|
+
res.end(JSON.stringify({
|
|
93
|
+
...state,
|
|
94
|
+
...secrets,
|
|
95
|
+
connection,
|
|
96
|
+
process: processInfo,
|
|
97
|
+
pid: deps.im.isWorker ? process.pid : (processInfo?.pid ?? null),
|
|
98
|
+
}));
|
|
69
99
|
}
|
|
70
100
|
|
|
71
101
|
function imConfigPost(req, res, parsedUrl, isLocal, deps) {
|
|
72
102
|
const id = platformOf(parsedUrl.pathname);
|
|
73
103
|
if (!id) { notFound(res); return; }
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
res.writeHead(403, JSON_HEADERS);
|
|
77
|
-
res.end(JSON.stringify({ error: 'Loopback only' }));
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
readBody(req, deps, (body) => {
|
|
104
|
+
if (!isLocal) { loopbackOnly(res); return; }
|
|
105
|
+
readBody(req, deps, async (body) => {
|
|
81
106
|
let incoming;
|
|
82
107
|
try { incoming = JSON.parse(body); }
|
|
83
108
|
catch {
|
|
@@ -85,33 +110,47 @@ function imConfigPost(req, res, parsedUrl, isLocal, deps) {
|
|
|
85
110
|
res.end(JSON.stringify({ error: 'Invalid JSON' }));
|
|
86
111
|
return;
|
|
87
112
|
}
|
|
88
|
-
//
|
|
89
|
-
|
|
90
|
-
//
|
|
91
|
-
|
|
113
|
+
// 发送者白名单为非必填:启用时若白名单为空,不再硬拦截,而是允许保存(前端会弹安全警告)。
|
|
114
|
+
// 安全提示:worker 以 --dangerously-skip-permissions 运行;白名单为空时运行期退化为
|
|
115
|
+
// bind-first-conversation(im-bridge-core.js)——首个向机器人发消息的会话被绑定,该会话内任何人
|
|
116
|
+
// 都可无审批驱动本地会话。这里打一条服务端审计(curl/headless 启用走不到前端 toast),
|
|
117
|
+
// PreToolUse permissions.deny 硬拦截(perm-bridge/im-deny,独立于白名单)仍然生效。
|
|
118
|
+
if (incoming.enabled) {
|
|
119
|
+
const allowField = getDescriptor(id).allowListField;
|
|
120
|
+
// 过滤空白项后再判空:saveConfig 会 normalize(trim+丢空),若只看原始长度,[" "] 这类全空白
|
|
121
|
+
// 白名单会被当成"已配置"而漏掉审计告警,但实际保存的是空名单(与 dingtalk 路由保持一致)。
|
|
122
|
+
const raw = Array.isArray(incoming[allowField]) ? incoming[allowField] : [];
|
|
123
|
+
const list = raw.filter((s) => typeof s === 'string' && s.trim());
|
|
124
|
+
if (list.length === 0) {
|
|
125
|
+
console.warn(`[CC Viewer] IM ${id} enabled with EMPTY allowlist — bind-first-conversation; the first conversation to message can drive this --dangerously-skip-permissions session`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const saved = saveConfig(id, incoming);
|
|
129
|
+
// 驱动进程管理器(替代旧的在进程 reloadBridge):启用→重启 worker(吸收新凭证),停用→停 worker。
|
|
130
|
+
try {
|
|
131
|
+
if (saved.enabled) await deps.im.restartProcess(id);
|
|
132
|
+
else await deps.im.stopProcess(id);
|
|
133
|
+
} catch (e) {
|
|
134
|
+
// 进程操作失败不应阻塞配置保存的响应,但必须记录——否则 worker 起不来时用户看到乐观的
|
|
135
|
+
// running:true 却毫无线索(spawn 失败 / EACCES on process.out.log 等)。
|
|
136
|
+
console.error(`[CC Viewer] IM config apply failed for ${id}:`, e?.message || e);
|
|
137
|
+
}
|
|
92
138
|
res.writeHead(200, JSON_HEADERS);
|
|
93
|
-
|
|
139
|
+
// 乐观返回:worker 刚 spawn 尚未就绪,避免回包瞬间显示"已停止";chip 轮询会很快收敛到真实态。
|
|
140
|
+
res.end(JSON.stringify({ ...loadState(id), connection: { running: !!saved.enabled, connected: false } }));
|
|
94
141
|
});
|
|
95
142
|
}
|
|
96
143
|
|
|
97
144
|
function imTestPost(req, res, parsedUrl, isLocal, deps) {
|
|
98
145
|
const id = platformOf(parsedUrl.pathname);
|
|
99
146
|
if (!id) { notFound(res); return; }
|
|
100
|
-
if (!isLocal) {
|
|
101
|
-
res.writeHead(403, JSON_HEADERS);
|
|
102
|
-
res.end(JSON.stringify({ error: 'Loopback only' }));
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
147
|
+
if (!isLocal) { loopbackOnly(res); return; }
|
|
105
148
|
readBody(req, deps, async (body) => {
|
|
106
149
|
let incoming = {};
|
|
107
150
|
try { incoming = body ? JSON.parse(body) : {}; } catch { /* fall back to stored */ }
|
|
108
|
-
// Merge incoming over stored per descriptor field (empty secret → use the stored one).
|
|
109
151
|
const stored = loadConfig(id);
|
|
110
152
|
const cfg = {};
|
|
111
153
|
for (const f of getDescriptor(id).fields) cfg[f.key] = incoming[f.key] || stored[f.key];
|
|
112
|
-
// Validate the credential fields are present BEFORE hitting the network, so an empty form
|
|
113
|
-
// yields "missing appId/botToken" instead of a cryptic adapter/transport error (mirrors the
|
|
114
|
-
// DingTalk route). cred + secret are the credential field types; everything else is optional.
|
|
115
154
|
const missing = getDescriptor(id).fields
|
|
116
155
|
.filter((f) => (f.type === 'cred' || f.type === 'secret') && !cfg[f.key])
|
|
117
156
|
.map((f) => f.key);
|
|
@@ -126,8 +165,54 @@ function imTestPost(req, res, parsedUrl, isLocal, deps) {
|
|
|
126
165
|
});
|
|
127
166
|
}
|
|
128
167
|
|
|
168
|
+
function imProcessPost(req, res, parsedUrl, isLocal, deps) {
|
|
169
|
+
const id = platformOf(parsedUrl.pathname);
|
|
170
|
+
if (!id) { notFound(res); return; }
|
|
171
|
+
if (!isLocal) { loopbackOnly(res); return; }
|
|
172
|
+
// 只有主进程负责管理 worker;worker 自身不应被要求 spawn/stop(避免嵌套)。
|
|
173
|
+
if (deps.im.isWorker) {
|
|
174
|
+
res.writeHead(409, JSON_HEADERS);
|
|
175
|
+
res.end(JSON.stringify({ error: 'Process control is only available in the main ccv process' }));
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
readBody(req, deps, async (body) => {
|
|
179
|
+
let action;
|
|
180
|
+
try { action = JSON.parse(body || '{}').action; } catch { /* invalid → handled below */ }
|
|
181
|
+
try {
|
|
182
|
+
if (action === 'stop') await deps.im.stopProcess(id);
|
|
183
|
+
else if (action === 'restart') await deps.im.restartProcess(id);
|
|
184
|
+
else if (action === 'start') await deps.im.startProcess(id);
|
|
185
|
+
else {
|
|
186
|
+
res.writeHead(400, JSON_HEADERS);
|
|
187
|
+
res.end(JSON.stringify({ error: 'action must be start|stop|restart' }));
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
res.writeHead(200, JSON_HEADERS);
|
|
191
|
+
res.end(JSON.stringify({ ok: true, process: await deps.im.getProcessStatus(id) }));
|
|
192
|
+
} catch (e) {
|
|
193
|
+
res.writeHead(500, JSON_HEADERS);
|
|
194
|
+
res.end(JSON.stringify({ ok: false, error: String(e?.message || e) }));
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function imLogs(req, res, parsedUrl, isLocal, deps) {
|
|
200
|
+
const id = platformOf(parsedUrl.pathname);
|
|
201
|
+
if (!id) { notFound(res); return; }
|
|
202
|
+
const project = `IM_${id}`;
|
|
203
|
+
let latest = null;
|
|
204
|
+
try {
|
|
205
|
+
const abs = findRecentLog(join(LOG_DIR, project), project); // 已排除 *_temp.jsonl
|
|
206
|
+
if (abs) latest = `${project}/${basename(abs)}`; // 相对 LOG_DIR,直接喂给 /api/local-log?file=
|
|
207
|
+
} catch { /* 无目录/无日志 → latest=null */ }
|
|
208
|
+
res.writeHead(200, JSON_HEADERS);
|
|
209
|
+
res.end(JSON.stringify({ project, latest }));
|
|
210
|
+
}
|
|
211
|
+
|
|
129
212
|
export const imRoutes = [
|
|
130
213
|
{ predicate: imPredicate('status', 'GET'), handler: imStatus },
|
|
131
214
|
{ predicate: imPredicate('config', 'POST'), handler: imConfigPost },
|
|
132
215
|
{ predicate: imPredicate('test', 'POST'), handler: imTestPost },
|
|
216
|
+
{ predicate: imPredicate('process', 'POST'), handler: imProcessPost },
|
|
217
|
+
{ predicate: imPredicate('logs', 'GET'), handler: imLogs },
|
|
133
218
|
];
|
package/server/server.js
CHANGED
|
@@ -33,6 +33,7 @@ import { authRoutes } from './routes/auth.js';
|
|
|
33
33
|
import { dingtalkRoutes } from './routes/dingtalk.js';
|
|
34
34
|
import { imRoutes } from './routes/im.js';
|
|
35
35
|
import * as imCore from './lib/im-bridge-core.js';
|
|
36
|
+
import * as imProcMgr from './lib/im-process-manager.js';
|
|
36
37
|
import './lib/adapters/dingtalk-adapter.js'; // side-effect: registers the DingTalk adapter
|
|
37
38
|
import './lib/adapters/feishu-adapter.js'; // side-effect: registers the Feishu adapter
|
|
38
39
|
import './lib/adapters/wecom-adapter.js'; // side-effect: registers the WeCom adapter
|
|
@@ -291,8 +292,12 @@ const SSE_BACKPRESSURE_TIMEOUT_MS = 5000;
|
|
|
291
292
|
|
|
292
293
|
|
|
293
294
|
const START_PORT = parseInt(process.env.CCV_START_PORT) || 7008;
|
|
294
|
-
|
|
295
|
-
|
|
295
|
+
// 主交互式 ccv 默认收到 7049,把 7050-7099 让给独立 IM worker 进程(worker 经 env 覆盖为 7050-7099)。
|
|
296
|
+
// env 可覆盖(向后兼容逃生口)。
|
|
297
|
+
const MAX_PORT = parseInt(process.env.CCV_MAX_PORT) || 7049;
|
|
298
|
+
// IM worker 绑 127.0.0.1(仅 loopback),避免把 N 个 skip-permissions 端点暴露到局域网(见 plan §安全 1)。
|
|
299
|
+
// 主进程默认仍绑 0.0.0.0 以支持手机/局域网访问。
|
|
300
|
+
const HOST = process.env.CCV_HOST || '0.0.0.0';
|
|
296
301
|
|
|
297
302
|
// 局域网访问 token(本地 127.0.0.1 免验证)
|
|
298
303
|
const ACCESS_TOKEN = randomBytes(16).toString('hex');
|
|
@@ -480,16 +485,28 @@ const deps = {
|
|
|
480
485
|
return authConfig;
|
|
481
486
|
},
|
|
482
487
|
// Constants local to server.js.
|
|
483
|
-
// Generic IM bridge admin surface
|
|
488
|
+
// Generic IM bridge admin surface, keyed by platform id.
|
|
489
|
+
// IM adapters now run in detached worker processes (im-process-manager), NOT in this process.
|
|
490
|
+
// - isWorker: are we an IM worker (CCV_IM_PLATFORM set)? The worker reports its own in-process
|
|
491
|
+
// adapter status (getBridgeStatus) — that's what the main process's manager probes.
|
|
492
|
+
// - In the MAIN process, status/lifecycle go through the manager (lock + loopback probe / spawn / kill).
|
|
484
493
|
im: {
|
|
485
|
-
|
|
486
|
-
|
|
494
|
+
isWorker: !!process.env.CCV_IM_PLATFORM,
|
|
495
|
+
getBridgeStatus: (id) => imCore.getBridgeStatus(id), // worker-side: real in-process adapter status
|
|
496
|
+
getProcessStatus: (id) => imProcMgr.getImProcessStatus(id), // main-side: detached worker status (async)
|
|
497
|
+
startProcess: (id) => imProcMgr.spawnImProcess(id),
|
|
498
|
+
stopProcess: (id) => imProcMgr.stopImProcess(id),
|
|
499
|
+
restartProcess: async (id) => { await imProcMgr.stopImProcess(id); return imProcMgr.spawnImProcess(id); },
|
|
487
500
|
testConnection: (id, cfg) => imCore.testConnection(id, cfg),
|
|
488
501
|
},
|
|
489
|
-
// DingTalk back-compat alias
|
|
502
|
+
// DingTalk back-compat alias (legacy /api/dingtalk/* routes). Same manager-backed semantics.
|
|
490
503
|
dingtalk: {
|
|
504
|
+
isWorker: !!process.env.CCV_IM_PLATFORM,
|
|
491
505
|
getBridgeStatus: () => imCore.getBridgeStatus('dingtalk'),
|
|
492
|
-
|
|
506
|
+
getProcessStatus: () => imProcMgr.getImProcessStatus('dingtalk'),
|
|
507
|
+
startProcess: () => imProcMgr.spawnImProcess('dingtalk'),
|
|
508
|
+
stopProcess: () => imProcMgr.stopImProcess('dingtalk'),
|
|
509
|
+
restartProcess: async () => { await imProcMgr.stopImProcess('dingtalk'); return imProcMgr.spawnImProcess('dingtalk'); },
|
|
493
510
|
testConnection: (cfg) => imCore.testConnection('dingtalk', cfg),
|
|
494
511
|
},
|
|
495
512
|
ACCESS_TOKEN,
|
|
@@ -914,21 +931,32 @@ export async function startViewer() {
|
|
|
914
931
|
resolveSdkApproval: (...args) => _sdkResolveApproval?.(...args),
|
|
915
932
|
},
|
|
916
933
|
});
|
|
917
|
-
// IM
|
|
918
|
-
//
|
|
919
|
-
//
|
|
920
|
-
//
|
|
921
|
-
|
|
934
|
+
// IM adapters no longer run in the main ccv. Each enabled IM runs as an independent,
|
|
935
|
+
// detached worker process (im-process-manager). Two CLI-mode cases:
|
|
936
|
+
// - WORKER (CCV_IM_PLATFORM set): connect ONLY this one platform to the singleton PTY.
|
|
937
|
+
// - MAIN ccv: connect NOTHING in-process; reconcile spawns/adopts the enabled IM workers.
|
|
938
|
+
// (Non-CLI workspace/SDK modes do neither — IM only makes sense with the singleton PTY.)
|
|
939
|
+
if (isCliMode && process.env.CCV_IM_PLATFORM) {
|
|
940
|
+
const id = process.env.CCV_IM_PLATFORM;
|
|
922
941
|
const pmb = await import('./pty-manager.js');
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
942
|
+
try {
|
|
943
|
+
await imCore.startBridge(id, {
|
|
944
|
+
writeToPty: pmb.writeToPty,
|
|
945
|
+
writeToPtySequential: pmb.writeToPtySequential,
|
|
946
|
+
getPtyState: pmb.getPtyState,
|
|
947
|
+
getPtyKind: pmb.getPtyKind,
|
|
948
|
+
getPtySkipPermissions: pmb.getPtySkipPermissions,
|
|
949
|
+
isStreaming: () => streamingState.active,
|
|
950
|
+
getConfig: () => loadConfig(id),
|
|
951
|
+
});
|
|
952
|
+
} catch (e) {
|
|
953
|
+
console.error(`[CC Viewer] IM worker startBridge(${id}) failed:`, e?.message || e);
|
|
954
|
+
}
|
|
955
|
+
} else if (isCliMode) {
|
|
956
|
+
// 主 ccv:先 stopAll(无在进程实例时为 no-op,防御升级残留)再 reconcile,
|
|
957
|
+
// 杜绝"旧在进程连接 + 新 detached worker"同时连同一机器人导致丢/重消息。
|
|
958
|
+
try { await imCore.stopAll(); } catch { /* no in-process instances */ }
|
|
959
|
+
imProcMgr.reconcileImProcesses().catch((e) => console.error('[CC Viewer] IM reconcile failed:', e?.message || e));
|
|
932
960
|
}
|
|
933
961
|
resolve(server);
|
|
934
962
|
});
|
|
@@ -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)}._chip_1vhy5_4{line-height:0;cursor:pointer;-webkit-user-select:none;user-select:none;transition:opacity .15s}._chip_1vhy5_4:hover{opacity:.75}._logo_1vhy5_15{display:block;transition:color .15s}._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_1l0it_2{display:flex;align-items:center;justify-content:space-between;width:100%;height:100%}._logoWrap_1l0it_10{display:inline-flex;align-items:center;position:relative;margin-top:14px}._logoWrapActive_1l0it_17:after{content:"";position:absolute;top:-10px;bottom:-10px;left:0;right:-200px}._logoImage_1l0it_26{height:24px;width:24px;border-radius:3px;vertical-align:middle;opacity:.75;transition:opacity .2s;cursor:pointer}._logoImageActive_1l0it_36{opacity:1}._compactBtn_1l0it_43{font-size:12px;height:30px;display:inline-flex;align-items:center;justify-content:center}._compactBtnNoBorder_1l0it_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_1l0it_52 .anticon{display:inline-flex;align-items:center;justify-content:center;line-height:0}._headerProjectName_1l0it_79{font-size:12px;color:inherit;white-space:nowrap}._headerProjectName_1l0it_79:hover [data-alias-edit-trigger],._headerProjectName_1l0it_79:focus-within [data-alias-edit-trigger]{opacity:.55}._countdownStrong_1l0it_96{font-variant-numeric:tabular-nums}._qrcodePopover_1l0it_101{display:flex;flex-direction:column;align-items:center;padding:8px}._qrcodeSection_1l0it_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_1l0it_119{font-size:14px;font-weight:600;color:var(--text-primary);margin-bottom:12px}._qrcodeUrlInput_1l0it_126{margin-top:12px;font-size:12px;font-family:Menlo,Monaco,monospace}._qrcodeUrlCopy_1l0it_132{cursor:pointer;color:var(--text-tertiary);transition:color .2s}._qrcodeUrlCopy_1l0it_132:hover{color:var(--color-primary-light)}._authSection_1l0it_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_1l0it_157{display:flex;align-items:center;justify-content:space-between}._authTitle_1l0it_163{font-size:13px;font-weight:600;color:var(--text-primary)}._authPasswordLabel_1l0it_169{font-size:12px;color:var(--text-secondary)}._authPasswordInput_1l0it_174{font-size:12px;font-family:Menlo,Monaco,monospace}._authSaveBtn_1l0it_179{align-self:flex-end}._authEmptyWarn_1l0it_183{font-size:12px;color:var(--color-error-light);line-height:1.4}._settingsGroupBox_1l0it_191{border:1px solid var(--border-secondary);border-radius:8px;background:var(--bg-container);padding:4px 16px;margin-bottom:12px}._settingsGroupTitle_1l0it_199{font-size:14px;font-weight:600;color:var(--text-primary);padding:12px 0 4px;border-bottom:1px solid var(--border-secondary)}._settingsItem_1l0it_208{display:flex;justify-content:space-between;align-items:center;padding:12px 0}._settingsLabel_1l0it_215{font-size:14px}._settingsHelpIcon_1l0it_220{font-size:16px;color:var(--text-disabled);cursor:help;margin-left:4px}._settingsDivider_1l0it_227{border-top:1px solid var(--border-primary);margin:12px 0}._logDirInput_1l0it_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_1l0it_242{padding:8px 4px;color:var(--text-tertiary);font-size:13px}._tokenStatsContainer_1l0it_249{display:flex;gap:12px;align-items:flex-start}._tokenStatsColumn_1l0it_255{min-width:240px}._toolStatsColumn_1l0it_259{min-width:180px}._modelCardSpaced_1l0it_265{margin-bottom:10px}._rebuildCard_1l0it_274{border:1px solid var(--border-secondary);border-radius:6px;padding:8px 10px;margin-top:10px;background:var(--bg-container)}._promptExportBar_1l0it_283{margin-bottom:12px}._promptScrollArea_1l0it_287{max-height:500px;overflow:auto}._promptEmpty_1l0it_292{color:var(--text-tertiary);padding:12px}._promptTimestamp_1l0it_298{color:var(--text-muted);font-size:12px;margin:12px 0 4px;padding-bottom:6px}._textPromptCard_1l0it_306{margin:4px 0;background:var(--bg-container);border-radius:6px;border:1px solid var(--border-secondary);padding:10px 14px}._preText_1l0it_315{white-space:pre-wrap;word-break:break-word;font-size:13px;line-height:1.6;color:var(--text-primary);margin:4px 0}._systemCollapse_1l0it_325{margin:4px 0;background:var(--bg-elevated);border:1px solid var(--border-secondary);border-radius:6px}._systemLabel_1l0it_332{color:var(--text-tertiary);font-size:12px}._preSys_1l0it_337{white-space:pre-wrap;word-break:break-word;font-size:12px;line-height:1.5;color:var(--text-tertiary);margin:0}._promptTextarea_1l0it_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_1l0it_363{display:flex;justify-content:center;padding:40px 0}._projectStatsEmpty_1l0it_369{color:var(--text-tertiary);padding:40px 0;text-align:center;font-size:13px}._projectStatsContent_1l0it_376{display:flex;flex-direction:column;gap:16px}._projectStatsUpdated_1l0it_382{color:var(--text-muted);font-size:12px;text-align:right}._projectStatsSummary_1l0it_388{display:grid;grid-template-columns:1fr 1fr;gap:10px}._projectStatCard_1l0it_394{background:var(--bg-container);border:1px solid var(--border-secondary);border-radius:8px;padding:14px 12px;text-align:center}._projectStatValue_1l0it_402{font-size:22px;font-weight:700;color:var(--text-primary);font-family:monospace;font-variant-numeric:tabular-nums}._projectStatLabel_1l0it_410{font-size:12px;color:var(--text-tertiary);margin-top:4px}._projectStatsSection_1l0it_416{display:flex;flex-direction:column;gap:10px}._projectStatsSectionTitle_1l0it_422{font-size:14px;font-weight:600;color:var(--text-secondary);padding-bottom:4px;border-bottom:1px solid var(--border-secondary)}._projectStatsModelCard_1l0it_430{background:var(--bg-container);border:1px solid var(--border-secondary);border-radius:6px;padding:10px 12px}._projectStatsModelHeader_1l0it_437{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;padding-bottom:4px;border-bottom:1px solid var(--border-secondary)}._projectStatsModelName_1l0it_446{font-size:13px;font-weight:600;color:var(--text-primary)}._projectStatsModelCount_1l0it_452{font-size:12px;color:var(--text-tertiary);font-family:monospace}._cacheCopyBtn_1l0it_460{font-size:14px;color:var(--text-tertiary);cursor:pointer;transition:color .2s;margin-left:8px}._cacheCopyBtn_1l0it_460:hover{color:var(--text-primary)}._cacheTokenInfo_1l0it_472{display:flex;align-items:center;font-size:12px;font-family:monospace;color:var(--text-light);margin-bottom:8px}._cacheCodeBlock_1l0it_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_1l0it_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_1l0it_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_1l0it_513:hover{background:var(--color-primary-bg-light)}._cacheNavList_1l0it_528{width:600px;max-height:300px;overflow-y:auto}._cacheNavItem_1l0it_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_1l0it_534:hover{background:var(--color-primary-bg-lighter);color:var(--text-white)}._cacheBlockHighlight_1l0it_550{box-shadow:0 0 10px var(--color-primary-shadow);transition:box-shadow .2s ease-in}._cacheBlockHighlightFading_1l0it_555{box-shadow:0 0 10px transparent;transition:box-shadow 3s ease-out}._cacheBorderSvg_1l0it_560{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%;pointer-events:none;overflow:visible}._cacheBorderSvgFading_1l0it_569{opacity:0;transition:opacity 3s ease-out}._cacheBorderRect_1l0it_574{animation:_cacheDashRotate_1l0it_1 4s linear infinite}@keyframes _cacheDashRotate_1l0it_1{0%{stroke-dashoffset:0}to{stroke-dashoffset:-100}}._thLeft_1l0it_584{text-align:left}._cacheWriteToken_1l0it_599{color:var(--color-code-orange)}._cacheReadToken_1l0it_603{color:var(--color-success)}._cacheCtxPercent_1l0it_607{color:var(--text-tertiary);margin-left:6px}._qrcodeIcon_1l0it_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_1l0it_627:hover{color:var(--text-primary);background:var(--bg-hover, rgba(0, 0, 0, .04))}._qrcodeIcon_1l0it_627:focus-visible{outline:2px solid var(--primary-color, #1677ff);outline-offset:1px}._approvalBell_1l0it_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_1l0it_653:hover{color:var(--text-primary);background:var(--bg-hover, rgba(0, 0, 0, .04))}._approvalBell_1l0it_653:focus-visible{outline:2px solid var(--primary-color, #1677ff);outline-offset:1px}._approvalBellBadge_1l0it_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_1l0it_695{margin-right:4px;font-size:11px}._proxyProfileTag_1l0it_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_1l0it_701:hover{color:var(--text-secondary);border-color:var(--text-disabled)}._themeToggle_1l0it_720{position:relative;box-sizing:border-box;display:inline-flex;vertical-align:middle;width:56px;height:30px;border-radius:15px;border:1px solid var(--border-hover);cursor:pointer;padding:0;overflow:hidden;transition:background-color .2s ease,border-color .2s ease;flex-shrink:0;outline:none}._themeToggle_1l0it_720:focus-visible{box-shadow:0 0 0 2px var(--primary-color, #1677ff)}._themeToggle_1l0it_720[data-theme=light]{background:linear-gradient(135deg,#eaf2fa,#d9e4ef);border-color:#c8d4e0}._themeToggle_1l0it_720[data-theme=dark]{background:#1a1a1a;border-color:#2a2a2a}._themeToggleKnob_1l0it_748{position:absolute;top:50%;left:2px;width:24px;height:24px;border-radius:50%;display:flex;align-items:center;justify-content:center;transform:translateY(-50%);transition:transform .28s cubic-bezier(.4,.2,.2,1),background-color .2s ease,color .2s ease,box-shadow .2s ease;pointer-events:none}._themeToggle_1l0it_720[data-theme=light] ._themeToggleKnob_1l0it_748{background:#fff;color:#8fa2b7;box-shadow:0 1px 3px #1e32502e}._themeToggle_1l0it_720[data-theme=dark] ._themeToggleKnob_1l0it_748{transform:translate(28px,-50%);background:#2a2a2a;color:#e8e8e8;box-shadow:0 1px 3px #0006}._themeToggleIcon_1l0it_778{display:block}._headerRightRow_1l0it_783 .ant-space-item{display:inline-flex;align-items:center}._headerCountdownTag_1l0it_789{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)}
|