coding-tool-x 3.3.9 → 3.4.1

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.
Files changed (33) hide show
  1. package/dist/web/assets/{Analytics-D6LzK9hk.js → Analytics-CbGxotgz.js} +4 -4
  2. package/dist/web/assets/Analytics-RNn1BUbG.css +1 -0
  3. package/dist/web/assets/{ConfigTemplates-BUDYuxRi.js → ConfigTemplates-oP6nrFEb.js} +1 -1
  4. package/dist/web/assets/{Home-D7KX7iF8.js → Home-DMntmEvh.js} +1 -1
  5. package/dist/web/assets/{PluginManager-DTgQ--vB.js → PluginManager-BUC_c7nH.js} +1 -1
  6. package/dist/web/assets/{ProjectList-DMCiGmCT.js → ProjectList-CW8J49n7.js} +1 -1
  7. package/dist/web/assets/{SessionList-CRBsdVRe.js → SessionList-7lYnF92v.js} +1 -1
  8. package/dist/web/assets/{SkillManager-DMwx2Q4k.js → SkillManager-Cs08216i.js} +1 -1
  9. package/dist/web/assets/{WorkspaceManager-DapB4ljL.js → WorkspaceManager-CY-oGtyB.js} +1 -1
  10. package/dist/web/assets/{index-D_5dRFOL.css → index-5qy5NMIP.css} +1 -1
  11. package/dist/web/assets/index-ClCqKpvX.js +2 -0
  12. package/dist/web/index.html +2 -2
  13. package/package.json +6 -2
  14. package/src/server/api/statistics.js +4 -4
  15. package/src/server/api/workspaces.js +1 -3
  16. package/src/server/codex-proxy-server.js +4 -92
  17. package/src/server/gemini-proxy-server.js +5 -28
  18. package/src/server/opencode-proxy-server.js +3 -93
  19. package/src/server/proxy-server.js +2 -57
  20. package/src/server/services/base/base-channel-service.js +247 -0
  21. package/src/server/services/base/proxy-utils.js +152 -0
  22. package/src/server/services/channel-health.js +30 -19
  23. package/src/server/services/channels.js +125 -293
  24. package/src/server/services/codex-channels.js +149 -517
  25. package/src/server/services/codex-env-manager.js +100 -67
  26. package/src/server/services/gemini-channels.js +2 -7
  27. package/src/server/services/oauth-credentials-service.js +12 -2
  28. package/src/server/services/opencode-channels.js +7 -9
  29. package/src/server/services/repo-scanner-base.js +1 -0
  30. package/src/server/services/statistics-service.js +5 -1
  31. package/src/server/services/workspace-service.js +100 -155
  32. package/dist/web/assets/Analytics-DuYvId7u.css +0 -1
  33. package/dist/web/assets/index-CL-qpoJ_.js +0 -2
@@ -8,6 +8,24 @@ const configTemplatesService = require('./config-templates-service');
8
8
  // 工作区配置文件路径
9
9
  const WORKSPACES_CONFIG = PATHS.workspaces;
10
10
 
11
+ function createSymlink(target, linkPath) {
12
+ try {
13
+ fs.symlinkSync(target, linkPath, 'dir');
14
+ } catch (err) {
15
+ if (err.code === 'EPERM' && process.platform === 'win32') {
16
+ throw new Error(
17
+ `创建软链接失败:Windows 需要管理员权限或开启开发者模式。\n` +
18
+ `解决方案:\n` +
19
+ `1. 以管理员身份运行终端后重试\n` +
20
+ `2. 或开启开发者模式:设置 → 系统 → 开发者选项 → 开发者模式\n` +
21
+ `3. 对于 Git 项目,建议保持默认的 worktree 模式(无需软链接)\n` +
22
+ `原始错误: ${err.message}`
23
+ );
24
+ }
25
+ throw err;
26
+ }
27
+ }
28
+
11
29
  function runGitCommand(args, options = {}) {
12
30
  const execOptions = {
13
31
  encoding: 'utf8',
@@ -287,7 +305,7 @@ function createWorkspace(options) {
287
305
  let targetPath = sourcePath;
288
306
  let worktrees = [];
289
307
 
290
- // Git 仓库且需要创建 worktree
308
+ // Git 仓库且需要创建 worktree:直接在工作区目录内创建,无需额外 symlink
291
309
  if (isGit && useWorktree) {
292
310
  // 获取当前分支作为默认分支
293
311
  let targetBranch = branch;
@@ -299,72 +317,46 @@ function createWorkspace(options) {
299
317
  }
300
318
  }
301
319
 
302
- // worktree 路径:源目录同级,名称为 "项目名-workspace-分支名"
303
- const worktreePath = path.join(
304
- path.dirname(sourcePath),
305
- `${path.basename(sourcePath)}-ws-${targetBranch.replace(/\//g, '-')}`
306
- );
307
-
308
- // 检查 worktree 是否已存在
309
- if (fs.existsSync(worktreePath)) {
310
- // 已存在则直接使用
311
- targetPath = worktreePath;
312
- worktrees.push({
313
- branch: targetBranch,
314
- path: worktreePath
320
+ // worktree 直接创建到工作区目录内的项目路径
321
+ const worktreePath = symlinkPath;
322
+
323
+ try {
324
+ // 尝试检出已有分支
325
+ runGitCommand(['worktree', 'add', worktreePath, targetBranch], {
326
+ cwd: sourcePath
315
327
  });
316
- } else {
328
+ } catch (error) {
329
+ // 如果分支不存在,尝试创建新分支
317
330
  try {
318
- // 尝试检出已有分支
319
- runGitCommand(['worktree', 'add', worktreePath, targetBranch], {
331
+ const worktreeArgs = ['worktree', 'add', worktreePath, '-b', targetBranch];
332
+ if (baseBranch && baseBranch.trim()) {
333
+ worktreeArgs.push(baseBranch.trim());
334
+ }
335
+ runGitCommand(worktreeArgs, {
320
336
  cwd: sourcePath
321
337
  });
322
-
323
- targetPath = worktreePath;
324
- worktrees.push({
325
- branch: targetBranch,
326
- path: worktreePath
327
- });
328
- } catch (error) {
329
- // 如果分支不存在,尝试创建新分支
330
- try {
331
- const worktreeArgs = ['worktree', 'add', worktreePath, '-b', targetBranch];
332
- if (baseBranch && baseBranch.trim()) {
333
- worktreeArgs.push(baseBranch.trim());
334
- }
335
- runGitCommand(worktreeArgs, {
336
- cwd: sourcePath
337
- });
338
- targetPath = worktreePath;
339
- worktrees.push({
340
- branch: targetBranch,
341
- path: worktreePath
342
- });
343
- } catch (err) {
344
- // Check if it's a "branch already checked out" error
345
- if (err.message.includes('already checked out')) {
346
- throw new Error(
347
- `无法创建 worktree:分支 '${targetBranch}' 已在其他工作树中检出。\n` +
348
- `错误详情: ${err.message}\n\n` +
349
- `提示:请为此项目指定不同的分支名,或禁用 worktree 模式。`
350
- );
351
- }
352
-
353
- // For other errors, provide clear message but allow fallback
354
- console.warn(`创建 worktree 失败,使用软链接: ${err.message}`);
355
- targetPath = sourcePath;
356
- worktrees = getGitWorktrees(sourcePath);
338
+ } catch (err) {
339
+ if (err.message.includes('already checked out')) {
340
+ throw new Error(
341
+ `无法创建 worktree:分支 '${targetBranch}' 已在其他工作树中检出。\n` +
342
+ `错误详情: ${err.message}\n\n` +
343
+ `提示:请为此项目指定不同的分支名,或禁用 worktree 模式。`
344
+ );
357
345
  }
346
+ throw new Error(`创建 worktree 失败: ${err.message}`);
358
347
  }
359
348
  }
349
+
350
+ targetPath = worktreePath;
351
+ worktrees.push({ branch: targetBranch, path: worktreePath });
360
352
  } else if (isGit) {
361
- // Git 仓库但不创建 worktree,获取已有的 worktrees 信息
353
+ // Git 仓库但不创建 worktree:symlink 指向源路径,记录已有 worktrees
362
354
  worktrees = getGitWorktrees(sourcePath);
355
+ createSymlink(targetPath, symlinkPath);
356
+ } else {
357
+ // 非 Git 仓库:symlink 指向源路径
358
+ createSymlink(targetPath, symlinkPath);
363
359
  }
364
- // 非 Git 仓库:targetPath 保持为 sourcePath,直接软链接
365
-
366
- // 创建软链接
367
- fs.symlinkSync(targetPath, symlinkPath, 'dir');
368
360
 
369
361
  workspaceProjects.push({
370
362
  name: symlinkName,
@@ -438,36 +430,17 @@ function deleteWorkspace(id, removeFiles = false) {
438
430
 
439
431
  const workspace = data.workspaces[index];
440
432
 
441
- // 清理 worktrees (无论是否删除工作区目录,都应该清理 worktree)
433
+ // 注销 worktrees(worktree 目录在工作区内,git 引用需先注销)
442
434
  for (const proj of workspace.projects) {
443
- if (proj.isGitRepo && proj.sourcePath && fs.existsSync(proj.sourcePath)) {
444
- try {
445
- // 重新扫描实际的 worktrees,确保获取最新状态
446
- const actualWorktrees = getGitWorktrees(proj.sourcePath);
447
- for (const wt of actualWorktrees) {
448
- // 只删除属于这个工作区的 worktree (通过 -ws- 标识符识别)
449
- if (wt.path && wt.path.includes('-ws-')) {
450
- try {
451
- console.log(`清理 worktree: ${wt.path}`);
452
- runGitCommand(['worktree', 'remove', wt.path, '--force'], {
453
- cwd: proj.sourcePath
454
- });
455
- } catch (error) {
456
- console.error(`删除 worktree 失败: ${wt.path}`, error.message);
457
- // 如果 git worktree remove 失败,尝试手动删除目录
458
- if (fs.existsSync(wt.path)) {
459
- try {
460
- fs.rmSync(wt.path, { recursive: true, force: true });
461
- console.log(`手动删除 worktree 目录: ${wt.path}`);
462
- } catch (rmError) {
463
- console.error(`手动删除 worktree 目录失败: ${wt.path}`, rmError.message);
464
- }
465
- }
466
- }
467
- }
435
+ if (proj.useWorktree && proj.sourcePath && proj.targetPath) {
436
+ if (fs.existsSync(proj.sourcePath)) {
437
+ try {
438
+ runGitCommand(['worktree', 'remove', proj.targetPath, '--force'], {
439
+ cwd: proj.sourcePath
440
+ });
441
+ } catch (error) {
442
+ console.error(`注销 worktree 失败: ${proj.targetPath}`, error.message);
468
443
  }
469
- } catch (error) {
470
- console.error(`扫描 worktree 失败: ${proj.sourcePath}`, error.message);
471
444
  }
472
445
  }
473
446
  }
@@ -533,9 +506,8 @@ function addProjectToWorkspace(workspaceId, projectConfig) {
533
506
  let targetPath = sourcePath;
534
507
  let worktrees = [];
535
508
 
536
- // Git 仓库且需要创建 worktree
509
+ // Git 仓库且需要创建 worktree:直接在工作区目录内创建,无需额外 symlink
537
510
  if (isGit && useWorktree) {
538
- // 获取当前分支作为默认分支
539
511
  let targetBranch = branch;
540
512
  if (!targetBranch) {
541
513
  try {
@@ -545,73 +517,44 @@ function addProjectToWorkspace(workspaceId, projectConfig) {
545
517
  }
546
518
  }
547
519
 
548
- const worktreePath = path.join(
549
- path.dirname(sourcePath),
550
- `${path.basename(sourcePath)}-ws-${targetBranch.replace(/\//g, '-')}`
551
- );
520
+ const worktreePath = symlinkPath;
552
521
 
553
- // 检查 worktree 是否已存在
554
- if (fs.existsSync(worktreePath)) {
555
- targetPath = worktreePath;
556
- worktrees.push({ branch: targetBranch, path: worktreePath });
557
- } else {
522
+ try {
523
+ runGitCommand(['worktree', 'add', worktreePath, targetBranch], {
524
+ cwd: sourcePath
525
+ });
526
+ } catch (error) {
558
527
  try {
559
- runGitCommand(['worktree', 'add', worktreePath, targetBranch], {
560
- cwd: sourcePath
561
- });
562
- targetPath = worktreePath;
563
- worktrees.push({ branch: targetBranch, path: worktreePath });
564
- } catch (error) {
565
- // Check if branch is already checked out elsewhere
566
- if (error.message && error.message.includes('already checked out')) {
528
+ const worktreeArgs = ['worktree', 'add', worktreePath, '-b', targetBranch];
529
+ if (baseBranch && baseBranch.trim()) {
530
+ worktreeArgs.push(baseBranch.trim());
531
+ }
532
+ runGitCommand(worktreeArgs, { cwd: sourcePath });
533
+ } catch (err) {
534
+ if (err.message && err.message.includes('already checked out')) {
567
535
  throw new Error(
568
536
  `无法添加项目:分支 '${targetBranch}' 已在其他工作树中检出。\n` +
569
537
  `仓库路径: ${sourcePath}\n\n` +
570
- `Git worktree 不允许在同一仓库的多个工作树中检出相同的分支。\n\n` +
571
538
  `解决方案:\n` +
572
539
  `1. 指定不同的分支名\n` +
573
540
  `2. 或者禁用 worktree 模式(设置 createWorktree: false)`
574
541
  );
575
542
  }
576
-
577
- // Branch doesn't exist, try creating it
578
- try {
579
- const worktreeArgs = ['worktree', 'add', worktreePath, '-b', targetBranch];
580
- if (baseBranch && baseBranch.trim()) {
581
- worktreeArgs.push(baseBranch.trim());
582
- }
583
- runGitCommand(worktreeArgs, {
584
- cwd: sourcePath
585
- });
586
- targetPath = worktreePath;
587
- worktrees.push({ branch: targetBranch, path: worktreePath });
588
- } catch (err) {
589
- // Check for "already checked out" error in create branch attempt
590
- if (err.message && err.message.includes('already checked out')) {
591
- throw new Error(
592
- `无法添加项目:分支 '${targetBranch}' 已在其他工作树中检出。\n` +
593
- `仓库路径: ${sourcePath}\n\n` +
594
- `Git worktree 不允许在同一仓库的多个工作树中检出相同的分支。\n\n` +
595
- `解决方案:\n` +
596
- `1. 指定不同的分支名\n` +
597
- `2. 或者禁用 worktree 模式(设置 createWorktree: false)`
598
- );
599
- }
600
-
601
- // Other errors: fall back to symlink mode
602
- console.warn(`创建 worktree 失败,使用软链接: ${err.message}`);
603
- targetPath = sourcePath;
604
- worktrees = getGitWorktrees(sourcePath);
605
- }
543
+ throw new Error(`创建 worktree 失败: ${err.message}`);
606
544
  }
607
545
  }
546
+
547
+ targetPath = worktreePath;
548
+ worktrees.push({ branch: targetBranch, path: worktreePath });
608
549
  } else if (isGit) {
550
+ // Git 仓库但不创建 worktree:symlink 指向源路径
609
551
  worktrees = getGitWorktrees(sourcePath);
552
+ createSymlink(targetPath, symlinkPath);
553
+ } else {
554
+ // 非 Git 仓库:symlink 指向源路径
555
+ createSymlink(targetPath, symlinkPath);
610
556
  }
611
557
 
612
- // 创建软链接
613
- fs.symlinkSync(targetPath, symlinkPath, 'dir');
614
-
615
558
  // 更新配置
616
559
  workspace.projects.push({
617
560
  name: symlinkName,
@@ -629,7 +572,7 @@ function addProjectToWorkspace(workspaceId, projectConfig) {
629
572
  /**
630
573
  * 从工作区移除项目
631
574
  */
632
- function removeProjectFromWorkspace(workspaceId, projectName, removeWorktrees = false) {
575
+ function removeProjectFromWorkspace(workspaceId, projectName) {
633
576
  const data = loadWorkspaces();
634
577
  const workspace = data.workspaces.find(ws => ws.id === workspaceId);
635
578
 
@@ -644,26 +587,28 @@ function removeProjectFromWorkspace(workspaceId, projectName, removeWorktrees =
644
587
  }
645
588
 
646
589
  const project = workspace.projects[projectIndex];
647
- const symlinkPath = path.join(workspace.path, projectName);
590
+ const projectPath = path.join(workspace.path, projectName);
648
591
 
649
- // 删除软链接
650
- if (fs.existsSync(symlinkPath)) {
651
- fs.unlinkSync(symlinkPath);
652
- }
653
-
654
- // 清理 worktrees
655
- if (removeWorktrees && project.worktrees && project.worktrees.length > 0) {
656
- for (const wt of project.worktrees) {
657
- if (fs.existsSync(wt.path)) {
658
- try {
659
- runGitCommand(['worktree', 'remove', wt.path, '--force'], {
660
- cwd: project.sourcePath
661
- });
662
- } catch (error) {
663
- console.error(`删除 worktree 失败: ${wt.path}`, error.message);
592
+ if (project.useWorktree) {
593
+ // worktree 项目:用 git worktree remove 注销引用(目录在工作区内)
594
+ if (fs.existsSync(projectPath) && fs.existsSync(project.sourcePath)) {
595
+ try {
596
+ runGitCommand(['worktree', 'remove', projectPath, '--force'], {
597
+ cwd: project.sourcePath
598
+ });
599
+ } catch (error) {
600
+ console.error(`注销 worktree 失败: ${projectPath}`, error.message);
601
+ // git 注销失败时手动删除目录
602
+ if (fs.existsSync(projectPath)) {
603
+ fs.rmSync(projectPath, { recursive: true, force: true });
664
604
  }
665
605
  }
666
606
  }
607
+ } else {
608
+ // symlink 项目:直接删除软链接
609
+ if (fs.existsSync(projectPath)) {
610
+ fs.unlinkSync(projectPath);
611
+ }
667
612
  }
668
613
 
669
614
  // 从配置中移除
@@ -1 +0,0 @@
1
- .analytics-page[data-v-7683ab57]{height:100%;background:var(--bg-primary);overflow-y:auto;padding:16px 20px;box-sizing:border-box;display:flex;flex-direction:column;gap:14px}.analytics-header[data-v-7683ab57]{display:flex;align-items:center;justify-content:space-between;flex-shrink:0;flex-wrap:wrap;gap:10px}.page-title[data-v-7683ab57]{margin:0;font-size:18px;font-weight:700;color:var(--text-primary)}.toolbar[data-v-7683ab57]{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.range-buttons[data-v-7683ab57]{display:flex;align-items:center;gap:4px}.range-btn[data-v-7683ab57]{padding:4px 10px;font-size:12px;border:1px solid var(--border-color, #e0e0e6);background:transparent;color:var(--text-secondary);border-radius:4px;cursor:pointer;transition:all .15s}.range-btn[data-v-7683ab57]:hover{border-color:var(--primary-color, #18a058);color:var(--primary-color, #18a058)}.range-btn.active[data-v-7683ab57]{background:var(--primary-color, #18a058);border-color:var(--primary-color, #18a058);color:#fff}.custom-range-wrapper[data-v-7683ab57]{margin-left:4px}.loading-overlay[data-v-7683ab57]{display:flex;justify-content:center;padding:20px 0}.summary-cards[data-v-7683ab57]{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;flex-shrink:0}.summary-card[data-v-7683ab57]{background:var(--bg-secondary);border:1px solid var(--border-color, #e0e0e6);border-radius:8px;padding:14px 16px}.summary-label[data-v-7683ab57]{font-size:11px;color:var(--text-secondary);margin-bottom:6px;text-transform:uppercase;letter-spacing:.05em}.summary-value[data-v-7683ab57]{font-size:22px;font-weight:700;color:var(--text-primary);line-height:1.2}.chart-section[data-v-7683ab57]{background:var(--bg-secondary);border:1px solid var(--border-color, #e0e0e6);border-radius:8px;padding:14px 16px;flex-shrink:0}.chart-section.fullscreen[data-v-7683ab57]{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1000;border-radius:0;padding:20px;background:var(--bg-secondary);overflow:auto}.cumulative-section[data-v-7683ab57]{margin-bottom:8px}.chart-section-header[data-v-7683ab57]{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}.chart-title[data-v-7683ab57]{font-size:13px;font-weight:600;color:var(--text-primary)}.chart-controls[data-v-7683ab57]{display:flex;align-items:center;gap:8px}.chart-type-toggle[data-v-7683ab57]{display:flex;border:1px solid var(--border-color, #e0e0e6);border-radius:4px;overflow:hidden}.toggle-btn[data-v-7683ab57]{padding:3px 10px;font-size:12px;border:none;background:transparent;color:var(--text-secondary);cursor:pointer;transition:all .15s}.toggle-btn.active[data-v-7683ab57]{background:var(--primary-color, #18a058);color:#fff}.icon-btn[data-v-7683ab57]{display:flex;align-items:center;justify-content:center;width:28px;height:28px;border:1px solid var(--border-color, #e0e0e6);border-radius:4px;background:transparent;cursor:pointer;color:var(--text-secondary);transition:all .15s}.icon-btn[data-v-7683ab57]:hover{border-color:var(--primary-color, #18a058);color:var(--primary-color, #18a058)}.main-chart[data-v-7683ab57]{height:560px;width:100%}.chart-section.fullscreen .main-chart[data-v-7683ab57]{height:calc(100vh - 100px)}.cumulative-chart[data-v-7683ab57]{height:200px;width:100%}.empty-state[data-v-7683ab57]{height:120px;display:flex;align-items:center;justify-content:center;color:var(--text-secondary);font-size:13px}@media (max-width: 768px){.analytics-page[data-v-7683ab57]{padding:10px 12px}.summary-cards[data-v-7683ab57]{grid-template-columns:repeat(2,1fr)}.analytics-header[data-v-7683ab57]{flex-direction:column;align-items:flex-start}.toolbar[data-v-7683ab57]{width:100%}}@media (max-width: 480px){.summary-cards[data-v-7683ab57]{grid-template-columns:1fr 1fr}.summary-value[data-v-7683ab57]{font-size:18px}}