coding-tool-x 3.5.9 → 3.5.11

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 (92) 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-C5W3axXs.js → Analytics-Bw-0r4Ys.js} +2 -2
  5. package/dist/web/assets/Analytics-RNn1BUbG.css +1 -0
  6. package/dist/web/assets/{ConfigTemplates-DzyVFDx9.js → ConfigTemplates-C6QRIeDj.js} +1 -1
  7. package/dist/web/assets/Home-BQxQ1LhR.css +1 -0
  8. package/dist/web/assets/{Home-C9TQNB6f.js → Home-B_Z9ythw.js} +1 -1
  9. package/dist/web/assets/{PluginManager-9B_brLWT.js → PluginManager-Ce-mws6Q.js} +1 -1
  10. package/dist/web/assets/ProjectList-9Z7FFCIT.js +1 -0
  11. package/dist/web/assets/ProjectList-DL4JK6ci.css +1 -0
  12. package/dist/web/assets/SessionList-CyETQcs8.js +1 -0
  13. package/dist/web/assets/SessionList-D12muXvx.css +1 -0
  14. package/dist/web/assets/{SkillManager-vST8DRRg.js → SkillManager-DSUcP1se.js} +1 -1
  15. package/dist/web/assets/{WorkspaceManager-ov1KgRXR.js → WorkspaceManager-Cvbc11bJ.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-CTeg1xG-.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-channels.js +4 -0
  37. package/src/server/api/codex-proxy.js +45 -2
  38. package/src/server/api/codex-sessions.js +89 -12
  39. package/src/server/api/commands.js +2 -38
  40. package/src/server/api/dashboard.js +1 -25
  41. package/src/server/api/gemini-proxy.js +4 -2
  42. package/src/server/api/gemini-sessions.js +86 -9
  43. package/src/server/api/hooks.js +1 -17
  44. package/src/server/api/opencode-proxy.js +7 -2
  45. package/src/server/api/opencode-sessions.js +83 -10
  46. package/src/server/api/plugins.js +34 -128
  47. package/src/server/api/proxy.js +5 -3
  48. package/src/server/api/sessions.js +181 -27
  49. package/src/server/api/workspaces.js +0 -14
  50. package/src/server/index.js +9 -31
  51. package/src/server/services/agents-service.js +62 -269
  52. package/src/server/services/base/base-channel-service.js +3 -1
  53. package/src/server/services/channels.js +5 -0
  54. package/src/server/services/codex-channels.js +2 -0
  55. package/src/server/services/codex-sessions.js +88 -108
  56. package/src/server/services/commands-service.js +81 -281
  57. package/src/server/services/config-export-service.js +7 -7
  58. package/src/server/services/config-registry-service.js +9 -8
  59. package/src/server/services/config-sync-manager.js +41 -61
  60. package/src/server/services/config-sync-service.js +3 -3
  61. package/src/server/services/gemini-channels.js +17 -6
  62. package/src/server/services/gemini-config.js +4 -3
  63. package/src/server/services/gemini-sessions.js +59 -26
  64. package/src/server/services/gemini-settings-manager.js +3 -2
  65. package/src/server/services/mcp-service.js +14 -9
  66. package/src/server/services/native-oauth-adapters.js +3 -3
  67. package/src/server/services/network-access.js +2 -61
  68. package/src/server/services/notification-hooks.js +19 -184
  69. package/src/server/services/opencode-channels.js +9 -0
  70. package/src/server/services/opencode-sessions.js +6 -16
  71. package/src/server/services/opencode-settings-manager.js +3 -3
  72. package/src/server/services/plugins-service.js +62 -996
  73. package/src/server/services/prompts-service.js +9 -5
  74. package/src/server/services/sessions.js +171 -183
  75. package/src/server/services/skill-service.js +18 -155
  76. package/src/server/websocket-server.js +4 -35
  77. package/dist/web/assets/Analytics-vQS5IWvs.css +0 -1
  78. package/dist/web/assets/Home-qzk118Of.css +0 -1
  79. package/dist/web/assets/ProjectList-Bjt6mrsV.js +0 -1
  80. package/dist/web/assets/ProjectList-GCC2QOmq.css +0 -1
  81. package/dist/web/assets/SessionList-BsHPgmUR.css +0 -1
  82. package/dist/web/assets/SessionList-DcBH13uA.js +0 -1
  83. package/dist/web/assets/icons-CEq2hYB-.js +0 -1
  84. package/dist/web/assets/index-Dih_bOsv.css +0 -1
  85. package/dist/web/assets/index-Duc7QP4e.js +0 -2
  86. package/dist/web/assets/vendors-Bsp-dq2d.js +0 -7
  87. package/dist/web/assets/vue-vendor-BxIT0uQq.js +0 -45
  88. package/docs/Caddyfile.example +0 -19
  89. package/docs/reverse-proxy-https.md +0 -57
  90. package/src/server/services/https-cert.js +0 -171
  91. package/src/server/services/session-launch-command.js +0 -58
  92. package/src/server/services/web-ui-runtime.js +0 -54
@@ -11,6 +11,7 @@ const fs = require('fs');
11
11
  const path = require('path');
12
12
  const crypto = require('crypto');
13
13
  const { normalizeGatewaySourceType, normalizeNumber } = require('./proxy-utils');
14
+ const { resolveChannelWebsiteUrl } = require('../../../config/channel-preset-websites');
14
15
 
15
16
  class BaseChannelService {
16
17
  /**
@@ -225,6 +226,7 @@ class BaseChannelService {
225
226
  normalized.gatewaySourceType,
226
227
  this.defaultGatewaySource
227
228
  );
229
+ normalized.websiteUrl = resolveChannelWebsiteUrl(this.platform, normalized);
228
230
  return normalized;
229
231
  }
230
232
 
@@ -248,4 +250,4 @@ class BaseChannelService {
248
250
  }
249
251
  }
250
252
 
251
- module.exports = BaseChannelService;
253
+ module.exports = BaseChannelService;
@@ -301,6 +301,10 @@ function updateChannel(id, updates) {
301
301
  return service.updateChannel(id, updates);
302
302
  }
303
303
 
304
+ function markChannelAsRecentlyUsed(id) {
305
+ return service.updateChannel(id, {});
306
+ }
307
+
304
308
  function deleteChannel(id) {
305
309
  return service.deleteChannel(id);
306
310
  }
@@ -323,6 +327,7 @@ module.exports = {
323
327
  getCurrentSettings,
324
328
  createChannel,
325
329
  updateChannel,
330
+ markChannelAsRecentlyUsed,
326
331
  deleteChannel,
327
332
  applyChannelToSettings,
328
333
  getBestChannelForRestore,
@@ -261,6 +261,7 @@ function createChannel(name, providerKey, baseUrl, apiKey, wireApi, extraConfig
261
261
  });
262
262
  }
263
263
  function updateChannel(id, updates) { return service.updateChannel(id, updates); }
264
+ function markChannelAsRecentlyUsed(id) { return service.updateChannel(id, {}); }
264
265
  function deleteChannel(id) { return service.deleteChannel(id); }
265
266
  function saveChannelOrder(order) { return service.saveChannelOrder(order); }
266
267
  function applyChannelToSettings(id) { return service.applyChannelToSettings(id); }
@@ -281,6 +282,7 @@ module.exports = {
281
282
  getChannels,
282
283
  createChannel,
283
284
  updateChannel,
285
+ markChannelAsRecentlyUsed,
284
286
  deleteChannel,
285
287
  getEnabledChannels,
286
288
  saveChannelOrder,
@@ -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,75 @@ 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;
900
834
  }
901
835
 
836
+ const { lines, eol, hasTrailingEol } = splitTextPreserveEol(content);
837
+ const keptLines = [];
838
+ let matchedUserMessages = 0;
839
+ let targetUserReached = false;
840
+
841
+ for (const line of lines) {
842
+ if (!line.trim()) {
843
+ keptLines.push(line);
844
+ continue;
845
+ }
846
+
847
+ let parsed;
848
+ try {
849
+ parsed = JSON.parse(line);
850
+ } catch (err) {
851
+ keptLines.push(line);
852
+ continue;
853
+ }
854
+
855
+ if (parsed?.type !== 'response_item' || !parsed?.payload) {
856
+ keptLines.push(line);
857
+ continue;
858
+ }
859
+
860
+ const preview = extractCodexPreviewFromResponseItem(parsed.payload);
861
+ if (!preview) {
862
+ keptLines.push(line);
863
+ continue;
864
+ }
865
+
866
+ if (targetUserReached) {
867
+ return joinTextPreserveEol(keptLines, eol, hasTrailingEol);
868
+ }
869
+
870
+ matchedUserMessages += 1;
871
+ keptLines.push(line);
872
+ if (matchedUserMessages >= afterUserMessageNumber) {
873
+ targetUserReached = true;
874
+ }
875
+ }
876
+
877
+ if (targetUserReached) {
878
+ return joinTextPreserveEol(keptLines, eol, hasTrailingEol);
879
+ }
880
+
881
+ throw new Error(`afterUserMessageNumber ${afterUserMessageNumber} exceeds available user messages (${matchedUserMessages})`);
882
+ }
883
+
884
+ function readSessionMetaSummaryFast(filePath) {
902
885
  let fd;
903
886
  try {
904
887
  fd = fs.openSync(filePath, 'r');
@@ -936,9 +919,7 @@ function readSessionMetaSummaryFast(filePath, fileMeta = null) {
936
919
  }
937
920
 
938
921
  if (!payload) return null;
939
- const summary = { payload, preview };
940
- setCachedSessionMetaSummary(filePath, fileMeta, summary);
941
- return summary;
922
+ return { payload, preview };
942
923
  } catch (err) {
943
924
  return null;
944
925
  } finally {
@@ -952,8 +933,8 @@ function readSessionMetaSummaryFast(filePath, fileMeta = null) {
952
933
  }
953
934
  }
954
935
 
955
- function readSessionMetaPayloadFast(filePath, fileMeta = null) {
956
- const summary = readSessionMetaSummaryFast(filePath, fileMeta);
936
+ function readSessionMetaPayloadFast(filePath) {
937
+ const summary = readSessionMetaSummaryFast(filePath);
957
938
  return summary?.payload || null;
958
939
  }
959
940
 
@@ -978,7 +959,7 @@ function calculateProjectAndSessionCounts() {
978
959
 
979
960
  const projectNames = new Set();
980
961
  sessions.forEach((session) => {
981
- const payload = readSessionMetaPayloadFast(session.filePath, session);
962
+ const payload = readSessionMetaPayloadFast(session.filePath);
982
963
  const projectName = extractCodexProjectNameFromMeta(payload || {});
983
964
  if (projectName) {
984
965
  projectNames.add(projectName);
@@ -1029,6 +1010,5 @@ module.exports = {
1029
1010
  saveSessionOrder,
1030
1011
  getProjectOrder,
1031
1012
  saveProjectOrder,
1032
- getProjectAndSessionCounts,
1033
- rewriteForkedCodexSessionContent
1013
+ getProjectAndSessionCounts
1034
1014
  };