@adversity/coding-tool-x 2.4.1 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -18,6 +18,61 @@ function detectAvailableTerminals() {
18
18
  }
19
19
  }
20
20
 
21
+ /**
22
+ * 获取系统 Shell 路径
23
+ */
24
+ function getSystemShell() {
25
+ if (process.platform === 'win32') {
26
+ const candidates = [];
27
+ if (process.env.COMSPEC) {
28
+ candidates.push(process.env.COMSPEC);
29
+ }
30
+ const systemRoot = process.env.SystemRoot || process.env.windir;
31
+ if (systemRoot) {
32
+ candidates.push(path.join(systemRoot, 'System32', 'cmd.exe'));
33
+ }
34
+ return candidates.find((candidate) => candidate && fs.existsSync(candidate)) || null;
35
+ }
36
+
37
+ if (process.env.SHELL && fs.existsSync(process.env.SHELL)) {
38
+ return process.env.SHELL;
39
+ }
40
+
41
+ const commonShells = [
42
+ '/bin/zsh',
43
+ '/bin/bash',
44
+ '/bin/sh',
45
+ '/usr/bin/zsh',
46
+ '/usr/bin/bash',
47
+ '/usr/bin/sh'
48
+ ];
49
+
50
+ for (const shell of commonShells) {
51
+ if (fs.existsSync(shell)) {
52
+ return shell;
53
+ }
54
+ }
55
+
56
+ return null;
57
+ }
58
+
59
+ function createSystemShellEntry(isDefault = false) {
60
+ const shell = getSystemShell();
61
+ if (!shell) {
62
+ return null;
63
+ }
64
+
65
+ return {
66
+ id: 'system-shell',
67
+ name: '系统 Shell',
68
+ available: true,
69
+ isDefault,
70
+ command: 'Web 终端(系统 Shell)',
71
+ supportsLocalLaunch: false,
72
+ shell
73
+ };
74
+ }
75
+
21
76
  /**
22
77
  * Windows 终端检测
23
78
  * 只保留经过验证、确定能自动执行命令的终端
@@ -82,6 +137,11 @@ function detectWindowsTerminals() {
82
137
  }
83
138
  }
84
139
 
140
+ const systemShell = createSystemShellEntry(false);
141
+ if (systemShell) {
142
+ terminals.push(systemShell);
143
+ }
144
+
85
145
  return terminals;
86
146
  }
87
147
 
@@ -245,6 +305,11 @@ function detectMacTerminals() {
245
305
  // Rio 不可用
246
306
  }
247
307
 
308
+ const systemShell = createSystemShellEntry(false);
309
+ if (systemShell) {
310
+ terminals.push(systemShell);
311
+ }
312
+
248
313
  return terminals;
249
314
  }
250
315
 
@@ -254,6 +319,8 @@ function detectMacTerminals() {
254
319
  function detectLinuxTerminals() {
255
320
  const terminals = [];
256
321
 
322
+ const systemShell = createSystemShellEntry(false);
323
+
257
324
  const terminalConfigs = [
258
325
  { id: 'gnome-terminal', name: 'GNOME Terminal', cmd: 'gnome-terminal', args: '-- bash -c "cd \'{cwd}\' && claude -r {sessionId}; exec bash"' },
259
326
  { id: 'konsole', name: 'Konsole', cmd: 'konsole', args: '-e bash -c "cd \'{cwd}\' && claude -r {sessionId}; exec bash"' },
@@ -288,6 +355,13 @@ function detectLinuxTerminals() {
288
355
  }
289
356
  });
290
357
 
358
+ if (systemShell) {
359
+ if (!terminals.find(t => t.isDefault)) {
360
+ systemShell.isDefault = true;
361
+ }
362
+ terminals.push(systemShell);
363
+ }
364
+
291
365
  return terminals;
292
366
  }
293
367
 
@@ -302,5 +376,6 @@ function getDefaultTerminal() {
302
376
 
303
377
  module.exports = {
304
378
  detectAvailableTerminals,
305
- getDefaultTerminal
379
+ getDefaultTerminal,
380
+ getSystemShell
306
381
  };
@@ -611,51 +611,72 @@ function removeProjectFromWorkspace(workspaceId, projectName, removeWorktrees =
611
611
 
612
612
  /**
613
613
  * 获取所有渠道(Claude/Codex/Gemini)的项目并集
614
- * @returns {Array} 去重后的项目列表
614
+ * @returns {Promise<Array>} 去重后的项目列表
615
615
  */
616
- function getAllAvailableProjects() {
617
- const { NATIVE_PATHS } = require('../../config/paths');
616
+ async function getAllAvailableProjects() {
617
+ const { loadConfig } = require('../../config/loader');
618
618
  const sessionsService = require('./sessions');
619
+ const codexSessionsService = require('./codex-sessions');
620
+ const geminiSessionsService = require('./gemini-sessions');
621
+ const { isCodexInstalled } = require('./codex-config');
622
+ const { isGeminiInstalled } = require('./gemini-config');
619
623
 
620
624
  const allProjects = [];
621
- const seenPaths = new Set();
625
+ const seenKeys = new Set();
626
+
627
+ function addProject(channel, project) {
628
+ if (!project || !project.name) return;
629
+
630
+ const projectPath = project.fullPath || project.path || null;
631
+ const keyBase = projectPath || project.name;
632
+ const projectKey = `${channel}:${keyBase}`;
633
+ if (seenKeys.has(projectKey)) return;
634
+ seenKeys.add(projectKey);
635
+
636
+ const displayName = project.displayName || (projectPath ? path.basename(projectPath) : project.name);
637
+ const lastUsedValue = project.lastUsed || project.lastUpdated || null;
638
+ const lastUsed = typeof lastUsedValue === 'string'
639
+ ? new Date(lastUsedValue).getTime()
640
+ : (lastUsedValue || 0);
641
+
642
+ allProjects.push({
643
+ name: project.name,
644
+ displayName,
645
+ fullPath: projectPath || project.name,
646
+ channel,
647
+ sessionCount: project.sessionCount || 0,
648
+ lastUsed,
649
+ isGitRepo: projectPath ? isGitRepo(projectPath) : false
650
+ });
651
+ }
622
652
 
623
- // 定义渠道配置
624
- const channels = [
625
- { name: 'claude', projectsDir: NATIVE_PATHS.claude.projects },
626
- { name: 'codex', projectsDir: NATIVE_PATHS.codex.sessions },
627
- { name: 'gemini', projectsDir: NATIVE_PATHS.gemini.tmp }
628
- ];
653
+ try {
654
+ const config = loadConfig();
655
+ const claudeProjects = await sessionsService.getProjectsWithStats(config, { force: true });
656
+ const list = Array.isArray(claudeProjects) ? claudeProjects : [];
657
+ list.forEach(project => addProject('claude', project));
658
+ } catch (error) {
659
+ console.error('获取 claude 项目失败:', error.message);
660
+ }
629
661
 
630
- for (const channel of channels) {
631
- try {
632
- if (!fs.existsSync(channel.projectsDir)) {
633
- continue;
634
- }
662
+ try {
663
+ if (isCodexInstalled()) {
664
+ const codexProjects = codexSessionsService.getProjects();
665
+ const list = Array.isArray(codexProjects) ? codexProjects : [];
666
+ list.forEach(project => addProject('codex', project));
667
+ }
668
+ } catch (error) {
669
+ console.error('获取 codex 项目失败:', error.message);
670
+ }
635
671
 
636
- const config = { projectsDir: channel.projectsDir };
637
- const projects = sessionsService.getProjectsWithStats(config, { force: true });
638
-
639
- for (const proj of projects) {
640
- // 使用 fullPath + channel 组合去重,允许同一项目在不同渠道显示
641
- const projectPath = proj.fullPath;
642
- const projectKey = `${projectPath}:${channel.name}`;
643
- if (projectPath && !seenPaths.has(projectKey)) {
644
- seenPaths.add(projectKey);
645
- allProjects.push({
646
- name: proj.name,
647
- displayName: proj.displayName,
648
- fullPath: projectPath,
649
- channel: channel.name,
650
- sessionCount: proj.sessionCount || 0,
651
- lastUsed: proj.lastUsed,
652
- isGitRepo: isGitRepo(projectPath)
653
- });
654
- }
655
- }
656
- } catch (error) {
657
- console.error(`获取 ${channel.name} 项目失败:`, error.message);
672
+ try {
673
+ if (isGeminiInstalled()) {
674
+ const geminiProjects = geminiSessionsService.getProjects();
675
+ const list = Array.isArray(geminiProjects) ? geminiProjects : [];
676
+ list.forEach(project => addProject('gemini', project));
658
677
  }
678
+ } catch (error) {
679
+ console.error('获取 gemini 项目失败:', error.message);
659
680
  }
660
681
 
661
682
  // 按最后使用时间排序
@@ -392,6 +392,7 @@ function broadcastSchedulerState(source, schedulerState) {
392
392
  // ============ 终端 WebSocket 消息处理 ============
393
393
 
394
394
  const { getCommandForChannel } = require('./services/terminal-commands');
395
+ const { getWebTerminalShellConfig } = require('./services/terminal-config');
395
396
 
396
397
  /**
397
398
  * 处理终端相关的 WebSocket 消息
@@ -444,13 +445,16 @@ function handleTerminalCreate(ws, message) {
444
445
  // 获取启动命令
445
446
  const startCommand = getCommandForChannel(channel, sessionId, workDir);
446
447
 
448
+ const shellConfig = getWebTerminalShellConfig();
449
+
447
450
  // 创建终端
448
451
  const terminal = ptyManager.create({
449
452
  cwd: workDir,
450
453
  channel,
451
454
  sessionId,
452
455
  projectName,
453
- startCommand
456
+ startCommand,
457
+ ...shellConfig
454
458
  });
455
459
 
456
460
  // 绑定 WebSocket
@@ -476,7 +480,7 @@ function handleTerminalCreate(ws, message) {
476
480
  * 绑定到已有终端
477
481
  */
478
482
  function handleTerminalAttach(ws, message) {
479
- const { terminalId } = message;
483
+ const { terminalId, includeHistory, cols, rows } = message;
480
484
 
481
485
  if (!terminalId) {
482
486
  ws.send(JSON.stringify({
@@ -498,7 +502,14 @@ function handleTerminalAttach(ws, message) {
498
502
 
499
503
  // 绑定 WebSocket
500
504
  ws.terminalId = terminalId;
501
- ptyManager.attachWebSocket(terminalId, ws);
505
+ const shouldIncludeHistory = typeof includeHistory === 'boolean' ? includeHistory : true;
506
+ if (Number.isFinite(cols) && Number.isFinite(rows) && cols > 0 && rows > 0) {
507
+ ptyManager.resize(terminalId, cols, rows);
508
+ }
509
+ ptyManager.attachWebSocket(terminalId, ws, {
510
+ includeHistory: shouldIncludeHistory,
511
+ trimLastLine: true
512
+ });
502
513
 
503
514
  ws.send(JSON.stringify({
504
515
  type: 'terminal:attached',