coding-tool-x 3.5.8 → 3.5.10

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 (91) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +4 -25
  3. package/bin/ctx.js +1 -6
  4. package/dist/web/assets/{Analytics-BzoNzfbi.js → Analytics-DPb1zErn.js} +2 -2
  5. package/dist/web/assets/Analytics-RNn1BUbG.css +1 -0
  6. package/dist/web/assets/{ConfigTemplates-O4ikBt1o.js → ConfigTemplates-Bki2k4Lo.js} +1 -1
  7. package/dist/web/assets/{Home-BQjsnblU.js → Home-BKYHKSax.js} +1 -1
  8. package/dist/web/assets/Home-BQxQ1LhR.css +1 -0
  9. package/dist/web/assets/{PluginManager-DS_DJnVc.js → PluginManager-jgeQOE24.js} +1 -1
  10. package/dist/web/assets/ProjectList-CF67RzP_.js +1 -0
  11. package/dist/web/assets/ProjectList-DL4JK6ci.css +1 -0
  12. package/dist/web/assets/SessionList-DhHsY5q9.js +1 -0
  13. package/dist/web/assets/SessionList-lz23U6ZJ.css +1 -0
  14. package/dist/web/assets/{SkillManager-DpNE02r0.js → SkillManager-CtEz5gGp.js} +1 -1
  15. package/dist/web/assets/{WorkspaceManager-DMY7_SHh.js → WorkspaceManager-D3B0Y5XE.js} +1 -1
  16. package/dist/web/assets/icons-lKCTySl7.js +1 -0
  17. package/dist/web/assets/index-BS7aplYp.css +1 -0
  18. package/dist/web/assets/index-BnyNm74G.js +2 -0
  19. package/dist/web/assets/{markdown-DyTJGI4N.js → markdown-BNJfEgCA.js} +1 -1
  20. package/dist/web/assets/{naive-ui-Cg4_ZeoT.js → naive-ui-DM897mIP.js} +1 -1
  21. package/dist/web/assets/vendors-DQA4Hw9v.js +7 -0
  22. package/dist/web/assets/vue-vendor-B5RqjEjQ.js +45 -0
  23. package/dist/web/index.html +8 -8
  24. package/package.json +1 -2
  25. package/src/commands/daemon.js +5 -33
  26. package/src/commands/export-config.js +6 -6
  27. package/src/commands/stats.js +4 -41
  28. package/src/commands/ui.js +3 -12
  29. package/src/config/channel-preset-websites.js +114 -0
  30. package/src/config/default.js +9 -5
  31. package/src/config/loader.js +2 -2
  32. package/src/config/model-metadata.json +16 -20
  33. package/src/config/paths.js +33 -166
  34. package/src/index.js +34 -125
  35. package/src/server/api/agents.js +2 -52
  36. package/src/server/api/codex-proxy.js +4 -2
  37. package/src/server/api/codex-sessions.js +88 -9
  38. package/src/server/api/commands.js +2 -38
  39. package/src/server/api/dashboard.js +1 -25
  40. package/src/server/api/gemini-proxy.js +4 -2
  41. package/src/server/api/gemini-sessions.js +86 -9
  42. package/src/server/api/hooks.js +1 -17
  43. package/src/server/api/opencode-proxy.js +7 -2
  44. package/src/server/api/opencode-sessions.js +82 -7
  45. package/src/server/api/plugins.js +34 -128
  46. package/src/server/api/proxy.js +5 -3
  47. package/src/server/api/sessions.js +180 -24
  48. package/src/server/api/workspaces.js +0 -14
  49. package/src/server/index.js +9 -31
  50. package/src/server/services/agents-service.js +62 -269
  51. package/src/server/services/base/base-channel-service.js +3 -1
  52. package/src/server/services/channels.js +5 -0
  53. package/src/server/services/codex-channels.js +2 -0
  54. package/src/server/services/codex-sessions.js +78 -108
  55. package/src/server/services/commands-service.js +81 -281
  56. package/src/server/services/config-export-service.js +7 -7
  57. package/src/server/services/config-registry-service.js +9 -8
  58. package/src/server/services/config-sync-manager.js +41 -61
  59. package/src/server/services/config-sync-service.js +3 -3
  60. package/src/server/services/gemini-channels.js +17 -6
  61. package/src/server/services/gemini-config.js +4 -3
  62. package/src/server/services/gemini-sessions.js +51 -26
  63. package/src/server/services/gemini-settings-manager.js +3 -2
  64. package/src/server/services/mcp-service.js +14 -9
  65. package/src/server/services/native-oauth-adapters.js +3 -3
  66. package/src/server/services/network-access.js +2 -61
  67. package/src/server/services/notification-hooks.js +19 -184
  68. package/src/server/services/opencode-channels.js +9 -0
  69. package/src/server/services/opencode-sessions.js +4 -4
  70. package/src/server/services/opencode-settings-manager.js +3 -3
  71. package/src/server/services/plugins-service.js +62 -996
  72. package/src/server/services/prompts-service.js +9 -5
  73. package/src/server/services/sessions.js +128 -99
  74. package/src/server/services/skill-service.js +18 -155
  75. package/src/server/websocket-server.js +4 -35
  76. package/dist/web/assets/Analytics-vQS5IWvs.css +0 -1
  77. package/dist/web/assets/Home-qzk118Of.css +0 -1
  78. package/dist/web/assets/ProjectList-CqYDtsHx.js +0 -1
  79. package/dist/web/assets/ProjectList-GCC2QOmq.css +0 -1
  80. package/dist/web/assets/SessionList-CfPtcq6Y.css +0 -1
  81. package/dist/web/assets/SessionList-DMlLtMCz.js +0 -1
  82. package/dist/web/assets/icons-CEq2hYB-.js +0 -1
  83. package/dist/web/assets/index-Clf0l3wc.js +0 -2
  84. package/dist/web/assets/index-Dih_bOsv.css +0 -1
  85. package/dist/web/assets/vendors-Bsp-dq2d.js +0 -7
  86. package/dist/web/assets/vue-vendor-BxIT0uQq.js +0 -45
  87. package/docs/Caddyfile.example +0 -19
  88. package/docs/reverse-proxy-https.md +0 -57
  89. package/src/server/services/https-cert.js +0 -171
  90. package/src/server/services/session-launch-command.js +0 -81
  91. package/src/server/services/web-ui-runtime.js +0 -54
@@ -10,7 +10,6 @@ const ALL_SESSIONS_CACHE_TTL_MS = 20 * 1000;
10
10
  const PROJECTS_CACHE_TTL_MS = 300 * 1000;
11
11
  const PROJECT_SESSIONS_CACHE_TTL_MS = 120 * 1000;
12
12
  const FAST_META_READ_BYTES = 64 * 1024;
13
- const MAX_SESSION_META_SUMMARY_CACHE_ENTRIES = 5000;
14
13
  const EMPTY_COUNTS = Object.freeze({ projectCount: 0, sessionCount: 0 });
15
14
 
16
15
  let countsCache = {
@@ -33,8 +32,6 @@ let allSessionsCache = {
33
32
  value: []
34
33
  };
35
34
 
36
- let sessionMetaSummaryCache = new Map();
37
-
38
35
  const CODEX_PROJECTS_CACHE_KEY = `${CacheKeys.PROJECTS}codex`;
39
36
  const codexSessionCacheKeys = new Set();
40
37
 
@@ -42,47 +39,6 @@ function getCodexSessionsCacheKey(projectName) {
42
39
  return `${CacheKeys.SESSIONS}codex:${projectName}`;
43
40
  }
44
41
 
45
- function getSessionMetaSummaryCacheKey(filePath, fileMeta = {}) {
46
- return `${filePath}:${fileMeta.mtimeMs || 0}:${fileMeta.size || 0}`;
47
- }
48
-
49
- function getCachedSessionMetaSummary(filePath, fileMeta) {
50
- if (!fileMeta) {
51
- return null;
52
- }
53
- return sessionMetaSummaryCache.get(getSessionMetaSummaryCacheKey(filePath, fileMeta)) || null;
54
- }
55
-
56
- function setCachedSessionMetaSummary(filePath, fileMeta, summary) {
57
- if (!fileMeta || !summary?.payload) {
58
- return;
59
- }
60
-
61
- const cacheKey = getSessionMetaSummaryCacheKey(filePath, fileMeta);
62
- sessionMetaSummaryCache.delete(cacheKey);
63
- sessionMetaSummaryCache.set(cacheKey, summary);
64
-
65
- while (sessionMetaSummaryCache.size > MAX_SESSION_META_SUMMARY_CACHE_ENTRIES) {
66
- const oldestKey = sessionMetaSummaryCache.keys().next().value;
67
- if (!oldestKey) {
68
- break;
69
- }
70
- sessionMetaSummaryCache.delete(oldestKey);
71
- }
72
- }
73
-
74
- function pruneSessionMetaSummaryCache(files = []) {
75
- const activeKeys = new Set(
76
- files.map(file => getSessionMetaSummaryCacheKey(file.filePath, file))
77
- );
78
-
79
- for (const cacheKey of sessionMetaSummaryCache.keys()) {
80
- if (!activeKeys.has(cacheKey)) {
81
- sessionMetaSummaryCache.delete(cacheKey);
82
- }
83
- }
84
- }
85
-
86
42
  /**
87
43
  * 获取会话目录
88
44
  */
@@ -172,7 +128,6 @@ function scanSessionFiles() {
172
128
  expiresAt,
173
129
  value: new Map(parsed.map(file => [file.sessionId, file]))
174
130
  };
175
- pruneSessionMetaSummaryCache(parsed);
176
131
 
177
132
  return parsed;
178
133
  }
@@ -190,7 +145,7 @@ function getAllSessions() {
190
145
  const files = scanSessionFiles();
191
146
 
192
147
  const parsed = files.map(file => {
193
- const fastSummary = readSessionMetaSummaryFast(file.filePath, file);
148
+ const fastSummary = readSessionMetaSummaryFast(file.filePath);
194
149
  let session = null;
195
150
 
196
151
  if (fastSummary && fastSummary.payload) {
@@ -261,7 +216,7 @@ function normalizeSession(codexSession) {
261
216
  filePath: filePath || '',
262
217
  gitBranch: meta.git?.branch || null,
263
218
  firstMessage: preview || null,
264
- forkedFrom: null,
219
+ forkedFrom: null, // Codex 不支持 fork
265
220
 
266
221
  // 额外的 Codex 特有字段(前端可能需要)
267
222
  source: 'codex'
@@ -666,7 +621,7 @@ function deleteSession(sessionId) {
666
621
  * @param {string} sessionId - 原会话 ID
667
622
  * @returns {Object} Fork 结果 { newSessionId, forkedFrom }
668
623
  */
669
- function forkSession(sessionId) {
624
+ function forkSession(sessionId, options = {}) {
670
625
  const sourceFile = findSessionFileById(sessionId);
671
626
 
672
627
  if (!sourceFile) {
@@ -674,7 +629,11 @@ function forkSession(sessionId) {
674
629
  }
675
630
 
676
631
  // 读取原会话文件内容
677
- const content = fs.readFileSync(sourceFile.filePath, 'utf8');
632
+ const originalContent = fs.readFileSync(sourceFile.filePath, 'utf8');
633
+ const content = sliceCodexContentByUserMessage(
634
+ originalContent,
635
+ options.afterUserMessageNumber
636
+ );
678
637
 
679
638
  // 生成新的 session ID (使用 crypto.randomUUID 生成 v4 UUID)
680
639
  const crypto = require('crypto');
@@ -700,70 +659,31 @@ function forkSession(sessionId) {
700
659
 
701
660
  const newFileName = `rollout-${timestamp}-${newSessionId}.jsonl`;
702
661
  const newFilePath = path.join(targetDir, newFileName);
703
- const rewrittenContent = rewriteForkedCodexSessionContent(content, newSessionId, now.toISOString());
704
662
 
705
663
  // 写入新文件
706
- fs.writeFileSync(newFilePath, rewrittenContent, 'utf8');
664
+ fs.writeFileSync(newFilePath, content, 'utf8');
707
665
 
708
666
  // 保存 fork 关系(复用 Claude Code 的 fork 关系存储)
709
667
  const { getForkRelations, saveForkRelations } = require('./sessions');
710
668
  const forkRelations = getForkRelations();
711
669
  forkRelations[newSessionId] = sessionId;
712
670
  saveForkRelations(forkRelations);
671
+ if (options.alias) {
672
+ const { setAlias } = require('./alias');
673
+ setAlias(newSessionId, options.alias);
674
+ }
713
675
 
714
676
  invalidateProjectAndSessionCountsCache();
715
677
  invalidateCodexSessionCaches();
716
678
  return {
717
679
  newSessionId,
718
680
  forkedFrom: sessionId,
719
- newFilePath
681
+ newFilePath,
682
+ alias: options.alias || null,
683
+ afterUserMessageNumber: options.afterUserMessageNumber || null
720
684
  };
721
685
  }
722
686
 
723
- function rewriteForkedCodexSessionContent(content, newSessionId, nowIsoTimestamp) {
724
- const lines = String(content || '').split('\n');
725
-
726
- return lines.map((line) => {
727
- if (!line.trim()) {
728
- return line;
729
- }
730
-
731
- let parsed;
732
- try {
733
- parsed = JSON.parse(line);
734
- } catch (err) {
735
- return line;
736
- }
737
-
738
- if (parsed.type === 'session_meta' && parsed.payload && typeof parsed.payload === 'object') {
739
- return JSON.stringify({
740
- ...parsed,
741
- timestamp: nowIsoTimestamp,
742
- payload: {
743
- ...parsed.payload,
744
- id: newSessionId,
745
- timestamp: nowIsoTimestamp
746
- }
747
- });
748
- }
749
-
750
- if (parsed.type === 'event' && parsed.event && typeof parsed.event === 'object' && parsed.event.type === 'session_start') {
751
- const nextEvent = { ...parsed.event };
752
- if (typeof nextEvent.session_id === 'string' && nextEvent.session_id.trim()) {
753
- nextEvent.session_id = newSessionId;
754
- }
755
-
756
- return JSON.stringify({
757
- ...parsed,
758
- timestamp: nowIsoTimestamp,
759
- event: nextEvent
760
- });
761
- }
762
-
763
- return line;
764
- }).join('\n');
765
- }
766
-
767
687
  /**
768
688
  * 获取会话排序(按项目)
769
689
  * @param {string} projectName - 项目名称
@@ -893,12 +813,65 @@ function extractCodexPreviewFromResponseItem(payload = {}) {
893
813
  return text.substring(0, 100);
894
814
  }
895
815
 
896
- function readSessionMetaSummaryFast(filePath, fileMeta = null) {
897
- const cached = getCachedSessionMetaSummary(filePath, fileMeta);
898
- if (cached) {
899
- return cached;
816
+ function splitTextPreserveEol(content) {
817
+ const eol = content.includes('\r\n') ? '\r\n' : '\n';
818
+ const hasTrailingEol = content.endsWith('\r\n') || content.endsWith('\n');
819
+ return {
820
+ lines: content.split(/\r?\n/),
821
+ eol,
822
+ hasTrailingEol
823
+ };
824
+ }
825
+
826
+ function joinTextPreserveEol(lines, eol, hasTrailingEol) {
827
+ const text = lines.join(eol);
828
+ return hasTrailingEol ? `${text}${eol}` : text;
829
+ }
830
+
831
+ function sliceCodexContentByUserMessage(content, afterUserMessageNumber) {
832
+ if (!Number.isInteger(afterUserMessageNumber) || afterUserMessageNumber <= 0) {
833
+ return content;
834
+ }
835
+
836
+ const { lines, eol, hasTrailingEol } = splitTextPreserveEol(content);
837
+ const keptLines = [];
838
+ let matchedUserMessages = 0;
839
+
840
+ for (const line of lines) {
841
+ if (!line.trim()) {
842
+ keptLines.push(line);
843
+ continue;
844
+ }
845
+
846
+ let parsed;
847
+ try {
848
+ parsed = JSON.parse(line);
849
+ } catch (err) {
850
+ keptLines.push(line);
851
+ continue;
852
+ }
853
+
854
+ keptLines.push(line);
855
+
856
+ if (parsed?.type !== 'response_item' || !parsed?.payload) {
857
+ continue;
858
+ }
859
+
860
+ const preview = extractCodexPreviewFromResponseItem(parsed.payload);
861
+ if (!preview) {
862
+ continue;
863
+ }
864
+
865
+ matchedUserMessages += 1;
866
+ if (matchedUserMessages >= afterUserMessageNumber) {
867
+ return joinTextPreserveEol(keptLines, eol, hasTrailingEol);
868
+ }
900
869
  }
901
870
 
871
+ throw new Error(`afterUserMessageNumber ${afterUserMessageNumber} exceeds available user messages (${matchedUserMessages})`);
872
+ }
873
+
874
+ function readSessionMetaSummaryFast(filePath) {
902
875
  let fd;
903
876
  try {
904
877
  fd = fs.openSync(filePath, 'r');
@@ -936,9 +909,7 @@ function readSessionMetaSummaryFast(filePath, fileMeta = null) {
936
909
  }
937
910
 
938
911
  if (!payload) return null;
939
- const summary = { payload, preview };
940
- setCachedSessionMetaSummary(filePath, fileMeta, summary);
941
- return summary;
912
+ return { payload, preview };
942
913
  } catch (err) {
943
914
  return null;
944
915
  } finally {
@@ -952,8 +923,8 @@ function readSessionMetaSummaryFast(filePath, fileMeta = null) {
952
923
  }
953
924
  }
954
925
 
955
- function readSessionMetaPayloadFast(filePath, fileMeta = null) {
956
- const summary = readSessionMetaSummaryFast(filePath, fileMeta);
926
+ function readSessionMetaPayloadFast(filePath) {
927
+ const summary = readSessionMetaSummaryFast(filePath);
957
928
  return summary?.payload || null;
958
929
  }
959
930
 
@@ -978,7 +949,7 @@ function calculateProjectAndSessionCounts() {
978
949
 
979
950
  const projectNames = new Set();
980
951
  sessions.forEach((session) => {
981
- const payload = readSessionMetaPayloadFast(session.filePath, session);
952
+ const payload = readSessionMetaPayloadFast(session.filePath);
982
953
  const projectName = extractCodexProjectNameFromMeta(payload || {});
983
954
  if (projectName) {
984
955
  projectNames.add(projectName);
@@ -1029,6 +1000,5 @@ module.exports = {
1029
1000
  saveSessionOrder,
1030
1001
  getProjectOrder,
1031
1002
  saveProjectOrder,
1032
- getProjectAndSessionCounts,
1033
- rewriteForkedCodexSessionContent
1003
+ getProjectAndSessionCounts
1034
1004
  };