coding-tool-x 3.8.7 → 3.8.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.
Files changed (143) hide show
  1. package/README.md +2 -2
  2. package/dist/web/assets/{AgentsDrawer-C7ePByZx.js → AgentsDrawer-Cj5CJ4d9.js} +1 -1
  3. package/dist/web/assets/Analytics-1doS-8uC.js +1 -0
  4. package/dist/web/assets/AssetPathField-C9TOPl7o.js +1 -0
  5. package/dist/web/assets/{ChatHistoryDrawer-Ccl4rhdz.js → ChatHistoryDrawer-BTmQ1aQr.js} +1 -1
  6. package/dist/web/assets/{CollapseItem-MxG_UD4o.js → CollapseItem-CwqJk3bE.js} +1 -1
  7. package/dist/web/assets/{CommandsDrawer-DWbaxHRZ.js → CommandsDrawer-CaZWDI_G.js} +1 -1
  8. package/dist/web/assets/ConfigExportDrawer-C3vSZEsS.js +1 -0
  9. package/dist/web/assets/{ConfigTemplates-AiRPv2cd.js → ConfigTemplates-gyERg9iC.js} +1 -1
  10. package/dist/web/assets/{ConfigTemplatesDrawer-CKMFsfEF.js → ConfigTemplatesDrawer-B17RqFi4.js} +1 -1
  11. package/dist/web/assets/{ConfigTemplatesPanel-Cvbnkvpt.js → ConfigTemplatesPanel-DKIC_FqG.js} +1 -1
  12. package/dist/web/assets/{Divider-D28g1gwj.js → Divider-DsdbkHrG.js} +1 -1
  13. package/dist/web/assets/{FavoritesDrawer-DA-dR_vP.js → FavoritesDrawer-DaziQmBp.js} +1 -1
  14. package/dist/web/assets/Forward-DLCL7aMY.js +1 -0
  15. package/dist/web/assets/{GatewayConvertDrawer-402_raGb.js → GatewayConvertDrawer-OMgWUUSc.js} +1 -1
  16. package/dist/web/assets/Home-DOSUTfrE.js +1 -0
  17. package/dist/web/assets/{MarkdownEditor-BvkDECwH.js → MarkdownEditor-9TTwTS-U.js} +1 -1
  18. package/dist/web/assets/McpDrawer-I8rl8lGH.js +1 -0
  19. package/dist/web/assets/{McpFormDrawer-DPMnyt5L.js → McpFormDrawer-DEP3C7ut.js} +1 -1
  20. package/dist/web/assets/{PluginManager-FKVeugr4.js → PluginManager-BuRcWuBj.js} +1 -1
  21. package/dist/web/assets/{PluginsDrawer-MvM1R55d.js → PluginsDrawer-_zZENaQn.js} +1 -1
  22. package/dist/web/assets/PluginsPanel-Bs5qZ-TK.js +1 -0
  23. package/dist/web/assets/PluginsPanel-CzGMK-Ap.css +1 -0
  24. package/dist/web/assets/ProjectList-BSxuL2td.js +1 -0
  25. package/dist/web/assets/{ProjectList-677RLZ8O.css → ProjectList-VbIYSHbH.css} +1 -1
  26. package/dist/web/assets/PromptsDrawer-CoZ-ZtsY.js +1 -0
  27. package/dist/web/assets/RecentSessionsDrawer-B_17aJBG.js +1 -0
  28. package/dist/web/assets/RecentSessionsDrawer-yrAD4p1A.css +1 -0
  29. package/dist/web/assets/SessionCard-BL_x0d3t.js +1 -0
  30. package/dist/web/assets/SessionList-DhRYZ6vl.js +1 -0
  31. package/dist/web/assets/SessionList-qG_XpPBJ.css +1 -0
  32. package/dist/web/assets/{SettingsDrawer-lMjttbkD.js → SettingsDrawer-fYb0DU7X.js} +1 -1
  33. package/dist/web/assets/SkillManager-S698Hns8.js +1 -0
  34. package/dist/web/assets/{SkillsDrawer-DF6JsSyj.js → SkillsDrawer-Ms4lPCow.js} +1 -1
  35. package/dist/web/assets/{SkillsPanel-B3Nm5j32.css → SkillsPanel-C4ke56nF.css} +1 -1
  36. package/dist/web/assets/SkillsPanel-rqnqAOGL.js +1 -0
  37. package/dist/web/assets/{SpeedTestDrawer-BaZkZqPP.js → SpeedTestDrawer-Bbn38SfV.js} +1 -1
  38. package/dist/web/assets/Upload-CdyVHxCI.js +1 -0
  39. package/dist/web/assets/{WorkspaceDrawer-BFSwZ5yU.js → WorkspaceDrawer-L7HJRc6u.js} +1 -1
  40. package/dist/web/assets/{WorkspaceManager-BHL1uYct.js → WorkspaceManager-DsoNg2z8.js} +1 -1
  41. package/dist/web/assets/{WorkspacePanel-B5PooBtg.js → WorkspacePanel-Be8PVZah.js} +1 -1
  42. package/dist/web/assets/{headers-cf-QapUq.js → headers-B5JWGn3v.js} +1 -1
  43. package/dist/web/assets/{icons-B2Vg-IOC.js → icons-DmFe4b5j.js} +1 -1
  44. package/dist/web/assets/{index-BohDDAy_.css → index-3Lp6z8TQ.css} +1 -1
  45. package/dist/web/assets/index-BKlAWTH-.js +2 -0
  46. package/dist/web/assets/{light-Cp0t7v32.js → light-0eavxNvt.js} +1 -1
  47. package/dist/web/assets/platforms-BmzzwnSw.js +1 -0
  48. package/dist/web/assets/platforms-DWXgmy2w.js +1 -0
  49. package/dist/web/assets/session-visibility-BAmxh3cK.js +1 -0
  50. package/dist/web/assets/sessions-CC0j-KH_.js +1 -0
  51. package/dist/web/assets/sessions-D4yQtfKb.js +1 -0
  52. package/dist/web/assets/use-message-DqCwefG6.js +1 -0
  53. package/dist/web/assets/{useResponsiveDrawer-D-DDpbnI.js → useResponsiveDrawer-V_onl9NV.js} +1 -1
  54. package/dist/web/index.html +7 -7
  55. package/package.json +1 -1
  56. package/src/commands/daemon.js +27 -4
  57. package/src/commands/search.js +17 -2
  58. package/src/config/paths.js +2 -0
  59. package/src/platforms/channel-auth-registry.js +36 -6
  60. package/src/platforms/channel-auth-service.js +12 -2
  61. package/src/platforms/driver-registry.js +9 -1
  62. package/src/platforms/drivers/claude/session-history-adapter.js +72 -1
  63. package/src/platforms/drivers/claude/sessions-implementation.js +49 -7
  64. package/src/platforms/drivers/claude/sessions.js +30 -6
  65. package/src/platforms/drivers/codex/parser.js +57 -1
  66. package/src/platforms/drivers/codex/session-history-adapter.js +8 -2
  67. package/src/platforms/drivers/codex/sessions-implementation.js +25 -3
  68. package/src/platforms/drivers/codex/sessions.js +18 -1
  69. package/src/platforms/drivers/dsh/api-operations.js +396 -0
  70. package/src/platforms/drivers/dsh/channels-implementation.js +458 -0
  71. package/src/platforms/drivers/dsh/channels.js +191 -0
  72. package/src/platforms/drivers/dsh/common.js +571 -0
  73. package/src/platforms/drivers/dsh/native-config.js +93 -0
  74. package/src/platforms/drivers/dsh/native-logs.js +116 -0
  75. package/src/platforms/drivers/dsh/plugin-manager.js +117 -0
  76. package/src/platforms/drivers/dsh/projects.js +23 -0
  77. package/src/platforms/drivers/dsh/proxy-implementation.js +260 -0
  78. package/src/platforms/drivers/dsh/proxy.js +23 -0
  79. package/src/platforms/drivers/dsh/resources.js +346 -0
  80. package/src/platforms/drivers/dsh/sessions.js +587 -0
  81. package/src/platforms/drivers/dsh/statistics-implementation.js +238 -0
  82. package/src/platforms/drivers/dsh/statistics.js +24 -0
  83. package/src/platforms/drivers/gemini/session-history-adapter.js +49 -1
  84. package/src/platforms/drivers/gemini/sessions-implementation.js +46 -1
  85. package/src/platforms/drivers/gemini/sessions.js +2 -1
  86. package/src/platforms/drivers/omp/prompts.js +58 -2
  87. package/src/platforms/drivers/omp/proxy-implementation.js +25 -1
  88. package/src/platforms/drivers/omp/session-history-adapter.js +42 -1
  89. package/src/platforms/drivers/omp/sessions-implementation.js +51 -3
  90. package/src/platforms/drivers/omp/sessions.js +33 -2
  91. package/src/platforms/drivers/opencode/channels-implementation.js +25 -22
  92. package/src/platforms/drivers/opencode/channels.js +6 -1
  93. package/src/platforms/drivers/opencode/sessions-implementation.js +89 -9
  94. package/src/platforms/drivers/opencode/sessions.js +37 -1
  95. package/src/platforms/manifest-schema.js +43 -3
  96. package/src/platforms/manifests/dsh.json +542 -0
  97. package/src/platforms/manifests/omp.json +2 -1
  98. package/src/platforms/manifests/opencode.json +28 -0
  99. package/src/platforms/native-oauth-adapters.js +318 -18
  100. package/src/platforms/oauth-credentials-service.js +344 -13
  101. package/src/platforms/path-resolver.js +2 -0
  102. package/src/platforms/platform-path-context.js +12 -2
  103. package/src/platforms/registry.js +16 -1
  104. package/src/platforms/runtime.js +5 -0
  105. package/src/platforms/session-history-adapters.js +1 -0
  106. package/src/server/api/platform-api-config.js +23 -2
  107. package/src/server/api/platform-route-factory.js +9 -2
  108. package/src/server/api/platforms.js +19 -1
  109. package/src/server/api/plugins.js +108 -13
  110. package/src/server/index.js +35 -1
  111. package/src/server/services/config-registry-service.js +10 -1
  112. package/src/server/services/config-templates-service.js +1 -0
  113. package/src/server/services/plugins-service.js +474 -103
  114. package/src/server/services/session-history-index.js +900 -140
  115. package/src/server/services/session-history-worker.js +3 -1
  116. package/src/server/services/skill-service.js +214 -7
  117. package/src/server/services/usage-log-utils.js +1 -0
  118. package/src/server/services/workspace-service.js +39 -10
  119. package/src/shared/driver-factories/api.js +22 -6
  120. package/src/shared/driver-factories/projects.js +8 -0
  121. package/dist/web/assets/Analytics-BVG6ehFv.js +0 -1
  122. package/dist/web/assets/AssetPathField-BZI9NvEr.js +0 -1
  123. package/dist/web/assets/ConfigExportDrawer-DrawKwxE.js +0 -1
  124. package/dist/web/assets/Home-CTF8ZWKW.js +0 -1
  125. package/dist/web/assets/McpDrawer-DZGHkuDm.js +0 -1
  126. package/dist/web/assets/PluginsPanel-CKuBs38B.js +0 -1
  127. package/dist/web/assets/PluginsPanel-D6ZqITcR.css +0 -1
  128. package/dist/web/assets/ProjectList-tsjwjXV1.js +0 -1
  129. package/dist/web/assets/PromptsDrawer-B-X9ci8n.js +0 -1
  130. package/dist/web/assets/RecentSessionsDrawer-CKFsOYDM.css +0 -1
  131. package/dist/web/assets/RecentSessionsDrawer-wLktMF_c.js +0 -1
  132. package/dist/web/assets/SessionCard-DkJPy0TY.js +0 -1
  133. package/dist/web/assets/SessionList-BpYRz1cX.css +0 -1
  134. package/dist/web/assets/SessionList-Gzhh4FOZ.js +0 -1
  135. package/dist/web/assets/SkillManager-CzFxWxx-.js +0 -1
  136. package/dist/web/assets/SkillsPanel--lLEts9y.js +0 -1
  137. package/dist/web/assets/Upload-BEQ-3Kqq.js +0 -1
  138. package/dist/web/assets/index-BFInlZGI.js +0 -2
  139. package/dist/web/assets/platforms-CBkB45JP.js +0 -1
  140. package/dist/web/assets/platforms-FTpjxAT6.js +0 -1
  141. package/dist/web/assets/sessions-Cmy31slD.js +0 -1
  142. package/dist/web/assets/sessions-yC_oVVh8.js +0 -1
  143. package/dist/web/assets/use-message-DoLM8C1C.js +0 -1
@@ -0,0 +1,238 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ recordRequest: recordSharedRequest,
5
+ getStatistics: getSharedStatistics,
6
+ getDailyStatistics: getSharedDailyStatistics,
7
+ getTodayStatistics: getSharedTodayStatistics
8
+ } = require('../../../server/services/statistics-service');
9
+ const { normalizeUsageTokens, toNumber } = require('../../../server/services/usage-log-utils');
10
+ const { listArtifacts } = require('./sessions');
11
+ const { normalizeDshEvent } = require('./native-logs');
12
+
13
+ const TOOL_TYPE = 'dsh';
14
+ let configuredContext = {};
15
+
16
+ function configure(context = {}) {
17
+ configuredContext = context;
18
+ }
19
+
20
+ function initAggregateEntry(name = '') {
21
+ return {
22
+ name,
23
+ requests: 0,
24
+ tokens: { input: 0, output: 0, reasoning: 0, cached: 0, total: 0 },
25
+ cost: 0,
26
+ firstUsed: null,
27
+ lastUsed: null
28
+ };
29
+ }
30
+
31
+ function toLegacyEntryShape(entry = {}, includeName = false) {
32
+ const normalized = normalizeUsageTokens(TOOL_TYPE, entry.tokens || {});
33
+ const result = {
34
+ requests: toNumber(entry.requests),
35
+ tokens: {
36
+ input: normalized.input,
37
+ output: normalized.output,
38
+ reasoning: normalized.reasoning,
39
+ cached: normalized.cached,
40
+ total: normalized.total
41
+ },
42
+ cost: toNumber(entry.cost)
43
+ };
44
+
45
+ if (includeName) {
46
+ result.name = entry.name || '';
47
+ if (entry.firstUsed) result.firstUsed = entry.firstUsed;
48
+ if (entry.lastUsed) result.lastUsed = entry.lastUsed;
49
+ }
50
+ return result;
51
+ }
52
+
53
+ function pickToolScope(sharedStats = {}) {
54
+ const byToolType = sharedStats.byToolType || {};
55
+ const toolScope = byToolType[TOOL_TYPE] || {};
56
+ const byChannel = toolScope.channels && typeof toolScope.channels === 'object'
57
+ ? toolScope.channels
58
+ : Object.fromEntries(Object.entries(sharedStats.byChannel || {}).filter(([, value]) => value?.toolType === TOOL_TYPE));
59
+ const byModel = toolScope.models && typeof toolScope.models === 'object'
60
+ ? toolScope.models
61
+ : Object.fromEntries(Object.entries(sharedStats.byModel || {}).filter(([, value]) => value?.toolType === TOOL_TYPE));
62
+ return { toolScope, byChannel, byModel };
63
+ }
64
+
65
+ function buildSummaryStatistics(sharedStats = {}) {
66
+ const { toolScope, byChannel, byModel } = pickToolScope(sharedStats);
67
+ const normalized = toLegacyEntryShape(toolScope);
68
+ return {
69
+ version: '1.0',
70
+ source: 'shared-stats',
71
+ lastUpdated: sharedStats.lastUpdated || new Date().toISOString(),
72
+ global: {
73
+ totalRequests: normalized.requests,
74
+ totalTokens: normalized.tokens.total,
75
+ totalCost: normalized.cost
76
+ },
77
+ byChannel: Object.fromEntries(Object.entries(byChannel).map(([key, value]) => [key, toLegacyEntryShape(value, true)])),
78
+ byModel: Object.fromEntries(Object.entries(byModel).map(([key, value]) => [key, toLegacyEntryShape(value)]))
79
+ };
80
+ }
81
+
82
+ function buildDailyStatistics(sharedDaily = {}, fallbackDate) {
83
+ const toolScope = sharedDaily.byToolType?.[TOOL_TYPE] || {};
84
+ const normalized = toLegacyEntryShape(toolScope);
85
+ return {
86
+ date: sharedDaily.date || fallbackDate,
87
+ source: 'shared-stats',
88
+ summary: {
89
+ requests: normalized.requests,
90
+ tokens: normalized.tokens.total,
91
+ cost: normalized.cost
92
+ },
93
+ byChannel: Object.fromEntries(Object.entries(toolScope.channels || {}).map(([key, value]) => [key, toLegacyEntryShape(value, true)])),
94
+ byModel: Object.fromEntries(Object.entries(toolScope.models || {}).map(([key, value]) => [key, toLegacyEntryShape(value)]))
95
+ };
96
+ }
97
+
98
+ function hasSummaryData(summary = {}) {
99
+ return Boolean(
100
+ summary.global?.totalRequests
101
+ || summary.global?.totalTokens
102
+ || summary.global?.totalCost
103
+ || Object.keys(summary.byChannel || {}).length
104
+ || Object.keys(summary.byModel || {}).length
105
+ );
106
+ }
107
+
108
+ function hasDailyData(daily = {}) {
109
+ return Boolean(
110
+ daily.summary?.requests
111
+ || daily.summary?.tokens
112
+ || daily.summary?.cost
113
+ || Object.keys(daily.byChannel || {}).length
114
+ || Object.keys(daily.byModel || {}).length
115
+ );
116
+ }
117
+
118
+ function eventDateKey(timestamp) {
119
+ const date = new Date(timestamp);
120
+ if (Number.isNaN(date.getTime())) return null;
121
+ return new Date(date.getTime() + 8 * 60 * 60 * 1000).toISOString().slice(0, 10);
122
+ }
123
+
124
+ function resolveDate(value) {
125
+ if (value && typeof value === 'object') {
126
+ return value.params?.date || value.query?.date || eventDateKey(Date.now());
127
+ }
128
+ return value || eventDateKey(Date.now());
129
+ }
130
+
131
+ function readSessionUsage() {
132
+ try {
133
+ const artifacts = listArtifacts(configuredContext, { includeEvents: true });
134
+ return artifacts.flatMap(artifact => (artifact.events || [])
135
+ .map((event, index) => normalizeDshEvent(event, artifact.filePath, index))
136
+ .filter(Boolean)
137
+ .map(event => ({ ...event, sessionId: artifact.header?.id || event.sessionId })));
138
+ } catch (_) {
139
+ return [];
140
+ }
141
+ }
142
+
143
+ function addUsage(entry, event) {
144
+ const tokens = normalizeUsageTokens(TOOL_TYPE, event.tokens || {});
145
+ if (!tokens.total && !tokens.input && !tokens.output && !tokens.reasoning && !tokens.cached) return false;
146
+ entry.requests += 1;
147
+ entry.tokens.input += tokens.input;
148
+ entry.tokens.output += tokens.output;
149
+ entry.tokens.reasoning += tokens.reasoning;
150
+ entry.tokens.cached += tokens.cached;
151
+ entry.tokens.total += tokens.total;
152
+ entry.cost += toNumber(event.cost);
153
+ const iso = new Date(event.timestamp).toISOString();
154
+ if (!entry.firstUsed || iso < entry.firstUsed) entry.firstUsed = iso;
155
+ if (!entry.lastUsed || iso > entry.lastUsed) entry.lastUsed = iso;
156
+ return true;
157
+ }
158
+
159
+ function buildSessionDerivedStatistics(events = readSessionUsage()) {
160
+ const global = initAggregateEntry('DSH Sessions');
161
+ const byChannel = {};
162
+ const byModel = {};
163
+ events.forEach(event => {
164
+ if (!addUsage(global, event)) return;
165
+ const channel = event.channel || event.provider || 'dsh-session';
166
+ const model = event.model || 'unknown';
167
+ byChannel[channel] ||= initAggregateEntry(channel || 'DSH Session');
168
+ byModel[model] ||= initAggregateEntry(model);
169
+ addUsage(byChannel[channel], event);
170
+ addUsage(byModel[model], event);
171
+ });
172
+ return {
173
+ version: '1.0',
174
+ source: global.requests ? 'sessions' : 'empty',
175
+ lastUpdated: global.lastUsed || new Date().toISOString(),
176
+ global: { totalRequests: global.requests, totalTokens: global.tokens.total, totalCost: global.cost },
177
+ byChannel: Object.fromEntries(Object.entries(byChannel).map(([key, value]) => [key, toLegacyEntryShape(value, true)])),
178
+ byModel: Object.fromEntries(Object.entries(byModel).map(([key, value]) => [key, toLegacyEntryShape(value)]))
179
+ };
180
+ }
181
+
182
+ function buildSessionDerivedDailyStatistics(date, events = readSessionUsage()) {
183
+ const daily = buildSessionDerivedStatistics(events.filter(event => eventDateKey(event.timestamp) === date));
184
+ return {
185
+ date,
186
+ source: daily.source,
187
+ summary: {
188
+ requests: daily.global.totalRequests,
189
+ tokens: daily.global.totalTokens,
190
+ cost: daily.global.totalCost
191
+ },
192
+ byChannel: daily.byChannel,
193
+ byModel: daily.byModel
194
+ };
195
+ }
196
+
197
+ function recordRequest(requestData = {}) {
198
+ const normalizedTokens = normalizeUsageTokens(TOOL_TYPE, requestData.tokens || {});
199
+ return recordSharedRequest({
200
+ ...requestData,
201
+ toolType: TOOL_TYPE,
202
+ tokens: {
203
+ input: normalizedTokens.input,
204
+ output: normalizedTokens.output,
205
+ reasoning: normalizedTokens.reasoning,
206
+ cached: normalizedTokens.cached,
207
+ cacheCreation: normalizedTokens.cacheCreation,
208
+ cacheRead: normalizedTokens.cacheRead,
209
+ total: normalizedTokens.total
210
+ }
211
+ });
212
+ }
213
+
214
+ function getStatistics() {
215
+ const shared = buildSummaryStatistics(getSharedStatistics());
216
+ return hasSummaryData(shared) ? shared : buildSessionDerivedStatistics();
217
+ }
218
+
219
+ function getDailyStatistics(date) {
220
+ const requestedDate = resolveDate(date);
221
+ const shared = buildDailyStatistics(getSharedDailyStatistics(requestedDate), requestedDate);
222
+ return hasDailyData(shared) ? shared : buildSessionDerivedDailyStatistics(requestedDate);
223
+ }
224
+
225
+ function getTodayStatistics() {
226
+ const shared = buildDailyStatistics(getSharedTodayStatistics());
227
+ const date = shared.date || eventDateKey(Date.now());
228
+ return hasDailyData(shared) ? shared : buildSessionDerivedDailyStatistics(date);
229
+ }
230
+
231
+ module.exports = {
232
+ configure,
233
+ recordRequest,
234
+ getStatistics,
235
+ getDailyStatistics,
236
+ getTodayStatistics,
237
+ _test: { buildSessionDerivedStatistics, buildSessionDerivedDailyStatistics }
238
+ };
@@ -0,0 +1,24 @@
1
+ 'use strict';
2
+
3
+ const { createCapabilityDriver } = require('../../../shared/capability-driver');
4
+
5
+ function createDriver(context = {}) {
6
+ return createCapabilityDriver({
7
+ ...context,
8
+ platform: 'dsh',
9
+ capability: 'statistics',
10
+ servicePath: './dsh/statistics-implementation',
11
+ localServicePath: '../platforms/drivers/dsh/statistics-implementation',
12
+ methods: {
13
+ summary: 'getStatistics',
14
+ daily: 'getDailyStatistics',
15
+ today: 'getTodayStatistics',
16
+ getStatistics: 'getStatistics',
17
+ getDailyStatistics: 'getDailyStatistics',
18
+ getTodayStatistics: 'getTodayStatistics',
19
+ recordRequest: 'recordRequest'
20
+ }
21
+ });
22
+ }
23
+
24
+ module.exports = { createDriver };
@@ -298,6 +298,54 @@ async function inventory() {
298
298
  return descriptors;
299
299
  }
300
300
 
301
+ async function summarize(descriptor) {
302
+ const header = readDescriptorMetadata(descriptor.filePath, descriptor.size);
303
+ let firstMessage = null;
304
+ let model = null;
305
+ try {
306
+ const fd = fs.openSync(descriptor.filePath, 'r');
307
+ try {
308
+ const buffer = Buffer.alloc(64 * 1024);
309
+ const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, 0);
310
+ const lines = buffer.subarray(0, bytesRead).toString('utf8')
311
+ .split(/\r?\n/).filter(line => line.trim());
312
+ const records = [];
313
+ for (const line of lines) {
314
+ try { records.push(JSON.parse(line)); } catch (_) {}
315
+ }
316
+ const state = records.length === 1 && records[0]?.messages
317
+ ? records[0]
318
+ : null;
319
+ const messages = state?.messages || records;
320
+ const firstUser = messages
321
+ .map(record => normalizeMessageRecord(record))
322
+ .find(message => message?.role === 'user' && message.content);
323
+ firstMessage = firstUser?.content || null;
324
+ model = messages.find(record => record?.model)?.model || null;
325
+ } finally {
326
+ fs.closeSync(fd);
327
+ }
328
+ } catch (_) {}
329
+ const projectPath = header.projectPath || descriptor.projectRoot || null;
330
+ const projectName = header.projectHash || descriptor.projectName
331
+ || (projectPath ? getFilePathHash(projectPath) : 'unknown');
332
+ return {
333
+ session: {
334
+ sessionId: header.sessionId || descriptor.sessionId,
335
+ projectName,
336
+ projectDisplayName: projectPath ? path.basename(projectPath) : `Project ${String(projectName).slice(0, 8)}`,
337
+ projectFullPath: projectPath,
338
+ firstMessage,
339
+ gitBranch: null,
340
+ provider: null,
341
+ model,
342
+ startedAt: null,
343
+ updatedAt: descriptor.mtimeMs,
344
+ extraJson: JSON.stringify({ projectPath, storageName: path.basename(path.dirname(path.dirname(descriptor.filePath))) })
345
+ }
346
+ };
347
+ }
348
+
301
349
  /**
302
350
  * Parse a Gemini session file.
303
351
  */
@@ -341,4 +389,4 @@ async function parse(descriptor) {
341
389
  return { session, messages };
342
390
  }
343
391
 
344
- module.exports = { configure, inventory, parse, extractContentText, normalizeMessageRecord, parseSessionContent };
392
+ module.exports = { configure, inventory, summarize, parse, extractContentText, normalizeMessageRecord, parseSessionContent };
@@ -649,7 +649,7 @@ function getProjectPath(projectHash, options = {}) {
649
649
  * @returns {Array} 项目对象数组
650
650
  */
651
651
  async function getProjects(options = {}) {
652
- const projects = await getSessionHistoryIndex().listProjects('gemini', options);
652
+ const projects = await getSessionHistoryIndex().getAllProjects('gemini', options);
653
653
  const paths = buildPathMapping();
654
654
  return projects.map(project => {
655
655
  const fullPath = project.fullPath || paths.get(project.name) || null;
@@ -664,6 +664,25 @@ async function getProjects(options = {}) {
664
664
  });
665
665
  }
666
666
 
667
+ async function getProjectsPage(options = {}) {
668
+ const payload = await getSessionHistoryIndex().listProjectsPage('gemini', options);
669
+ const paths = buildPathMapping();
670
+ return {
671
+ ...payload,
672
+ projects: payload.projects.map(project => {
673
+ const fullPath = project.fullPath || paths.get(project.name) || null;
674
+ return {
675
+ ...project,
676
+ displayName: fullPath ? path.basename(fullPath) : project.displayName,
677
+ path: fullPath,
678
+ fullPath,
679
+ storageName: fullPath ? path.basename(fullPath) : project.name,
680
+ lastUpdated: project.lastUsed ? new Date(project.lastUsed).toISOString() : null
681
+ };
682
+ })
683
+ };
684
+ }
685
+
667
686
  /**
668
687
  * 获取指定项目的所有会话
669
688
  * @param {string} projectHash - 项目 hash
@@ -690,6 +709,30 @@ async function getProjectSessions(projectHash, options = {}) {
690
709
  }));
691
710
  }
692
711
 
712
+ async function getProjectSessionsPage(projectHash, options = {}) {
713
+ const payload = await getSessionHistoryIndex().listSessionsPage('gemini', projectHash, options);
714
+ return {
715
+ ...payload,
716
+ sessions: payload.sessions.map(session => ({
717
+ sessionId: session.sessionId,
718
+ mtime: new Date(session.mtime).toISOString(),
719
+ size: session.size,
720
+ filePath: session.filePath,
721
+ gitBranch: null,
722
+ firstMessage: session.firstMessage,
723
+ forkedFrom: session.extra?.forkedFrom || null,
724
+ source: 'gemini',
725
+ tokens: session.tokens?.total ?? session.tokens ?? 0,
726
+ cost: session.extra?.cost || 0,
727
+ model: session.model || 'gemini-2.5-pro',
728
+ projectHash: session.projectName,
729
+ projectName: session.projectName,
730
+ storageName: session.extra?.storageName || session.projectName,
731
+ projectRoot: session.projectFullPath || null
732
+ }))
733
+ };
734
+ }
735
+
693
736
  /**
694
737
  * 获取单个会话的完整内容
695
738
  * @param {string} sessionId - 会话 ID
@@ -936,7 +979,9 @@ module.exports = {
936
979
  configure,
937
980
  getAllSessions,
938
981
  getProjects,
982
+ getProjectsPage,
939
983
  getProjectSessions,
984
+ getProjectSessionsPage,
940
985
  getSession,
941
986
  getSessionById,
942
987
  deleteSession,
@@ -11,9 +11,10 @@ function createDriver(context = {}) {
11
11
  servicePath: './gemini/sessions-implementation',
12
12
  localServicePath: '../platforms/drivers/gemini/sessions-implementation',
13
13
  adapterLocalPath: '../platforms/drivers/gemini/session-history-adapter',
14
- adapterMethods: { inventory: 'inventory', parse: 'parse' },
14
+ adapterMethods: { inventory: 'inventory', summarize: 'summarize', parse: 'parse' },
15
15
  methods: {
16
16
  listSessions: 'getProjectSessions',
17
+ listSessionsPage: 'getProjectSessionsPage',
17
18
  recent: 'getRecentSessions',
18
19
  getSessionById: 'getSessionById',
19
20
  getAllSessions: 'getAllSessions',
@@ -1,9 +1,65 @@
1
1
  'use strict';
2
2
 
3
- const { createPromptDriver } = require('../../../shared/driver-factories/prompts');
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { ok, failed } = require('../../../shared/driver-result');
6
+ const { getOmpPaths } = require('./config');
7
+
8
+ const MANAGED_PROMPT_NAMESPACE = 'coding-tool-x';
9
+ const MANAGED_PROMPT_FILE = 'prompt.md';
10
+
11
+ function resolvePromptsDir(context = {}) {
12
+ const native = context.pathContext?.native || {};
13
+ return native.prompts
14
+ || context.paths?.prompts
15
+ || getOmpPaths(process.env, { resolveRuntime: false }).prompts;
16
+ }
17
+
18
+ function stripFrontmatter(content) {
19
+ return String(content || '').replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n(?:\r?\n)?/, '');
20
+ }
21
+
22
+ function buildTemplateContent(content) {
23
+ return `---\ndescription: "coding-tool-x prompt"\n---\n\n${content}`;
24
+ }
4
25
 
5
26
  function createDriver(context = {}) {
6
- return createPromptDriver({ ...context, platform: 'omp' });
27
+ const promptsDir = resolvePromptsDir(context);
28
+ const managedDir = path.join(promptsDir, MANAGED_PROMPT_NAMESPACE);
29
+ const promptPath = path.join(managedDir, MANAGED_PROMPT_FILE);
30
+
31
+ const invoke = (operation, args) => {
32
+ try {
33
+ if (operation === 'read') {
34
+ return ok('omp', 'prompts', operation,
35
+ fs.existsSync(promptPath) ? stripFrontmatter(fs.readFileSync(promptPath, 'utf8')) : '');
36
+ }
37
+
38
+ if (operation === 'write') {
39
+ const [content] = args;
40
+ if (typeof content !== 'string') throw new Error('提示词内容必须是字符串');
41
+ fs.mkdirSync(managedDir, { recursive: true });
42
+ fs.writeFileSync(promptPath, buildTemplateContent(content), 'utf8');
43
+ return ok('omp', 'prompts', operation, content);
44
+ }
45
+
46
+ if (fs.existsSync(managedDir)) {
47
+ fs.rmSync(managedDir, { recursive: true, force: true });
48
+ }
49
+ return ok('omp', 'prompts', operation, true);
50
+ } catch (error) {
51
+ return failed('omp', 'prompts', operation, error);
52
+ }
53
+ };
54
+
55
+ return {
56
+ platform: 'omp',
57
+ capability: 'prompts',
58
+ path: managedDir,
59
+ read: (...args) => invoke('read', args),
60
+ write: (...args) => invoke('write', args),
61
+ remove: (...args) => invoke('remove', args)
62
+ };
7
63
  }
8
64
 
9
65
  module.exports = { createDriver };
@@ -15,6 +15,7 @@ const {
15
15
  const { createOmpGateway } = require('./gateway');
16
16
  const { prepareManagedOmpChannels } = require('./gateway-routing');
17
17
  const { probeOmpAuthGateways } = require('./auth-gateway-client');
18
+ const { restoreStoredOmpOAuthCredentials } = require('../../oauth-credentials-service');
18
19
 
19
20
  let currentPort = null;
20
21
  let lastSyncResult = null;
@@ -33,11 +34,24 @@ function withLifecycleLock(operation) {
33
34
 
34
35
  async function startOmpProxyServerUnlocked(options = {}) {
35
36
  if (gateway.status().listening) {
37
+ const oauthRestore = restoreStoredOmpOAuthCredentials(getEnabledChannels());
38
+ if (oauthRestore.warnings.length > 0) {
39
+ lastSyncResult = {
40
+ ...(lastSyncResult || {}),
41
+ warnings: [
42
+ ...(lastSyncResult?.warnings || []),
43
+ ...oauthRestore.warnings
44
+ ]
45
+ };
46
+ }
36
47
  return {
37
48
  success: true,
38
49
  port: gateway.status().port,
39
50
  sync: lastSyncResult,
40
- warnings: lastSyncResult?.warnings || []
51
+ warnings: [
52
+ ...(lastSyncResult?.warnings || []),
53
+ ...(oauthRestore.warnings || [])
54
+ ]
41
55
  };
42
56
  }
43
57
  const preserveStartTime = options.preserveStartTime || false;
@@ -56,6 +70,7 @@ async function startOmpProxyServerUnlocked(options = {}) {
56
70
  });
57
71
  currentPort = gatewayStatus.port;
58
72
  try {
73
+ const oauthRestore = restoreStoredOmpOAuthCredentials(channels);
59
74
  const oauthSupport = await probeOmpAuthGateways(channels);
60
75
  const descriptor = {
61
76
  ...gateway.descriptor(),
@@ -71,10 +86,19 @@ async function startOmpProxyServerUnlocked(options = {}) {
71
86
  lastSyncResult = {
72
87
  ...lastSyncResult,
73
88
  warnings: [
89
+ ...(oauthRestore.warnings || []),
74
90
  ...(lastSyncResult?.warnings || []),
75
91
  ...oauthSupport.warnings
76
92
  ]
77
93
  };
94
+ } else if (oauthRestore.warnings.length > 0) {
95
+ lastSyncResult = {
96
+ ...lastSyncResult,
97
+ warnings: [
98
+ ...(oauthRestore.warnings || []),
99
+ ...(lastSyncResult?.warnings || [])
100
+ ]
101
+ };
78
102
  }
79
103
  } catch (error) {
80
104
  if (wasEnabled) {
@@ -194,6 +194,47 @@ async function inventory({ projectsDir } = {}) {
194
194
  return descriptors;
195
195
  }
196
196
 
197
+ async function summarize(descriptor) {
198
+ let entries = [];
199
+ let fd;
200
+ try {
201
+ fd = fs.openSync(descriptor.filePath, 'r');
202
+ const buffer = Buffer.alloc(64 * 1024);
203
+ const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, 0);
204
+ entries = buffer.subarray(0, bytesRead).toString('utf8')
205
+ .split(/\r?\n/).filter(line => line.trim()).map(line => {
206
+ try { return JSON.parse(line); } catch (_) { return null; }
207
+ }).filter(Boolean);
208
+ } catch (_) {
209
+ entries = [];
210
+ } finally {
211
+ if (fd !== undefined) {
212
+ try { fs.closeSync(fd); } catch (_) {}
213
+ }
214
+ }
215
+ const header = entries.find(entry => entry?.type === 'session') || {};
216
+ const messages = entries.map(convertOmpEntry).filter(Boolean);
217
+ const firstUser = messages.find(message => message.type === 'user' && message.content);
218
+ const modelChange = [...entries].reverse().find(entry => entry?.type === 'model_change');
219
+ const cwd = header.cwd || header.project?.cwd || '';
220
+ const sessionId = header.id || descriptor.sessionId || parseOmpSessionId(descriptor.filePath);
221
+ return {
222
+ session: {
223
+ sessionId,
224
+ projectName: cwd ? encodeProjectName(cwd) : 'unknown',
225
+ projectDisplayName: getDisplayName(cwd),
226
+ projectFullPath: cwd || null,
227
+ firstMessage: firstUser?.content || null,
228
+ gitBranch: header.gitBranch || null,
229
+ provider: modelChange?.provider || messages.find(message => message.provider)?.provider || null,
230
+ model: modelChange?.modelId || modelChange?.model || messages.find(message => message.model)?.model || null,
231
+ startedAt: header.timestamp ? new Date(header.timestamp).getTime() : null,
232
+ updatedAt: descriptor.mtimeMs,
233
+ extraJson: JSON.stringify({ cwd: cwd || null })
234
+ }
235
+ };
236
+ }
237
+
197
238
  /**
198
239
  * Parse an OMP session file.
199
240
  */
@@ -277,4 +318,4 @@ async function parse(descriptor) {
277
318
  return { session, messages };
278
319
  }
279
320
 
280
- module.exports = { configure, inventory, parse, convertOmpEntry, parseUsage, encodeProjectName: encodeProjectName, getDisplayName, readJsonLines, extractMessageText, normalizeTextContent };
321
+ module.exports = { configure, inventory, summarize, parse, convertOmpEntry, parseUsage, encodeProjectName: encodeProjectName, getDisplayName, readJsonLines, extractMessageText, normalizeTextContent };
@@ -331,7 +331,7 @@ function scanSessionFiles(rootDir = getOmpSessionPaths().sessions) {
331
331
  }
332
332
 
333
333
  async function getAllSessions(options = {}) {
334
- const projects = await getSessionHistoryIndex().listProjects('omp', options);
334
+ const projects = await getSessionHistoryIndex().getAllProjects('omp', options);
335
335
  const groups = await Promise.all(projects.map(project => getSessionHistoryIndex().listSessions('omp', project.name, options)));
336
336
  return groups.flat().map(session => normalizeSession({
337
337
  ...session,
@@ -417,6 +417,24 @@ async function getProjects(options = {}) {
417
417
  return projects;
418
418
  }
419
419
 
420
+ async function getProjectsPage(options = {}) {
421
+ const payload = await getSessionHistoryIndex().listProjectsPage('omp', options);
422
+ return {
423
+ ...payload,
424
+ projects: payload.projects.map(p => ({
425
+ name: p.name,
426
+ path: p.fullPath || p.path || '',
427
+ fullPath: p.fullPath || '',
428
+ displayName: p.displayName || p.name,
429
+ sessionCount: p.sessionCount || 0,
430
+ latestSession: p.latestSession || null,
431
+ mtime: p.lastUsed || null,
432
+ mtimeMs: p.lastUsed ? new Date(p.lastUsed).getTime() : 0,
433
+ source: 'omp'
434
+ }))
435
+ };
436
+ }
437
+
420
438
  async function getSessionsByProject(projectName, options = {}) {
421
439
  const idxSessions = await getSessionHistoryIndex().listSessions('omp', projectName, options);
422
440
  const sessions = idxSessions.map(s => ({
@@ -450,6 +468,27 @@ async function getSessionsByProject(projectName, options = {}) {
450
468
  return sessions.map(normalizeSession);
451
469
  }
452
470
 
471
+ async function getSessionsPage(projectName, options = {}) {
472
+ const payload = await getSessionHistoryIndex().listSessionsPage('omp', projectName, options);
473
+ return {
474
+ ...payload,
475
+ sessions: payload.sessions.map(s => normalizeSession({
476
+ sessionId: s.sessionId,
477
+ filePath: s.filePath,
478
+ firstMessage: s.firstMessage,
479
+ provider: s.provider,
480
+ model: s.model,
481
+ size: s.size,
482
+ mtime: new Date(s.mtime).toISOString(),
483
+ mtimeMs: s.mtime,
484
+ directory: s.projectFullPath,
485
+ cwd: s.projectFullPath,
486
+ messageCount: s.messageCount,
487
+ usage: s.tokens
488
+ }))
489
+ };
490
+ }
491
+
453
492
  function normalizeSession(session) {
454
493
  if (!session) return null;
455
494
  return {
@@ -509,8 +548,15 @@ async function getRecentSessions(limit = 5) {
509
548
  }));
510
549
  }
511
550
 
512
- async function searchSessions(keyword, contextLength = 35, projectName = null) {
513
- return getSessionHistoryIndex().searchSessions('omp', keyword, { contextLength, projectName });
551
+ async function searchSessions(keyword, contextLength = 35, options = {}) {
552
+ const normalizedOptions = options && typeof options === 'object' && !Array.isArray(options)
553
+ ? options
554
+ : { projectName: typeof options === 'string' ? options : null };
555
+ return getSessionHistoryIndex().searchSessions('omp', keyword, {
556
+ ...normalizedOptions,
557
+ contextLength,
558
+ projectName: normalizedOptions.projectName || null
559
+ });
514
560
  }
515
561
 
516
562
  async function deleteSession(sessionId) {
@@ -611,10 +657,12 @@ module.exports = {
611
657
  getOmpSessionPaths,
612
658
  getProjectAndSessionCounts,
613
659
  getProjects,
660
+ getProjectsPage,
614
661
  getRecentSessions,
615
662
  getSessionById,
616
663
  getSessionMessages,
617
664
  getSessionsByProject,
665
+ getSessionsPage,
618
666
  isOmpInstalled: isOmpCliInstalled,
619
667
  normalizeSession,
620
668
  parseSessionFile,