coding-tool-x 3.8.6 → 3.8.8
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.
- package/dist/web/assets/{AgentsDrawer-Db9wAyaZ.js → AgentsDrawer-DRQuwi0m.js} +1 -1
- package/dist/web/assets/Analytics-Cf5KwEdj.js +1 -0
- package/dist/web/assets/AssetPathField-DyniM0pF.js +1 -0
- package/dist/web/assets/{ChatHistoryDrawer-CwzJHyNP.js → ChatHistoryDrawer-CrfS0KYV.js} +1 -1
- package/dist/web/assets/{CollapseItem-CTCfSiue.js → CollapseItem-ClMsJuUu.js} +1 -1
- package/dist/web/assets/{CommandsDrawer-vDghB1U9.js → CommandsDrawer-CzHelf56.js} +1 -1
- package/dist/web/assets/ConfigExportDrawer-BEZ8wTby.js +1 -0
- package/dist/web/assets/ConfigTemplates-Crg3xWe5.js +1 -0
- package/dist/web/assets/{ConfigTemplatesDrawer-CbsJp76X.js → ConfigTemplatesDrawer-BqM32Im7.js} +1 -1
- package/dist/web/assets/{ConfigTemplatesPanel-liMSTh7s.js → ConfigTemplatesPanel-lTdutC8H.js} +1 -1
- package/dist/web/assets/{Divider-D28g1gwj.js → Divider-DsdbkHrG.js} +1 -1
- package/dist/web/assets/{FavoritesDrawer-D6QSiYaL.js → FavoritesDrawer-DDyWUBJR.js} +1 -1
- package/dist/web/assets/Forward-DLCL7aMY.js +1 -0
- package/dist/web/assets/{GatewayConvertDrawer-RvET6iNG.js → GatewayConvertDrawer-Rigtt_5J.js} +1 -1
- package/dist/web/assets/Home-tCSKmSHV.js +1 -0
- package/dist/web/assets/{MarkdownEditor-CYmLGYju.js → MarkdownEditor-BMMUufSc.js} +1 -1
- package/dist/web/assets/McpDrawer-CmE-d7VW.js +1 -0
- package/dist/web/assets/{McpFormDrawer-BcTlgSpN.js → McpFormDrawer-ByvS18od.js} +1 -1
- package/dist/web/assets/{PluginManager-Dh2o0xkZ.js → PluginManager-C4kJlpVX.js} +1 -1
- package/dist/web/assets/{PluginsDrawer-deKQ5u3I.js → PluginsDrawer-05R4k15x.js} +1 -1
- package/dist/web/assets/PluginsPanel-BCoEs7Og.js +1 -0
- package/dist/web/assets/PluginsPanel-CzGMK-Ap.css +1 -0
- package/dist/web/assets/ProjectList-BJt1OIaI.js +1 -0
- package/dist/web/assets/ProjectList-DG3yu8xZ.css +1 -0
- package/dist/web/assets/PromptsDrawer-DnwBf_oW.js +1 -0
- package/dist/web/assets/RecentSessionsDrawer-B7d-38wH.js +1 -0
- package/dist/web/assets/RecentSessionsDrawer-yrAD4p1A.css +1 -0
- package/dist/web/assets/SessionCard-BUgjHQwX.js +1 -0
- package/dist/web/assets/SessionList-B-bXZRFk.js +1 -0
- package/dist/web/assets/SessionList-qG_XpPBJ.css +1 -0
- package/dist/web/assets/{SettingsDrawer-u_vwUuPS.js → SettingsDrawer-BITzjQLl.js} +1 -1
- package/dist/web/assets/SkillManager-D9jkNuRM.js +1 -0
- package/dist/web/assets/{SkillsDrawer-CkF3BpyC.js → SkillsDrawer-0Z4IefxC.js} +1 -1
- package/dist/web/assets/SkillsPanel-BKIVOj7n.js +1 -0
- package/dist/web/assets/{SkillsPanel-B3Nm5j32.css → SkillsPanel-C4ke56nF.css} +1 -1
- package/dist/web/assets/{SpeedTestDrawer-BgfG1Ked.js → SpeedTestDrawer-Bx1bYf5i.js} +1 -1
- package/dist/web/assets/Upload-CzC4rGI4.js +1 -0
- package/dist/web/assets/{WorkspaceDrawer-Br5CeCzL.js → WorkspaceDrawer-eujtWpoq.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-B2O8-qIw.js → WorkspaceManager-CQ-8Ej68.js} +1 -1
- package/dist/web/assets/{WorkspacePanel-5oDKit3c.js → WorkspacePanel-Cql5qE2R.js} +1 -1
- package/dist/web/assets/headers-BqDmkMjy.js +1 -0
- package/dist/web/assets/{icons-B2Vg-IOC.js → icons-DmFe4b5j.js} +1 -1
- package/dist/web/assets/index-Cmmwj3Fx.js +2 -0
- package/dist/web/assets/{index-Bi1MQHYX.css → index-Cnbe696C.css} +1 -1
- package/dist/web/assets/{light-Cp0t7v32.js → light-0eavxNvt.js} +1 -1
- package/dist/web/assets/platforms-BmzzwnSw.js +1 -0
- package/dist/web/assets/platforms-DWXgmy2w.js +1 -0
- package/dist/web/assets/session-visibility-BAmxh3cK.js +1 -0
- package/dist/web/assets/sessions-D4yQtfKb.js +1 -0
- package/dist/web/assets/sessions-DqlbocLJ.js +1 -0
- package/dist/web/assets/use-message-DqCwefG6.js +1 -0
- package/dist/web/assets/{useResponsiveDrawer-jbmqZuJs.js → useResponsiveDrawer-DZd5rNk5.js} +1 -1
- package/dist/web/index.html +7 -7
- package/package.json +1 -1
- package/src/commands/search.js +17 -2
- package/src/config/paths.js +2 -0
- package/src/platforms/driver-registry.js +9 -1
- package/src/platforms/drivers/claude/session-history-adapter.js +72 -1
- package/src/platforms/drivers/claude/sessions-implementation.js +49 -7
- package/src/platforms/drivers/claude/sessions.js +30 -6
- package/src/platforms/drivers/codex/parser.js +57 -1
- package/src/platforms/drivers/codex/session-history-adapter.js +8 -2
- package/src/platforms/drivers/codex/sessions-implementation.js +25 -3
- package/src/platforms/drivers/codex/sessions.js +18 -1
- package/src/platforms/drivers/dsh/api-operations.js +396 -0
- package/src/platforms/drivers/dsh/channels-implementation.js +458 -0
- package/src/platforms/drivers/dsh/channels.js +191 -0
- package/src/platforms/drivers/dsh/common.js +571 -0
- package/src/platforms/drivers/dsh/native-config.js +93 -0
- package/src/platforms/drivers/dsh/native-logs.js +116 -0
- package/src/platforms/drivers/dsh/plugin-manager.js +117 -0
- package/src/platforms/drivers/dsh/projects.js +23 -0
- package/src/platforms/drivers/dsh/proxy-implementation.js +260 -0
- package/src/platforms/drivers/dsh/proxy.js +23 -0
- package/src/platforms/drivers/dsh/resources.js +346 -0
- package/src/platforms/drivers/dsh/sessions.js +587 -0
- package/src/platforms/drivers/dsh/statistics-implementation.js +238 -0
- package/src/platforms/drivers/dsh/statistics.js +24 -0
- package/src/platforms/drivers/gemini/session-history-adapter.js +49 -1
- package/src/platforms/drivers/gemini/sessions-implementation.js +46 -1
- package/src/platforms/drivers/gemini/sessions.js +2 -1
- package/src/platforms/drivers/omp/session-history-adapter.js +42 -1
- package/src/platforms/drivers/omp/sessions-implementation.js +51 -3
- package/src/platforms/drivers/omp/sessions.js +33 -2
- package/src/platforms/drivers/opencode/sessions-implementation.js +89 -9
- package/src/platforms/drivers/opencode/sessions.js +37 -1
- package/src/platforms/manifest-schema.js +43 -3
- package/src/platforms/manifests/dsh.json +542 -0
- package/src/platforms/path-resolver.js +2 -0
- package/src/platforms/platform-path-context.js +12 -2
- package/src/platforms/registry.js +16 -1
- package/src/platforms/runtime.js +5 -0
- package/src/platforms/session-history-adapters.js +1 -0
- package/src/server/api/platform-api-config.js +23 -2
- package/src/server/api/platform-route-factory.js +9 -2
- package/src/server/api/platforms.js +19 -1
- package/src/server/api/plugins.js +108 -13
- package/src/server/services/config-registry-service.js +10 -1
- package/src/server/services/config-templates-service.js +1 -0
- package/src/server/services/plugins-service.js +474 -103
- package/src/server/services/session-history-index.js +900 -140
- package/src/server/services/session-history-worker.js +3 -1
- package/src/server/services/skill-service.js +214 -7
- package/src/server/services/usage-log-utils.js +1 -0
- package/src/server/services/workspace-service.js +39 -10
- package/src/shared/channel-driver.js +80 -5
- package/src/shared/driver-factories/api.js +22 -6
- package/src/shared/driver-factories/projects.js +8 -0
- package/dist/web/assets/Analytics-C4gMzv7g.js +0 -1
- package/dist/web/assets/AssetPathField-Bd_nMFu3.js +0 -1
- package/dist/web/assets/ConfigExportDrawer-C-XDvlHc.js +0 -1
- package/dist/web/assets/ConfigTemplates-CYY8XRED.js +0 -1
- package/dist/web/assets/Home-P8eseZcf.js +0 -1
- package/dist/web/assets/McpDrawer-Ch3bkKIq.js +0 -1
- package/dist/web/assets/PluginsPanel-BofsgQgj.js +0 -1
- package/dist/web/assets/PluginsPanel-D6ZqITcR.css +0 -1
- package/dist/web/assets/ProjectList-677RLZ8O.css +0 -1
- package/dist/web/assets/ProjectList-DhSWJHTA.js +0 -1
- package/dist/web/assets/PromptsDrawer-Cfb2FeeU.js +0 -1
- package/dist/web/assets/RecentSessionsDrawer-3eXamJhr.js +0 -1
- package/dist/web/assets/RecentSessionsDrawer-CKFsOYDM.css +0 -1
- package/dist/web/assets/SessionCard-CW0eHURI.js +0 -1
- package/dist/web/assets/SessionList-BpYRz1cX.css +0 -1
- package/dist/web/assets/SessionList-DQfyDGWU.js +0 -1
- package/dist/web/assets/SkillManager-DHMprJLA.js +0 -1
- package/dist/web/assets/SkillsPanel-DWE4pFGx.js +0 -1
- package/dist/web/assets/Upload-CNwaGOws.js +0 -1
- package/dist/web/assets/headers-cf-QapUq.js +0 -1
- package/dist/web/assets/index-BMAUPq5K.js +0 -2
- package/dist/web/assets/platforms-CBkB45JP.js +0 -1
- package/dist/web/assets/platforms-FTpjxAT6.js +0 -1
- package/dist/web/assets/sessions-Cmy31slD.js +0 -1
- package/dist/web/assets/sessions-yC_oVVh8.js +0 -1
- package/dist/web/assets/use-message-DoLM8C1C.js +0 -1
|
@@ -339,11 +339,67 @@ function parseSession(filePath) {
|
|
|
339
339
|
};
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
+
/**
|
|
343
|
+
* Read only the beginning of a rollout for list views. Codex rollouts can be
|
|
344
|
+
* hundreds of megabytes; this helper must never call readJSONL/parseSession.
|
|
345
|
+
*/
|
|
346
|
+
function parseSessionSummary(filePath, { sessionId = null, mtimeMs = null } = {}) {
|
|
347
|
+
let fd;
|
|
348
|
+
try {
|
|
349
|
+
fd = fs.openSync(filePath, 'r');
|
|
350
|
+
const buffer = Buffer.alloc(64 * 1024);
|
|
351
|
+
const bytesRead = fs.readSync(fd, buffer, 0, buffer.length, 0);
|
|
352
|
+
const lines = buffer.subarray(0, bytesRead).toString('utf8')
|
|
353
|
+
.split(/\r?\n/)
|
|
354
|
+
.filter(line => line.trim())
|
|
355
|
+
.map(line => {
|
|
356
|
+
try { return JSON.parse(line); } catch (_) { return null; }
|
|
357
|
+
})
|
|
358
|
+
.filter(Boolean);
|
|
359
|
+
const meta = extractSessionMeta(lines);
|
|
360
|
+
if (!meta) return null;
|
|
361
|
+
const firstUser = lines.find(line => {
|
|
362
|
+
const payload = line.type === 'response_item' ? line.payload : null;
|
|
363
|
+
return payload?.type === 'message' && payload.role === 'user' && Array.isArray(payload.content);
|
|
364
|
+
});
|
|
365
|
+
const content = firstUser?.payload?.content
|
|
366
|
+
?.map(block => block?.text || block?.input_text || '')
|
|
367
|
+
.join('\n')
|
|
368
|
+
.trim() || null;
|
|
369
|
+
const nativeSessionId = typeof meta.sessionId === 'string' && meta.sessionId.trim()
|
|
370
|
+
? meta.sessionId.trim()
|
|
371
|
+
: sessionId;
|
|
372
|
+
const projectName = extractCodexProjectName(meta);
|
|
373
|
+
return {
|
|
374
|
+
session: {
|
|
375
|
+
sessionId: nativeSessionId,
|
|
376
|
+
projectName,
|
|
377
|
+
projectDisplayName: projectName,
|
|
378
|
+
projectFullPath: typeof meta.cwd === 'string' && meta.cwd.trim() ? meta.cwd.trim() : null,
|
|
379
|
+
firstMessage: content,
|
|
380
|
+
gitBranch: meta.git?.branch || null,
|
|
381
|
+
provider: meta.provider || null,
|
|
382
|
+
model: null,
|
|
383
|
+
startedAt: meta.timestamp ? new Date(meta.timestamp).getTime() : null,
|
|
384
|
+
updatedAt: mtimeMs,
|
|
385
|
+
extraJson: JSON.stringify({ cwd: meta.cwd || null })
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
} catch (_) {
|
|
389
|
+
return null;
|
|
390
|
+
} finally {
|
|
391
|
+
if (fd !== undefined) {
|
|
392
|
+
try { fs.closeSync(fd); } catch (_) {}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
342
397
|
module.exports = {
|
|
343
398
|
readJSONL,
|
|
344
399
|
extractSessionMeta,
|
|
345
400
|
extractMessages,
|
|
346
401
|
extractTokenUsage,
|
|
347
402
|
parseSession,
|
|
348
|
-
parseSessionMeta
|
|
403
|
+
parseSessionMeta,
|
|
404
|
+
parseSessionSummary
|
|
349
405
|
};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { getCodexDir } = require('./config');
|
|
6
|
-
const { parseSession } = require('./parser');
|
|
6
|
+
const { parseSession, parseSessionSummary } = require('./parser');
|
|
7
7
|
|
|
8
8
|
function configure({ pathContext } = {}) {
|
|
9
9
|
require('./config').configure?.({ pathContext });
|
|
@@ -90,6 +90,12 @@ async function inventory({ projectsDir } = {}) {
|
|
|
90
90
|
}));
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
async function summarize(descriptor) {
|
|
94
|
+
const parsed = parseSessionSummary(descriptor.filePath, descriptor);
|
|
95
|
+
if (!parsed?.session) return { sessionId: descriptor.sessionId, projectName: descriptor.projectHint || 'unknown' };
|
|
96
|
+
return parsed;
|
|
97
|
+
}
|
|
98
|
+
|
|
93
99
|
/**
|
|
94
100
|
* Parse a Codex session file.
|
|
95
101
|
*/
|
|
@@ -169,4 +175,4 @@ async function parse(descriptor) {
|
|
|
169
175
|
return { session, messages: normalizedMessages };
|
|
170
176
|
}
|
|
171
177
|
|
|
172
|
-
module.exports = { configure, inventory, parse, extractCodexProjectName };
|
|
178
|
+
module.exports = { configure, inventory, summarize, parse, extractCodexProjectName };
|
|
@@ -250,7 +250,7 @@ function normalizeSession(codexSession) {
|
|
|
250
250
|
*/
|
|
251
251
|
async function getProjects(options = {}) {
|
|
252
252
|
if (!fs.existsSync(getSessionsDir())) return [];
|
|
253
|
-
const projects = await getSessionHistoryIndex({ ...options, projectsDir: getSessionsDir() }).
|
|
253
|
+
const projects = await getSessionHistoryIndex({ ...options, projectsDir: getSessionsDir() }).getAllProjects('codex', options);
|
|
254
254
|
const savedOrder = getProjectOrder();
|
|
255
255
|
if (savedOrder.length === 0) return projects;
|
|
256
256
|
|
|
@@ -260,6 +260,11 @@ async function getProjects(options = {}) {
|
|
|
260
260
|
return [...ordered, ...byName.values()];
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
+
async function getProjectsPage(options = {}) {
|
|
264
|
+
return getSessionHistoryIndex({ ...options, projectsDir: getSessionsDir() })
|
|
265
|
+
.listProjectsPage('codex', options);
|
|
266
|
+
}
|
|
267
|
+
|
|
263
268
|
/**
|
|
264
269
|
* 根据项目名获取会话列表(归一化格式)
|
|
265
270
|
* @param {string} projectName - 项目名称
|
|
@@ -284,6 +289,21 @@ async function getSessionsByProject(projectName, options = {}) {
|
|
|
284
289
|
return [...byId.values(), ...ordered];
|
|
285
290
|
}
|
|
286
291
|
|
|
292
|
+
async function getSessionsPage(projectName, options = {}) {
|
|
293
|
+
const payload = await getSessionHistoryIndex().listSessionsPage('codex', projectName, options);
|
|
294
|
+
const forkRelations = require('../claude/sessions-implementation').getForkRelations();
|
|
295
|
+
const aliases = require('../../../server/services/alias').loadAliases();
|
|
296
|
+
return {
|
|
297
|
+
...payload,
|
|
298
|
+
sessions: payload.sessions.map(session => ({
|
|
299
|
+
...session,
|
|
300
|
+
mtime: new Date(session.mtime).toISOString(),
|
|
301
|
+
forkedFrom: forkRelations[session.sessionId] || null,
|
|
302
|
+
alias: aliases[session.sessionId] || null
|
|
303
|
+
}))
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
287
307
|
/**
|
|
288
308
|
* 根据 sessionId 获取会话(归一化格式)
|
|
289
309
|
* @param {string} sessionId - 会话 ID
|
|
@@ -302,8 +322,8 @@ async function getSessionById(sessionId) {
|
|
|
302
322
|
* @param {string} keyword - 搜索关键词
|
|
303
323
|
* @returns {Array} 搜索结果
|
|
304
324
|
*/
|
|
305
|
-
async function searchSessions(keyword) {
|
|
306
|
-
return getSessionHistoryIndex().searchSessions('codex', keyword);
|
|
325
|
+
async function searchSessions(keyword, options = {}) {
|
|
326
|
+
return getSessionHistoryIndex().searchSessions('codex', keyword, options);
|
|
307
327
|
}
|
|
308
328
|
|
|
309
329
|
/**
|
|
@@ -825,7 +845,9 @@ module.exports = {
|
|
|
825
845
|
scanSessionFiles,
|
|
826
846
|
getAllSessions,
|
|
827
847
|
getProjects,
|
|
848
|
+
getProjectsPage,
|
|
828
849
|
getSessionsByProject,
|
|
850
|
+
getSessionsPage,
|
|
829
851
|
getSessionById,
|
|
830
852
|
searchSessions,
|
|
831
853
|
normalizeSession,
|
|
@@ -11,9 +11,10 @@ function createDriver(context = {}) {
|
|
|
11
11
|
servicePath: './codex/sessions-implementation',
|
|
12
12
|
localServicePath: '../platforms/drivers/codex/sessions-implementation',
|
|
13
13
|
adapterLocalPath: '../platforms/drivers/codex/session-history-adapter',
|
|
14
|
-
adapterMethods: { inventory: 'inventory', parse: 'parse' },
|
|
14
|
+
adapterMethods: { inventory: 'inventory', summarize: 'summarize', parse: 'parse' },
|
|
15
15
|
methods: {
|
|
16
16
|
listSessions: 'getSessionsByProject',
|
|
17
|
+
listSessionsPage: 'getSessionsPage',
|
|
17
18
|
recent: 'getRecentSessions',
|
|
18
19
|
search: 'searchSessions',
|
|
19
20
|
getSessionById: 'getSessionById',
|
|
@@ -25,6 +26,22 @@ function createDriver(context = {}) {
|
|
|
25
26
|
status: 'getSessionStatus',
|
|
26
27
|
messages: 'getSessionMessages'
|
|
27
28
|
},
|
|
29
|
+
customMethods: {
|
|
30
|
+
searchAcrossProjects: (service, requestOrKeyword, limit, options = {}) => {
|
|
31
|
+
if (requestOrKeyword && typeof requestOrKeyword === 'object' && !Array.isArray(requestOrKeyword)) {
|
|
32
|
+
const request = requestOrKeyword;
|
|
33
|
+
const query = request.query || {};
|
|
34
|
+
return service.searchSessions(query.keyword || query.q || '', {
|
|
35
|
+
...request,
|
|
36
|
+
...options,
|
|
37
|
+
projectName: query.projectName || query.project || null,
|
|
38
|
+
limit: Number.parseInt(query.limit, 10) || 35,
|
|
39
|
+
contextLength: Number(query.context) || 35
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return service.searchSessions(requestOrKeyword, { ...options, limit });
|
|
43
|
+
}
|
|
44
|
+
},
|
|
28
45
|
onSuccess: operation => {
|
|
29
46
|
if (['delete', 'fork', 'saveSessionOrder'].includes(operation)) {
|
|
30
47
|
context.sessionHistoryIndex?.invalidateSource('codex');
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { createDriver: createNativeConfigDriver } = require('./native-config');
|
|
4
|
+
const { createDriver: createProjectsDriver } = require('./projects');
|
|
5
|
+
const { createDriver: createSessionsDriver } = require('./sessions');
|
|
6
|
+
const { listProfiles, listProfilePlugins, listProfileCapabilities, listProfileMcp, listProfilePrompts, listPlugins, upsertProfilePatchRow, deleteProfilePatchRow } = require('./common');
|
|
7
|
+
const { listSkills, getSkill, createSkill, updateSkill, deleteSkill } = require('./resources');
|
|
8
|
+
const { installPlugin, uninstallPlugin, updatePlugin } = require('./plugin-manager');
|
|
9
|
+
const { createDriver: createChannelsDriver } = require('./channels');
|
|
10
|
+
const { createDriver: createProxyDriver } = require('./proxy');
|
|
11
|
+
const { createDriver: createStatisticsDriver } = require('./statistics');
|
|
12
|
+
|
|
13
|
+
function requiredId(value, label) {
|
|
14
|
+
const id = String(value || '').trim();
|
|
15
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/.test(id)) throw new Error(`${label} must be a safe identifier`);
|
|
16
|
+
return id;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function profilePatchOptions(request = {}) {
|
|
20
|
+
const body = request.body || {};
|
|
21
|
+
return { expectedRevision: body.expectedRevision };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function mcpConfigFromRequest(request = {}) {
|
|
25
|
+
const body = request.body || {};
|
|
26
|
+
const source = body.config && typeof body.config === 'object' && !Array.isArray(body.config) ? body.config : body;
|
|
27
|
+
const config = { ...source };
|
|
28
|
+
delete config.id;
|
|
29
|
+
delete config.name;
|
|
30
|
+
delete config.profile;
|
|
31
|
+
delete config.expectedRevision;
|
|
32
|
+
delete config.enabled;
|
|
33
|
+
delete config.disabled;
|
|
34
|
+
delete config.scope;
|
|
35
|
+
delete config.cwd;
|
|
36
|
+
if (!['stdio', 'streamable-http'].includes(config.transport)) throw new Error('MCP transport must be stdio or streamable-http');
|
|
37
|
+
if (!/^[A-Za-z0-9_-]{1,32}$/.test(String(config.serverName || '').trim())) throw new Error('MCP serverName must be 1-32 ASCII letters, digits, underscores, or hyphens');
|
|
38
|
+
return config;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function promptConfigFromRequest(request = {}) {
|
|
42
|
+
const body = request.body || {};
|
|
43
|
+
const source = body.config && typeof body.config === 'object' && !Array.isArray(body.config) ? body.config : body;
|
|
44
|
+
const config = { ...source };
|
|
45
|
+
delete config.id;
|
|
46
|
+
delete config.name;
|
|
47
|
+
delete config.profile;
|
|
48
|
+
delete config.expectedRevision;
|
|
49
|
+
delete config.enabled;
|
|
50
|
+
delete config.disabled;
|
|
51
|
+
delete config.scope;
|
|
52
|
+
delete config.cwd;
|
|
53
|
+
return config;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function patchResult(context, request, profileName, operation, patch) {
|
|
57
|
+
return {
|
|
58
|
+
profile: profileName,
|
|
59
|
+
operation,
|
|
60
|
+
patch,
|
|
61
|
+
mcp: listProfileMcp(context, profileName),
|
|
62
|
+
prompts: listProfilePrompts(context, profileName)
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function requirePatchResult(patch, profileName) {
|
|
67
|
+
if (patch) return patch;
|
|
68
|
+
const error = new Error(`DSH profile not found: ${profileName}`);
|
|
69
|
+
error.statusCode = 404;
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function resultFor(context = {}, status, data, error, cause) {
|
|
74
|
+
const route = context.route || {};
|
|
75
|
+
const result = {
|
|
76
|
+
status,
|
|
77
|
+
platform: context.platform || 'dsh',
|
|
78
|
+
capability: route.capability || context.capability || 'api',
|
|
79
|
+
operation: route.operation || context.operation
|
|
80
|
+
};
|
|
81
|
+
if (status === 'ok') result.data = data;
|
|
82
|
+
if (error) result.error = error instanceof Error ? error.message : String(error);
|
|
83
|
+
if (cause) Object.defineProperty(result, 'cause', { value: cause, enumerable: false });
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function resolveProfileName(context = {}, requested = '') {
|
|
88
|
+
const profiles = listProfiles(context);
|
|
89
|
+
const requestedName = String(requested || '').trim();
|
|
90
|
+
if (requestedName) return requestedName;
|
|
91
|
+
const envProfile = String(process.env.DSH_PROFILE || '').trim();
|
|
92
|
+
if (envProfile && profiles.some(profile => profile.name === envProfile)) return envProfile;
|
|
93
|
+
return profiles.find(profile => profile.name === 'default')?.name || profiles[0]?.name || null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function profileNameForRequest(context = {}, request = {}) {
|
|
97
|
+
return resolveProfileName(
|
|
98
|
+
context,
|
|
99
|
+
request.profile
|
|
100
|
+
|| request.query?.profile
|
|
101
|
+
|| request.body?.profile
|
|
102
|
+
|| request.params?.profileName
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function dshMcpConfigToSpec(config = {}) {
|
|
107
|
+
const transport = config.transport === 'streamable-http' ? 'streamable_http' : config.transport || 'stdio';
|
|
108
|
+
const spec = { type: transport };
|
|
109
|
+
if (transport === 'stdio') {
|
|
110
|
+
if (config.command) spec.command = config.command;
|
|
111
|
+
if (Array.isArray(config.args)) spec.args = [...config.args];
|
|
112
|
+
if (config.env && typeof config.env === 'object') spec.env = { ...config.env };
|
|
113
|
+
} else {
|
|
114
|
+
if (config.url) spec.url = config.url;
|
|
115
|
+
if (config.headers && typeof config.headers === 'object') spec.headers = { ...config.headers };
|
|
116
|
+
}
|
|
117
|
+
return spec;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function dshMcpSpecToConfig(spec = {}, serverName) {
|
|
121
|
+
const type = spec.type === 'streamable_http' ? 'streamable-http' : spec.type || 'stdio';
|
|
122
|
+
if (!['stdio', 'streamable-http'].includes(type)) {
|
|
123
|
+
throw new Error(`DSH MCP does not support transport: ${spec.type || type}`);
|
|
124
|
+
}
|
|
125
|
+
const config = { serverName, transport: type };
|
|
126
|
+
if (type === 'stdio') {
|
|
127
|
+
if (spec.command) config.command = spec.command;
|
|
128
|
+
if (Array.isArray(spec.args)) config.args = [...spec.args];
|
|
129
|
+
if (spec.env && typeof spec.env === 'object') config.env = { ...spec.env };
|
|
130
|
+
} else {
|
|
131
|
+
if (spec.url) config.url = spec.url;
|
|
132
|
+
if (spec.headers && typeof spec.headers === 'object') config.headers = { ...spec.headers };
|
|
133
|
+
}
|
|
134
|
+
return config;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function readDshMcpEntries(context = {}) {
|
|
138
|
+
const profile = resolveProfileName(context);
|
|
139
|
+
if (!profile) return {};
|
|
140
|
+
const result = listProfileMcp(context, profile);
|
|
141
|
+
const entries = {};
|
|
142
|
+
for (const server of result?.servers || []) {
|
|
143
|
+
if (!server?.id || server.disabled) continue;
|
|
144
|
+
entries[server.id] = dshMcpConfigToSpec(server.config || {});
|
|
145
|
+
}
|
|
146
|
+
return entries;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function promptContentFromDshConfig(config = {}) {
|
|
150
|
+
for (const key of ['content', 'prompt', 'text']) {
|
|
151
|
+
if (typeof config[key] === 'string') return config[key];
|
|
152
|
+
}
|
|
153
|
+
return [config.personaPrefix, config.personaSuffix]
|
|
154
|
+
.filter(value => typeof value === 'string' && value.trim())
|
|
155
|
+
.join('\n')
|
|
156
|
+
.trim();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function readDshPrompt(context = {}) {
|
|
160
|
+
const profile = resolveProfileName(context);
|
|
161
|
+
if (!profile) return '';
|
|
162
|
+
const result = listProfilePrompts(context, profile);
|
|
163
|
+
const prompt = [...(result?.prompts || [])]
|
|
164
|
+
.filter(entry => entry && entry.disabled !== true)
|
|
165
|
+
.pop();
|
|
166
|
+
return promptContentFromDshConfig(prompt?.config || {});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function dshCapabilityContext(context, capability, operation) {
|
|
170
|
+
return { ...context, capability, operation };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function createDriver(context = {}) {
|
|
174
|
+
const native = createNativeConfigDriver(context);
|
|
175
|
+
const projects = createProjectsDriver(context);
|
|
176
|
+
const sessions = createSessionsDriver(context);
|
|
177
|
+
const channels = createChannelsDriver(context);
|
|
178
|
+
const proxy = createProxyDriver(context);
|
|
179
|
+
const statistics = createStatisticsDriver(context);
|
|
180
|
+
const driver = {
|
|
181
|
+
...projects,
|
|
182
|
+
...sessions,
|
|
183
|
+
platform: 'dsh',
|
|
184
|
+
capability: context.capability || 'api'
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
// Keep the aggregate DSH API driver compatible with the manifest consistency
|
|
188
|
+
// contract. Actual descriptor requests are still dispatched by capability.
|
|
189
|
+
for (const operation of ['list', 'current', 'enabled', 'create', 'update', 'remove', 'applyToSettings', 'sync', 'order', 'models', 'probeModels', 'speedTest', 'speedTestAll']) {
|
|
190
|
+
driver[operation] = (...args) => channels[operation](...args);
|
|
191
|
+
}
|
|
192
|
+
for (const operation of ['status', 'start', 'stop']) {
|
|
193
|
+
driver[operation] = (...args) => proxy[operation](...args);
|
|
194
|
+
}
|
|
195
|
+
for (const operation of ['summary', 'today', 'daily']) {
|
|
196
|
+
driver[operation] = (...args) => statistics[operation](...args);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
driver.getConfig = async request => resultFor(request, 'ok', native.getConfig());
|
|
200
|
+
driver.updateConfig = async request => {
|
|
201
|
+
try {
|
|
202
|
+
return resultFor(request, 'ok', native.updateConfig(request));
|
|
203
|
+
} catch (error) {
|
|
204
|
+
return resultFor(request, error.statusCode === 409 ? 'conflict' : 'failed', undefined, error, error);
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
driver.getConfigCapabilities = async request => resultFor(request, 'ok', native.getConfigCapabilities());
|
|
208
|
+
driver.getConfigAuthProviders = async request => resultFor(request, 'ok', native.getConfigAuthProviders());
|
|
209
|
+
driver.getConfigResources = async request => resultFor(request, 'ok', native.getConfigResources(request));
|
|
210
|
+
driver.listProfiles = async request => resultFor(request, 'ok', { profiles: listProfiles(context) });
|
|
211
|
+
driver.listPlugins = request => resultFor(request, 'ok', listPlugins(context));
|
|
212
|
+
driver.listProfilePlugins = async (request = {}) => {
|
|
213
|
+
const profile = request.params?.profileName;
|
|
214
|
+
const result = listProfilePlugins(context, profile);
|
|
215
|
+
return result ? resultFor(request, 'ok', result) : resultFor(request, 'unsupported');
|
|
216
|
+
};
|
|
217
|
+
driver.listProfileCapabilities = async (request = {}) => {
|
|
218
|
+
const profile = request.params?.profileName;
|
|
219
|
+
const result = listProfileCapabilities(context, profile);
|
|
220
|
+
return result ? resultFor(request, 'ok', result) : resultFor(request, 'unsupported');
|
|
221
|
+
};
|
|
222
|
+
driver.listProfileMcp = async (request = {}) => {
|
|
223
|
+
const profile = request.params?.profileName;
|
|
224
|
+
const result = listProfileMcp(context, profile);
|
|
225
|
+
return result ? resultFor(request, 'ok', result) : resultFor(request, 'unsupported');
|
|
226
|
+
};
|
|
227
|
+
driver.listProfilePrompts = async (request = {}) => {
|
|
228
|
+
const profile = request.params?.profileName;
|
|
229
|
+
const result = listProfilePrompts(context, profile);
|
|
230
|
+
return result ? resultFor(request, 'ok', result) : resultFor(request, 'unsupported');
|
|
231
|
+
};
|
|
232
|
+
driver.installPlugin = async (request = {}) => {
|
|
233
|
+
const result = await installPlugin(context, request);
|
|
234
|
+
return resultFor(request, 'ok', result);
|
|
235
|
+
};
|
|
236
|
+
driver.uninstallPlugin = async (request = {}) => {
|
|
237
|
+
const result = await uninstallPlugin(context, request);
|
|
238
|
+
return resultFor(request, 'ok', result);
|
|
239
|
+
};
|
|
240
|
+
driver.updatePlugin = async (request = {}) => {
|
|
241
|
+
const result = await updatePlugin(context, request);
|
|
242
|
+
return resultFor(request, 'ok', result);
|
|
243
|
+
};
|
|
244
|
+
driver.upsertMcp = async (request = {}) => {
|
|
245
|
+
const profileName = request.params?.profileName;
|
|
246
|
+
const body = request.body || {};
|
|
247
|
+
const config = mcpConfigFromRequest(request);
|
|
248
|
+
const serverName = String(config.serverName).trim();
|
|
249
|
+
const id = requiredId(request.params?.serverId || body.id || `mcp-${serverName}`, 'MCP id');
|
|
250
|
+
const entry = {
|
|
251
|
+
id,
|
|
252
|
+
name: '@deepseek-ai/dsh-mcp-client',
|
|
253
|
+
...(body.disabled === true || body.enabled === false ? { disabled: true } : {}),
|
|
254
|
+
config
|
|
255
|
+
};
|
|
256
|
+
const patch = requirePatchResult(
|
|
257
|
+
upsertProfilePatchRow(context, profileName, entry, profilePatchOptions(request)),
|
|
258
|
+
profileName
|
|
259
|
+
);
|
|
260
|
+
return resultFor(request, 'ok', patchResult(context, request, profileName, 'upsertMcp', patch));
|
|
261
|
+
};
|
|
262
|
+
driver.deleteMcp = async (request = {}) => {
|
|
263
|
+
const profileName = request.params?.profileName;
|
|
264
|
+
const body = request.body || {};
|
|
265
|
+
const id = requiredId(request.params?.serverId || body.id, 'MCP id');
|
|
266
|
+
const patch = requirePatchResult(
|
|
267
|
+
deleteProfilePatchRow(context, profileName, id, profilePatchOptions(request)),
|
|
268
|
+
profileName
|
|
269
|
+
);
|
|
270
|
+
return resultFor(request, 'ok', patchResult(context, request, profileName, 'deleteMcp', patch));
|
|
271
|
+
};
|
|
272
|
+
driver.upsertPrompt = async (request = {}) => {
|
|
273
|
+
const profileName = request.params?.profileName;
|
|
274
|
+
const body = request.body || {};
|
|
275
|
+
const id = requiredId(request.params?.promptId || body.id, 'prompt id');
|
|
276
|
+
const patch = requirePatchResult(
|
|
277
|
+
upsertProfilePatchRow(context, profileName, {
|
|
278
|
+
id,
|
|
279
|
+
name: '@deepseek-ai/dsh-system-prompt',
|
|
280
|
+
...(body.disabled === true || body.enabled === false ? { disabled: true } : {}),
|
|
281
|
+
config: promptConfigFromRequest(request)
|
|
282
|
+
}, profilePatchOptions(request)),
|
|
283
|
+
profileName
|
|
284
|
+
);
|
|
285
|
+
return resultFor(request, 'ok', patchResult(context, request, profileName, 'upsertPrompt', patch));
|
|
286
|
+
};
|
|
287
|
+
driver.deletePrompt = async (request = {}) => {
|
|
288
|
+
const profileName = request.params?.profileName;
|
|
289
|
+
const body = request.body || {};
|
|
290
|
+
const id = requiredId(request.params?.promptId || body.id, 'prompt id');
|
|
291
|
+
const patch = requirePatchResult(
|
|
292
|
+
deleteProfilePatchRow(context, profileName, id, profilePatchOptions(request)),
|
|
293
|
+
profileName
|
|
294
|
+
);
|
|
295
|
+
return resultFor(request, 'ok', patchResult(context, request, profileName, 'deletePrompt', patch));
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
if (context.capability === 'mcp') {
|
|
299
|
+
const mcpContext = operation => dshCapabilityContext(context, 'mcp', operation);
|
|
300
|
+
driver.entries = () => resultFor(mcpContext('entries'), 'ok', readDshMcpEntries(context));
|
|
301
|
+
driver.read = () => resultFor(mcpContext('read'), 'ok', { mcpServers: readDshMcpEntries(context) });
|
|
302
|
+
driver.normalize = spec => resultFor(mcpContext('normalize'), 'ok', spec);
|
|
303
|
+
driver.sync = async server => {
|
|
304
|
+
const profileName = profileNameForRequest(context, server);
|
|
305
|
+
const id = String(server?.id || '').trim();
|
|
306
|
+
if (!id) throw new Error('MCP server id is required');
|
|
307
|
+
const patch = requirePatchResult(
|
|
308
|
+
upsertProfilePatchRow(context, profileName, {
|
|
309
|
+
id,
|
|
310
|
+
name: '@deepseek-ai/dsh-mcp-client',
|
|
311
|
+
config: dshMcpSpecToConfig(server.server || {}, id)
|
|
312
|
+
}),
|
|
313
|
+
profileName
|
|
314
|
+
);
|
|
315
|
+
return resultFor(mcpContext('sync'), 'ok', patch);
|
|
316
|
+
};
|
|
317
|
+
driver.remove = async serverId => {
|
|
318
|
+
const profileName = profileNameForRequest(context);
|
|
319
|
+
const id = String(serverId || '').trim();
|
|
320
|
+
if (!id) throw new Error('MCP server id is required');
|
|
321
|
+
if (!profileName) return resultFor(mcpContext('remove'), 'ok', true);
|
|
322
|
+
const patch = requirePatchResult(deleteProfilePatchRow(context, profileName, id), profileName);
|
|
323
|
+
return resultFor(mcpContext('remove'), 'ok', patch);
|
|
324
|
+
};
|
|
325
|
+
driver.import = async servers => {
|
|
326
|
+
const entries = readDshMcpEntries(context);
|
|
327
|
+
let count = 0;
|
|
328
|
+
for (const [id, spec] of Object.entries(entries)) {
|
|
329
|
+
if (servers[id]) {
|
|
330
|
+
servers[id].apps = { ...(servers[id].apps || {}), dsh: true };
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
const now = Date.now();
|
|
334
|
+
servers[id] = {
|
|
335
|
+
id,
|
|
336
|
+
name: id,
|
|
337
|
+
server: spec,
|
|
338
|
+
apps: { dsh: true },
|
|
339
|
+
createdAt: now,
|
|
340
|
+
updatedAt: now
|
|
341
|
+
};
|
|
342
|
+
count++;
|
|
343
|
+
}
|
|
344
|
+
return resultFor(mcpContext('import'), 'ok', count);
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (context.capability === 'prompts') {
|
|
349
|
+
const promptContext = operation => dshCapabilityContext(context, 'prompts', operation);
|
|
350
|
+
driver.read = () => resultFor(promptContext('read'), 'ok', readDshPrompt(context));
|
|
351
|
+
driver.write = async content => {
|
|
352
|
+
const profileName = profileNameForRequest(context);
|
|
353
|
+
if (!profileName) throw new Error('No DSH profile is available');
|
|
354
|
+
if (typeof content !== 'string') throw new Error('Prompt text must be a string');
|
|
355
|
+
const patch = requirePatchResult(
|
|
356
|
+
upsertProfilePatchRow(context, profileName, {
|
|
357
|
+
id: 'system-prompt',
|
|
358
|
+
name: '@deepseek-ai/dsh-system-prompt',
|
|
359
|
+
config: { personaPrefix: content }
|
|
360
|
+
}),
|
|
361
|
+
profileName
|
|
362
|
+
);
|
|
363
|
+
return resultFor(promptContext('write'), 'ok', patch);
|
|
364
|
+
};
|
|
365
|
+
driver.remove = async () => {
|
|
366
|
+
const profileName = profileNameForRequest(context);
|
|
367
|
+
if (!profileName) return resultFor(promptContext('remove'), 'ok', true);
|
|
368
|
+
const patch = requirePatchResult(
|
|
369
|
+
deleteProfilePatchRow(context, profileName, 'system-prompt'),
|
|
370
|
+
profileName
|
|
371
|
+
);
|
|
372
|
+
return resultFor(promptContext('remove'), 'ok', patch);
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
driver.listSkills = async (request = {}) => resultFor(request, 'ok', listSkills(context, {
|
|
377
|
+
profile: request.query?.profile,
|
|
378
|
+
cwd: request.query?.cwd,
|
|
379
|
+
scope: request.query?.scope,
|
|
380
|
+
includeContent: request.query?.includeContent === '1'
|
|
381
|
+
}));
|
|
382
|
+
driver.getSkill = async (request = {}) => {
|
|
383
|
+
const result = getSkill(context, request.query?.profile, request.params?.skillName, {
|
|
384
|
+
cwd: request.query?.cwd,
|
|
385
|
+
scope: request.query?.scope
|
|
386
|
+
});
|
|
387
|
+
return result ? resultFor(request, 'ok', result) : resultFor(request, 'unsupported');
|
|
388
|
+
};
|
|
389
|
+
driver.createSkill = async (request = {}) => resultFor(request, 'ok', createSkill(context, request));
|
|
390
|
+
driver.updateSkill = async (request = {}) => resultFor(request, 'ok', updateSkill(context, request));
|
|
391
|
+
driver.deleteSkill = async (request = {}) => resultFor(request, 'ok', deleteSkill(context, request));
|
|
392
|
+
|
|
393
|
+
return driver;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
module.exports = { createDriver };
|