coding-tool-x 3.5.8 → 3.5.9

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.
@@ -5,7 +5,7 @@
5
5
  <link rel="icon" href="/favicon.ico">
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
7
  <title>coding-tool-x · AI 编程工作台</title>
8
- <script type="module" crossorigin src="/assets/index-Clf0l3wc.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-Duc7QP4e.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="/assets/markdown-DyTJGI4N.js">
10
10
  <link rel="modulepreload" crossorigin href="/assets/vue-vendor-BxIT0uQq.js">
11
11
  <link rel="modulepreload" crossorigin href="/assets/vendors-Bsp-dq2d.js">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coding-tool-x",
3
- "version": "3.5.8",
3
+ "version": "3.5.9",
4
4
  "description": "Vibe Coding 增强工作助手 - 智能会话管理、动态渠道切换、全局搜索、实时监控",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -468,6 +468,7 @@ module.exports = (config) => {
468
468
  }
469
469
 
470
470
  const { sessionId } = req.params;
471
+ const fork = Boolean(req.body?.fork);
471
472
  const fs = require('fs');
472
473
 
473
474
  // 获取会话详情
@@ -510,7 +511,7 @@ module.exports = (config) => {
510
511
  broadcastLog({
511
512
  type: 'action',
512
513
  action: 'launch_codex_session',
513
- message: `复制 Codex 会话启动命令 ${alias || sessionId.substring(0, 8)}`,
514
+ message: `${fork ? '复制 Codex 会话 Fork 命令' : '复制 Codex 会话启动命令'} ${alias || sessionId.substring(0, 8)}`,
514
515
  sessionId: sessionId,
515
516
  alias: alias || null,
516
517
  timestamp: Date.now()
@@ -519,7 +520,7 @@ module.exports = (config) => {
519
520
  const { command, copyCommand } = buildLaunchCommand({
520
521
  cwd,
521
522
  executable: 'codex',
522
- args: ['resume', sessionId]
523
+ args: fork ? ['fork', sessionId] : ['resume', sessionId]
523
524
  });
524
525
 
525
526
  res.json({
@@ -528,6 +529,7 @@ module.exports = (config) => {
528
529
  sessionFile: session.filePath,
529
530
  sessionId,
530
531
  tool: 'codex',
532
+ fork,
531
533
  command,
532
534
  copyCommand
533
535
  });
@@ -342,6 +342,7 @@ module.exports = (config) => {
342
342
  }
343
343
 
344
344
  const { projectName, sessionId } = req.params;
345
+ const fork = Boolean(req.body?.fork);
345
346
 
346
347
  const session = getSessionById(sessionId);
347
348
  if (!session) {
@@ -354,13 +355,13 @@ module.exports = (config) => {
354
355
  const { command, copyCommand } = buildLaunchCommand({
355
356
  cwd,
356
357
  executable: 'opencode',
357
- args: ['-r', sessionId]
358
+ args: fork ? ['--session', sessionId, '--fork'] : ['--session', sessionId]
358
359
  });
359
360
 
360
361
  broadcastLog({
361
362
  type: 'action',
362
363
  action: 'launch_opencode_session',
363
- message: `复制 OpenCode 会话启动命令 ${sessionId.substring(0, 8)}`,
364
+ message: `${fork ? '复制 OpenCode 会话 Fork 命令' : '复制 OpenCode 会话启动命令'} ${sessionId.substring(0, 8)}`,
364
365
  sessionId,
365
366
  tool: 'opencode',
366
367
  timestamp: Date.now()
@@ -371,6 +372,7 @@ module.exports = (config) => {
371
372
  cwd,
372
373
  sessionId,
373
374
  tool: 'opencode',
375
+ fork,
374
376
  command,
375
377
  copyCommand
376
378
  });
@@ -472,6 +472,7 @@ module.exports = (config) => {
472
472
  router.post('/:projectName/:sessionId/launch', async (req, res) => {
473
473
  try {
474
474
  const { projectName, sessionId } = req.params;
475
+ const fork = Boolean(req.body?.fork);
475
476
  const path = require('path');
476
477
  const fs = require('fs');
477
478
 
@@ -564,7 +565,7 @@ module.exports = (config) => {
564
565
  broadcastLog({
565
566
  type: 'action',
566
567
  action: 'launch_session',
567
- message: `复制会话启动命令 ${alias || sessionId.substring(0, 8)} (claude)`,
568
+ message: `${fork ? '复制会话 Fork 命令' : '复制会话启动命令'} ${alias || sessionId.substring(0, 8)} (claude)`,
568
569
  sessionId,
569
570
  alias: alias || null,
570
571
  tool: 'claude',
@@ -574,7 +575,7 @@ module.exports = (config) => {
574
575
  const { command, copyCommand } = buildLaunchCommand({
575
576
  cwd,
576
577
  executable: 'claude',
577
- args: ['-r', sessionId]
578
+ args: fork ? ['-r', sessionId, '--fork-session'] : ['-r', sessionId]
578
579
  });
579
580
 
580
581
  res.json({
@@ -583,6 +584,7 @@ module.exports = (config) => {
583
584
  sessionFile,
584
585
  sessionId,
585
586
  tool: 'claude',
587
+ fork,
586
588
  command,
587
589
  copyCommand
588
590
  });
@@ -513,7 +513,13 @@ function getProjects() {
513
513
 
514
514
  // 根据项目ID获取会话列表
515
515
  function getSessionsByProjectId(projectId) {
516
- const sessions = getSessionRowsByProjectId(projectId).map(session => normalizeSession(session, projectId));
516
+ const { getForkRelations } = require('./sessions');
517
+ const forkRelations = getForkRelations();
518
+ const sessions = getSessionRowsByProjectId(projectId).map((session) => {
519
+ const normalized = normalizeSession(session, projectId);
520
+ normalized.forkedFrom = forkRelations[normalized.sessionId] || null;
521
+ return normalized;
522
+ });
517
523
  const order = getSessionOrder(projectId);
518
524
 
519
525
  const fallbackSorted = sessions.sort(
@@ -558,7 +564,11 @@ function getSessionById(sessionId) {
558
564
  return null;
559
565
  }
560
566
 
561
- return normalizeSession(location.sessionData, location.projectId);
567
+ const { getForkRelations } = require('./sessions');
568
+ const forkRelations = getForkRelations();
569
+ const normalized = normalizeSession(location.sessionData, location.projectId);
570
+ normalized.forkedFrom = forkRelations[normalized.sessionId] || null;
571
+ return normalized;
562
572
  }
563
573
 
564
574
  function buildSessionMessages(sessionId) {
@@ -1,28 +1,11 @@
1
- const { isWindowsLikePlatform } = require('../../utils/home-dir');
2
-
3
1
  function escapeForDoubleQuotes(value) {
4
2
  return String(value).replace(/"/g, '\\"');
5
3
  }
6
4
 
7
- function escapeForPowerShellSingleQuotes(value) {
8
- return String(value).replace(/'/g, "''");
9
- }
10
-
11
5
  function buildDisplayCommand(executable, args = []) {
12
6
  return [String(executable || ''), ...args.map(arg => String(arg))].filter(Boolean).join(' ').trim();
13
7
  }
14
8
 
15
- function buildWindowsCopyCommand(cwd, executable, args = []) {
16
- const quotedCwd = `'${escapeForPowerShellSingleQuotes(cwd)}'`;
17
- const quotedExecutable = `'${escapeForPowerShellSingleQuotes(executable)}'`;
18
- const quotedArgs = args.map(arg => `'${escapeForPowerShellSingleQuotes(arg)}'`).join(' ');
19
- const invokeCommand = quotedArgs
20
- ? `& ${quotedExecutable} ${quotedArgs}`
21
- : `& ${quotedExecutable}`;
22
-
23
- return `powershell -NoProfile -ExecutionPolicy Bypass -Command "& { Set-Location -LiteralPath ${quotedCwd}; ${invokeCommand} }"`;
24
- }
25
-
26
9
  function buildPosixCopyCommand(cwd, command) {
27
10
  const quotedCwd = `"${escapeForDoubleQuotes(cwd)}"`;
28
11
  return `cd ${quotedCwd} && ${command}`;
@@ -41,10 +24,6 @@ function buildCopyCommand({
41
24
  return resolvedCommand;
42
25
  }
43
26
 
44
- if (isWindowsLikePlatform(runtimePlatform, runtimeEnv)) {
45
- return buildWindowsCopyCommand(cwd, executable, args);
46
- }
47
-
48
27
  return buildPosixCopyCommand(cwd, resolvedCommand);
49
28
  }
50
29
 
@@ -74,8 +53,6 @@ module.exports = {
74
53
  _test: {
75
54
  buildDisplayCommand,
76
55
  buildCopyCommand,
77
- buildWindowsCopyCommand,
78
- buildPosixCopyCommand,
79
- escapeForPowerShellSingleQuotes
56
+ buildPosixCopyCommand
80
57
  }
81
58
  };
@@ -20,6 +20,29 @@ const PROJECT_PATH_CACHE_TTL_MS = 5 * 60 * 1000;
20
20
  const MAX_PROJECT_PATH_CACHE_ENTRIES = 500;
21
21
  let projectPathResolutionCache = new Map();
22
22
 
23
+ function getProjectsStatsCacheKey(config) {
24
+ return `${CacheKeys.PROJECTS}${config?.projectsDir || '__default__'}`;
25
+ }
26
+
27
+ function getProjectSessionsCacheKey(projectName) {
28
+ return `${CacheKeys.SESSIONS}${projectName}`;
29
+ }
30
+
31
+ function invalidateProjectStatsCache(config) {
32
+ invalidateProjectsCache(config);
33
+ globalCache.delete(getProjectsStatsCacheKey(config));
34
+ }
35
+
36
+ function invalidateProjectSessionCache(projectName) {
37
+ if (!projectName) return;
38
+ globalCache.delete(getProjectSessionsCacheKey(projectName));
39
+ }
40
+
41
+ function invalidateProjectCaches(config, projectName) {
42
+ invalidateProjectStatsCache(config);
43
+ invalidateProjectSessionCache(projectName);
44
+ }
45
+
23
46
  // Base directory for cc-tool data
24
47
  function getCcToolDir() {
25
48
  return PATHS.base;
@@ -313,6 +336,41 @@ function validateProjectPath(candidatePath) {
313
336
  return null;
314
337
  }
315
338
 
339
+ function scanSessionsDir(sessionsDir) {
340
+ if (!sessionsDir || !fs.existsSync(sessionsDir)) {
341
+ return [];
342
+ }
343
+
344
+ return fs.readdirSync(sessionsDir)
345
+ .filter(file => file.endsWith('.jsonl') && !file.startsWith('agent-'))
346
+ .map((file) => {
347
+ const filePath = path.join(sessionsDir, file);
348
+ const stats = fs.statSync(filePath);
349
+ return {
350
+ sessionId: file.replace('.jsonl', ''),
351
+ filePath,
352
+ size: stats.size,
353
+ mtime: stats.mtime
354
+ };
355
+ });
356
+ }
357
+
358
+ function getProjectSessionsDir(config, projectName) {
359
+ return path.join(config.projectsDir, projectName);
360
+ }
361
+
362
+ function getProjectSessionFiles(config, projectName) {
363
+ return scanSessionsDir(getProjectSessionsDir(config, projectName)).sort((a, b) => b.mtime - a.mtime);
364
+ }
365
+
366
+ function resolveSessionFile(config, projectName, sessionId) {
367
+ const sessionFile = path.join(getProjectSessionsDir(config, projectName), `${sessionId}.jsonl`);
368
+ if (fs.existsSync(sessionFile)) {
369
+ return sessionFile;
370
+ }
371
+ return '';
372
+ }
373
+
316
374
  function tryResolvePathFromSessions(encodedName) {
317
375
  try {
318
376
  const projectDir = path.join(CLAUDE_PROJECTS_DIR, encodedName);
@@ -365,7 +423,7 @@ function extractCwdFromSessionHeader(sessionFile) {
365
423
  async function getProjectsWithStats(config, options = {}) {
366
424
  if (!options.force) {
367
425
  // Check enhanced cache first
368
- const cacheKey = `${CacheKeys.PROJECTS}${config.projectsDir}`;
426
+ const cacheKey = getProjectsStatsCacheKey(config);
369
427
  const enhancedCached = globalCache.get(cacheKey);
370
428
  if (enhancedCached) {
371
429
  return enhancedCached;
@@ -386,7 +444,7 @@ async function getProjectsWithStats(config, options = {}) {
386
444
  return [];
387
445
  }
388
446
  setCachedProjects(config, data);
389
- globalCache.set(`${CacheKeys.PROJECTS}${config.projectsDir}`, data, 300000);
447
+ globalCache.set(getProjectsStatsCacheKey(config), data, 300000);
390
448
  return data;
391
449
  } catch (err) {
392
450
  console.error(`[getProjectsWithStats] Failed to build projects for ${config.projectsDir}:`, err);
@@ -418,14 +476,10 @@ async function buildProjectsWithStats(config) {
418
476
  let lastUsed = null;
419
477
 
420
478
  try {
421
- const files = await fs.promises.readdir(projectPath);
422
- const jsonlFiles = files.filter(f => f.endsWith('.jsonl') && !f.startsWith('agent-'));
423
-
424
479
  const sessionChecks = await Promise.all(
425
- jsonlFiles.map(async (fileName) => {
426
- const filePath = path.join(projectPath, fileName);
427
- const stats = await fs.promises.stat(filePath);
428
- const hasMessages = await hasActualMessages(filePath, stats);
480
+ getProjectSessionFiles(config, projectName).map(async (session) => {
481
+ const stats = await fs.promises.stat(session.filePath);
482
+ const hasMessages = await hasActualMessages(session.filePath, stats);
429
483
  return hasMessages ? stats.mtime.getTime() : null;
430
484
  })
431
485
  );
@@ -469,10 +523,8 @@ function getProjectAndSessionCounts(config) {
469
523
  return;
470
524
  }
471
525
  projectCount += 1;
472
- const projectPath = path.join(projectsDir, entry.name);
473
526
  try {
474
- const files = fs.readdirSync(projectPath);
475
- sessionCount += files.filter(file => file.endsWith('.jsonl') && !file.startsWith('agent-')).length;
527
+ sessionCount += getProjectSessionFiles(config, entry.name).length;
476
528
  } catch (err) {
477
529
  // 忽略单个项目的读取错误
478
530
  }
@@ -575,14 +627,14 @@ function scanSessionFileForMessagesAsync(filePath) {
575
627
  // Get sessions for a project - async version
576
628
  async function getSessionsForProject(config, projectName) {
577
629
  // Check cache first
578
- const cacheKey = `${CacheKeys.SESSIONS}${projectName}`;
630
+ const cacheKey = getProjectSessionsCacheKey(projectName);
579
631
  const cached = globalCache.get(cacheKey);
580
632
  if (cached) {
581
633
  return cached;
582
634
  }
583
635
 
584
636
  const projectConfig = { ...config, currentProject: projectName };
585
- const sessions = getAllSessions(projectConfig);
637
+ const sessions = getProjectSessionFiles(projectConfig, projectName);
586
638
  const forkRelations = getForkRelations();
587
639
  const savedOrder = getSessionOrder(projectName);
588
640
 
@@ -627,8 +679,10 @@ async function getSessionsForProject(config, projectName) {
627
679
  }
628
680
 
629
681
  // Add remaining sessions (new ones not in saved order)
630
- ordered.push(...sessionMap.values());
631
- orderedSessions = ordered;
682
+ const newSessions = [...sessionMap.values()].sort((a, b) => {
683
+ return new Date(b.mtime).getTime() - new Date(a.mtime).getTime();
684
+ });
685
+ orderedSessions = [...newSessions, ...ordered];
632
686
  }
633
687
 
634
688
  const result = {
@@ -643,24 +697,22 @@ async function getSessionsForProject(config, projectName) {
643
697
 
644
698
  // Delete a session
645
699
  function deleteSession(config, projectName, sessionId) {
646
- const projectDir = path.join(config.projectsDir, projectName);
647
- const sessionFile = path.join(projectDir, sessionId + '.jsonl');
700
+ const sessionFile = resolveSessionFile(config, projectName, sessionId);
648
701
 
649
- if (!fs.existsSync(sessionFile)) {
702
+ if (!sessionFile || !fs.existsSync(sessionFile)) {
650
703
  throw new Error('Session not found');
651
704
  }
652
705
 
653
706
  fs.unlinkSync(sessionFile);
654
- invalidateProjectsCache(config);
707
+ invalidateProjectCaches(config, projectName);
655
708
  return { success: true };
656
709
  }
657
710
 
658
711
  // Fork a session
659
712
  function forkSession(config, projectName, sessionId) {
660
- const projectDir = path.join(config.projectsDir, projectName);
661
- const sessionFile = path.join(projectDir, sessionId + '.jsonl');
713
+ const sessionFile = resolveSessionFile(config, projectName, sessionId);
662
714
 
663
- if (!fs.existsSync(sessionFile)) {
715
+ if (!sessionFile || !fs.existsSync(sessionFile)) {
664
716
  throw new Error('Session not found');
665
717
  }
666
718
 
@@ -669,7 +721,7 @@ function forkSession(config, projectName, sessionId) {
669
721
 
670
722
  // Generate new session ID (UUID v4)
671
723
  const newSessionId = crypto.randomUUID();
672
- const newSessionFile = path.join(projectDir, newSessionId + '.jsonl');
724
+ const newSessionFile = path.join(path.dirname(sessionFile), newSessionId + '.jsonl');
673
725
 
674
726
  // Write to new file
675
727
  fs.writeFileSync(newSessionFile, content, 'utf8');
@@ -678,7 +730,13 @@ function forkSession(config, projectName, sessionId) {
678
730
  const forkRelations = getForkRelations();
679
731
  forkRelations[newSessionId] = sessionId;
680
732
  saveForkRelations(forkRelations);
681
- invalidateProjectsCache(config);
733
+
734
+ const savedOrder = getSessionOrder(projectName);
735
+ if (savedOrder.length > 0) {
736
+ saveSessionOrder(projectName, [newSessionId, ...savedOrder.filter(id => id !== newSessionId)]);
737
+ }
738
+
739
+ invalidateProjectCaches(config, projectName);
682
740
 
683
741
  return { newSessionId, forkedFrom: sessionId };
684
742
  }
@@ -720,6 +778,7 @@ function saveSessionOrder(projectName, order) {
720
778
  // Update order for this project
721
779
  allOrders[projectName] = order;
722
780
  fs.writeFileSync(orderFile, JSON.stringify(allOrders, null, 2), 'utf8');
781
+ invalidateProjectSessionCache(projectName);
723
782
  }
724
783
 
725
784
  // Delete a project (remove the entire project directory)
@@ -739,7 +798,7 @@ function deleteProject(config, projectName) {
739
798
  saveProjectOrder(config, newOrder);
740
799
  }
741
800
 
742
- invalidateProjectsCache(config);
801
+ invalidateProjectCaches(config, projectName);
743
802
  clearProjectPathResolutionCache(projectName);
744
803
  return {
745
804
  success: true,
@@ -749,20 +808,12 @@ function deleteProject(config, projectName) {
749
808
 
750
809
  // Search sessions for keyword
751
810
  function searchSessions(config, projectName, keyword, contextLength = 15) {
752
- const projectDir = path.join(config.projectsDir, projectName);
753
-
754
- if (!fs.existsSync(projectDir)) {
755
- return [];
756
- }
757
-
758
811
  const results = [];
759
- const files = fs.readdirSync(projectDir);
760
- const jsonlFiles = files.filter(f => f.endsWith('.jsonl') && !f.startsWith('agent-'));
761
812
  const aliases = loadAliases();
762
813
 
763
- for (const file of jsonlFiles) {
764
- const sessionId = file.replace('.jsonl', '');
765
- const filePath = path.join(projectDir, file);
814
+ for (const session of getProjectSessionFiles(config, projectName)) {
815
+ const sessionId = session.sessionId;
816
+ const filePath = session.filePath;
766
817
 
767
818
  // Skip sessions with no actual messages
768
819
  if (!hasActualMessages(filePath)) {
@@ -836,7 +887,7 @@ async function getRecentSessions(config, limit = 5) {
836
887
  // Collect all sessions from all projects
837
888
  projects.forEach(projectName => {
838
889
  const projectConfig = { ...config, currentProject: projectName };
839
- const sessions = getAllSessions(projectConfig);
890
+ const sessions = getProjectSessionFiles(projectConfig, projectName);
840
891
  const { projectName: displayName, fullPath } = parseRealProjectPath(projectName);
841
892
 
842
893
  sessions.forEach(session => {
@@ -1 +0,0 @@
1
- .session-list-container[data-v-e76934ff]{width:100%}.sessions-list[data-v-e76934ff]{position:relative;overflow:hidden}.session-header[data-v-e76934ff]{align-items:flex-start;gap:14px}.session-header-main[data-v-e76934ff]{display:flex;align-items:flex-start;gap:12px;min-width:0;flex:1}.back-button[data-v-e76934ff]{flex-shrink:0}.title-section[data-v-e76934ff]{flex:1;min-width:0}.title-with-count[data-v-e76934ff]{display:flex;align-items:center;gap:6px;margin-bottom:0;flex-wrap:wrap}.title-section h2[data-v-e76934ff]{margin:0}.session-page-title[data-v-e76934ff]{font-size:clamp(22px,1.75vw,27px);line-height:1.08;letter-spacing:-.03em}.session-count[data-v-e76934ff]{font-size:13px;color:var(--text-secondary)}.total-size-tag[data-v-e76934ff]{margin-left:0}.project-path[data-v-e76934ff]{margin-top:0}.header-actions[data-v-e76934ff]{display:flex;align-items:center;gap:12px;flex-wrap:wrap}.session-toolbar[data-v-e76934ff]{min-width:min(420px,100%)}.search-input[data-v-e76934ff]{width:300px;min-width:min(300px,100%)}.search-input[data-v-e76934ff] .n-input{border-radius:14px;background:var(--surface-panel-strong);border:1px solid var(--border-primary);transition:all .2s ease}.search-input[data-v-e76934ff] .n-input:hover{border-color:var(--border-secondary);box-shadow:var(--shadow-sm)}.search-input[data-v-e76934ff] .n-input:focus-within{border-color:var(--primary-color);box-shadow:var(--focus-ring)}.loading-container[data-v-e76934ff]{display:flex;justify-content:center;align-items:center;min-height:400px}.session-item[data-v-e76934ff]{display:flex;align-items:center;gap:12px;padding:18px;background:var(--surface-panel);border:1px solid var(--border-primary);border-radius:18px;margin-bottom:10px;transition:all .22s ease;cursor:pointer;box-shadow:var(--shadow-sm)}.session-item[data-v-e76934ff]:hover{border-color:#18a05842;box-shadow:var(--shadow-md);transform:translateY(-1px)}.session-item-selection-mode[data-v-e76934ff]{cursor:default}.session-item-selected[data-v-e76934ff]{border-color:#18a0585c;box-shadow:0 0 0 3px #18a0581f;background:#18a0580a}.drag-handle[data-v-e76934ff]{cursor:move;width:24px;height:24px;padding:4px;opacity:.4;transition:all .2s;flex-shrink:0;display:flex;align-items:center;justify-content:center}.session-item:hover .drag-handle[data-v-e76934ff]{opacity:1;background-color:#18a0581a;border-radius:10px}.selection-checkbox[data-v-e76934ff]{width:24px;min-width:24px;display:flex;align-items:center;justify-content:center}.session-left[data-v-e76934ff]{flex:1;display:flex;align-items:center;gap:16px;min-width:0}.session-icon[data-v-e76934ff]{flex-shrink:0}.session-info[data-v-e76934ff]{flex:1;min-width:0}.session-header[data-v-e76934ff]{display:flex;align-items:center;margin-bottom:6px}.session-title-row[data-v-e76934ff]{display:flex;align-items:center;gap:8px}.session-title[data-v-e76934ff]{font-size:15px;font-weight:650;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex-shrink:1;min-width:0}.session-meta[data-v-e76934ff]{display:flex;align-items:center;gap:8px;margin-bottom:6px;font-size:13px}.session-message[data-v-e76934ff]{display:block;max-width:680px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px}.session-message-empty[data-v-e76934ff]{font-style:italic;opacity:.5}.session-right[data-v-e76934ff]{display:flex;flex-direction:column;justify-content:flex-start;align-items:flex-end;min-width:248px;flex-shrink:0;gap:12px}.session-tags-area[data-v-e76934ff]{min-height:24px;display:flex;align-items:flex-start;justify-content:flex-end}.session-actions[data-v-e76934ff]{display:flex;align-items:center;margin-top:auto}.session-actions[data-v-e76934ff] .n-button{border-radius:12px}.ghost[data-v-e76934ff]{opacity:.4}.chosen[data-v-e76934ff]{box-shadow:0 4px 16px #00000026}.search-result-item[data-v-e76934ff]{margin-bottom:16px;padding:14px;border:1px solid var(--border-primary);border-radius:14px;background:var(--surface-panel);box-shadow:var(--shadow-sm)}.search-result-header[data-v-e76934ff]{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px}.search-result-title[data-v-e76934ff]{display:flex;align-items:center;gap:8px}.search-match[data-v-e76934ff]{display:flex;align-items:flex-start;gap:8px;margin-top:8px;padding:10px 12px;background:var(--surface-panel-muted);border-radius:12px}.search-match-text[data-v-e76934ff]{flex:1;line-height:1.6}@media (max-width: 1024px){.session-header[data-v-e76934ff]{align-items:stretch}.session-toolbar[data-v-e76934ff]{width:100%;min-width:0}.search-input[data-v-e76934ff]{width:100%}}@media (max-width: 900px){.session-page-title[data-v-e76934ff]{font-size:20px}.session-item[data-v-e76934ff]{flex-direction:column;align-items:stretch}.session-right[data-v-e76934ff]{min-width:0;align-items:flex-start}.session-tags-area[data-v-e76934ff],.session-actions[data-v-e76934ff]{width:100%;justify-content:flex-start}}