coding-tool-x 3.6.13 → 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -33
- package/dist/web/assets/{Analytics-CnA75GxO.js → Analytics-Dma_j2Rq.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-D8XEscQY.js → ConfigTemplates-C49Y1PLH.js} +1 -1
- package/dist/web/assets/Home-DGGC8H4g.js +1 -0
- package/dist/web/assets/Home-ewgeIhF7.css +1 -0
- package/dist/web/assets/{PluginManager-BzKZ8TYu.js → PluginManager-B1ECTHyY.js} +1 -1
- package/dist/web/assets/{ProjectList-DL4JK6ci.css → ProjectList-B_L55-l9.css} +1 -1
- package/dist/web/assets/ProjectList-DyiGyT-H.js +1 -0
- package/dist/web/assets/SessionList-CH5GwwiJ.js +1 -0
- package/dist/web/assets/{SkillManager-fxOvFTYD.js → SkillManager-CRyVhjYQ.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-Ch2mM8jm.js → WorkspaceManager-CO9tclVt.js} +1 -1
- package/dist/web/assets/{icons-QFXzEgqM.js → icons-Dop5j7Bf.js} +1 -1
- package/dist/web/assets/index-CM26UrnJ.css +1 -0
- package/dist/web/assets/index-EQq56pRa.js +2 -0
- package/dist/web/assets/{naive-ui-kcWDeJwl.js → naive-ui-CVyvdmg1.js} +1 -1
- package/dist/web/index.html +4 -4
- package/package.json +2 -1
- package/src/commands/channels.js +85 -24
- package/src/commands/daemon.js +7 -4
- package/src/commands/doctor.js +17 -8
- package/src/commands/logs.js +48 -6
- package/src/commands/port-config.js +83 -75
- package/src/commands/proxy-control.js +52 -19
- package/src/commands/stats.js +37 -19
- package/src/commands/switch.js +5 -1
- package/src/commands/toggle-proxy.js +131 -53
- package/src/commands/ui.js +3 -4
- package/src/commands/workspace.js +3 -35
- package/src/config/default.js +19 -2
- package/src/config/loader.js +5 -76
- package/src/config/paths.js +99 -11
- package/src/index.js +11 -16
- package/src/server/api/commands.js +1 -1
- package/src/server/api/config-export.js +4 -2
- package/src/server/api/config-registry.js +40 -34
- package/src/server/api/config.js +5 -1
- package/src/server/api/dashboard.js +22 -6
- package/src/server/api/mcp.js +5 -3
- package/src/server/api/pi-channels.js +349 -0
- package/src/server/api/pi-config.js +164 -0
- package/src/server/api/pi-projects.js +76 -0
- package/src/server/api/pi-proxy.js +154 -0
- package/src/server/api/pi-sessions.js +334 -0
- package/src/server/api/pi-statistics.js +40 -0
- package/src/server/api/plugins.js +1 -1
- package/src/server/api/prompts.js +3 -2
- package/src/server/api/settings.js +2 -0
- package/src/server/api/skills.js +1 -1
- package/src/server/api/workspaces.js +3 -41
- package/src/server/index.js +41 -0
- package/src/server/pi-proxy-server.js +66 -0
- package/src/server/services/agents-service.js +2 -1
- package/src/server/services/base/proxy-utils.js +1 -0
- package/src/server/services/channel-balance.js +151 -1
- package/src/server/services/channel-scheduler.js +7 -1
- package/src/server/services/commands-service.js +11 -2
- package/src/server/services/config-export-service.js +275 -18
- package/src/server/services/config-registry-service.js +20 -18
- package/src/server/services/config-sync-manager.js +115 -6
- package/src/server/services/config-sync-service.js +4 -2
- package/src/server/services/config-templates-service.js +183 -45
- package/src/server/services/mcp-service.js +217 -33
- package/src/server/services/pi-auth-providers.js +294 -0
- package/src/server/services/pi-channels.js +94 -0
- package/src/server/services/pi-config.js +287 -0
- package/src/server/services/pi-sessions.js +490 -0
- package/src/server/services/pi-settings-manager.js +571 -0
- package/src/server/services/pi-statistics-service.js +317 -0
- package/src/server/services/plugins-service.js +673 -11
- package/src/server/services/prompts-service.js +112 -7
- package/src/server/services/proxy-log-helper.js +1 -0
- package/src/server/services/proxy-runtime.js +5 -1
- package/src/server/services/sessions.js +19 -35
- package/src/server/services/skill-service.js +13 -3
- package/src/server/services/speed-test.js +9 -3
- package/src/server/services/ui-config.js +50 -12
- package/src/server/services/workspace-service.js +117 -225
- package/src/server/websocket-server.js +7 -1
- package/src/utils/session.js +4 -10
- package/dist/web/assets/Home-BiaKZMah.js +0 -1
- package/dist/web/assets/Home-CIua-b-o.css +0 -1
- package/dist/web/assets/ProjectList-BPfG_92X.js +0 -1
- package/dist/web/assets/SessionList-BIVtw00v.js +0 -1
- package/dist/web/assets/index-BpBFF3b2.js +0 -2
- package/dist/web/assets/index-Dsh4Q5Cg.css +0 -1
- package/src/utils/cli-flags.js +0 -7
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const router = express.Router();
|
|
5
|
+
const {
|
|
6
|
+
startPiProxyServer,
|
|
7
|
+
stopPiProxyServer,
|
|
8
|
+
getPiProxyStatus
|
|
9
|
+
} = require('../pi-proxy-server');
|
|
10
|
+
const {
|
|
11
|
+
getChannels,
|
|
12
|
+
getEnabledChannels,
|
|
13
|
+
markChannelAsRecentlyUsed
|
|
14
|
+
} = require('../services/pi-channels');
|
|
15
|
+
const { getSchedulerState } = require('../services/channel-scheduler');
|
|
16
|
+
const { PATHS, ensureStorageDirMigrated } = require('../../config/paths');
|
|
17
|
+
|
|
18
|
+
function sanitizeChannel(channel) {
|
|
19
|
+
if (!channel) return null;
|
|
20
|
+
return {
|
|
21
|
+
id: channel.id,
|
|
22
|
+
name: channel.name,
|
|
23
|
+
baseUrl: channel.baseUrl,
|
|
24
|
+
websiteUrl: channel.websiteUrl
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function selectLatestEnabledChannel(channels) {
|
|
29
|
+
const enabledChannels = (channels || []).filter(ch => ch.enabled !== false);
|
|
30
|
+
if (enabledChannels.length === 0) return null;
|
|
31
|
+
return enabledChannels.reduce((latest, current) => {
|
|
32
|
+
const latestTs = Number(latest?.updatedAt || latest?.createdAt || 0);
|
|
33
|
+
const currentTs = Number(current?.updatedAt || current?.createdAt || 0);
|
|
34
|
+
return currentTs > latestTs ? current : latest;
|
|
35
|
+
}, enabledChannels[0]);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function saveActiveChannelId(channelId) {
|
|
39
|
+
ensureStorageDirMigrated();
|
|
40
|
+
const filePath = PATHS.activeChannel.pi;
|
|
41
|
+
const dir = path.dirname(filePath);
|
|
42
|
+
if (!fs.existsSync(dir)) {
|
|
43
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
44
|
+
}
|
|
45
|
+
fs.writeFileSync(filePath, JSON.stringify({ activeChannelId: channelId }, null, 2), 'utf8');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function loadActiveChannelId() {
|
|
49
|
+
ensureStorageDirMigrated();
|
|
50
|
+
try {
|
|
51
|
+
if (!fs.existsSync(PATHS.activeChannel.pi)) return null;
|
|
52
|
+
const data = JSON.parse(fs.readFileSync(PATHS.activeChannel.pi, 'utf8'));
|
|
53
|
+
return data.activeChannelId || null;
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function removeActiveChannelFile() {
|
|
60
|
+
ensureStorageDirMigrated();
|
|
61
|
+
if (fs.existsSync(PATHS.activeChannel.pi)) {
|
|
62
|
+
fs.unlinkSync(PATHS.activeChannel.pi);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function resolveActiveChannel(channels, activeChannelId = null) {
|
|
67
|
+
if (!Array.isArray(channels) || channels.length === 0) return null;
|
|
68
|
+
if (activeChannelId) {
|
|
69
|
+
const matched = channels.find(channel => channel.id === activeChannelId);
|
|
70
|
+
if (matched) return matched;
|
|
71
|
+
}
|
|
72
|
+
return selectLatestEnabledChannel(channels)
|
|
73
|
+
|| channels.find(channel => channel.enabled !== false)
|
|
74
|
+
|| channels[0]
|
|
75
|
+
|| null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
router.get('/status', (req, res) => {
|
|
79
|
+
try {
|
|
80
|
+
const proxy = getPiProxyStatus();
|
|
81
|
+
const { channels } = getChannels();
|
|
82
|
+
const enabledChannels = channels.filter(ch => ch.enabled !== false);
|
|
83
|
+
const activeChannel = resolveActiveChannel(channels, loadActiveChannelId());
|
|
84
|
+
res.json({
|
|
85
|
+
proxy,
|
|
86
|
+
config: {
|
|
87
|
+
mode: 'models-yml-provider-config',
|
|
88
|
+
nativeProxyProtocol: false
|
|
89
|
+
},
|
|
90
|
+
activeChannel: sanitizeChannel(activeChannel),
|
|
91
|
+
enabledChannelsCount: enabledChannels.length,
|
|
92
|
+
totalChannelsCount: channels.length
|
|
93
|
+
});
|
|
94
|
+
} catch (error) {
|
|
95
|
+
res.status(500).json({ error: error.message });
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
router.post('/start', async (req, res) => {
|
|
100
|
+
try {
|
|
101
|
+
const enabledChannels = getEnabledChannels();
|
|
102
|
+
if (enabledChannels.length === 0) {
|
|
103
|
+
return res.status(400).json({
|
|
104
|
+
error: 'No enabled OMP channel found. Please create and enable a channel first.'
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let currentChannel = selectLatestEnabledChannel(enabledChannels) || enabledChannels[0];
|
|
109
|
+
currentChannel = markChannelAsRecentlyUsed(currentChannel.id);
|
|
110
|
+
saveActiveChannelId(currentChannel.id);
|
|
111
|
+
|
|
112
|
+
const proxyResult = await startPiProxyServer();
|
|
113
|
+
if (!proxyResult.success) {
|
|
114
|
+
return res.status(500).json({ error: 'Failed to enable OMP models.yml provider config' });
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const { broadcastProxyState, broadcastSchedulerState } = require('../websocket-server');
|
|
118
|
+
const updatedStatus = getPiProxyStatus();
|
|
119
|
+
const { channels: latestChannels } = getChannels();
|
|
120
|
+
const activeChannel = latestChannels.find(channel => channel.id === currentChannel.id) || currentChannel;
|
|
121
|
+
broadcastProxyState('pi', updatedStatus, activeChannel, latestChannels);
|
|
122
|
+
broadcastSchedulerState('pi', getSchedulerState('pi'));
|
|
123
|
+
|
|
124
|
+
res.json({
|
|
125
|
+
success: true,
|
|
126
|
+
port: proxyResult.port,
|
|
127
|
+
activeChannel: sanitizeChannel(activeChannel),
|
|
128
|
+
mode: 'models-yml-provider-config',
|
|
129
|
+
sync: proxyResult.sync || null,
|
|
130
|
+
warnings: proxyResult.warnings || [],
|
|
131
|
+
message: `OMP models.yml provider config enabled, active channel: ${activeChannel.name}`
|
|
132
|
+
});
|
|
133
|
+
} catch (error) {
|
|
134
|
+
console.error('[OMP Proxy] Start failed:', error);
|
|
135
|
+
res.status(500).json({ error: error.message });
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
router.post('/stop', async (req, res) => {
|
|
140
|
+
try {
|
|
141
|
+
const result = await stopPiProxyServer();
|
|
142
|
+
removeActiveChannelFile();
|
|
143
|
+
const { broadcastProxyState, broadcastSchedulerState } = require('../websocket-server');
|
|
144
|
+
const { channels } = getChannels();
|
|
145
|
+
broadcastProxyState('pi', getPiProxyStatus(), null, channels);
|
|
146
|
+
broadcastSchedulerState('pi', getSchedulerState('pi'));
|
|
147
|
+
res.json({ success: true, ...result });
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.error('[OMP Proxy] Stop failed:', error);
|
|
150
|
+
res.status(500).json({ error: error.message });
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
module.exports = router;
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const router = express.Router();
|
|
3
|
+
const {
|
|
4
|
+
buildLaunchCommand,
|
|
5
|
+
getProjects,
|
|
6
|
+
getSessionsByProject,
|
|
7
|
+
getSessionById,
|
|
8
|
+
getSessionMessages,
|
|
9
|
+
getRecentSessions,
|
|
10
|
+
searchSessions,
|
|
11
|
+
deleteSession,
|
|
12
|
+
forkSession,
|
|
13
|
+
saveSessionOrder,
|
|
14
|
+
isPiInstalled,
|
|
15
|
+
HOME_DIR
|
|
16
|
+
} = require('../services/pi-sessions');
|
|
17
|
+
const { loadAliases } = require('../services/alias');
|
|
18
|
+
const { broadcastLog } = require('../websocket-server');
|
|
19
|
+
|
|
20
|
+
function isNotFoundError(error) {
|
|
21
|
+
if (!error || !error.message) return false;
|
|
22
|
+
return error.message === 'Session not found' || error.message === 'Project not found';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function quoteForShell(value) {
|
|
26
|
+
return `"${String(value).replace(/"/g, '\\"')}"`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = () => {
|
|
30
|
+
router.get('/search/global', (req, res) => {
|
|
31
|
+
try {
|
|
32
|
+
if (!isPiInstalled()) {
|
|
33
|
+
return res.status(404).json({ error: 'OMP CLI not installed' });
|
|
34
|
+
}
|
|
35
|
+
const { keyword } = req.query;
|
|
36
|
+
const parsedContextLength = req.query.context ? parseInt(req.query.context, 10) : 35;
|
|
37
|
+
const contextLength = Number.isFinite(parsedContextLength) ? parsedContextLength : 35;
|
|
38
|
+
if (!keyword) {
|
|
39
|
+
return res.status(400).json({ error: 'Keyword is required' });
|
|
40
|
+
}
|
|
41
|
+
const sessions = searchSessions(keyword, contextLength);
|
|
42
|
+
const totalMatches = sessions.reduce((sum, session) => sum + (session.matchCount || 0), 0);
|
|
43
|
+
res.json({ keyword, totalMatches, sessions, source: 'pi' });
|
|
44
|
+
} catch (err) {
|
|
45
|
+
console.error('[OMP API] Failed to search sessions:', err);
|
|
46
|
+
res.status(500).json({ error: err.message });
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
router.get('/recent/list', (req, res) => {
|
|
51
|
+
try {
|
|
52
|
+
if (!isPiInstalled()) {
|
|
53
|
+
return res.status(404).json({ error: 'OMP CLI not installed' });
|
|
54
|
+
}
|
|
55
|
+
const limit = parseInt(req.query.limit, 10) || 5;
|
|
56
|
+
res.json({ sessions: getRecentSessions(limit), source: 'pi' });
|
|
57
|
+
} catch (err) {
|
|
58
|
+
console.error('[OMP API] Failed to get recent sessions:', err);
|
|
59
|
+
res.status(500).json({ error: err.message });
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
router.get('/:projectName/search', (req, res) => {
|
|
64
|
+
try {
|
|
65
|
+
if (!isPiInstalled()) {
|
|
66
|
+
return res.status(404).json({ error: 'OMP CLI not installed' });
|
|
67
|
+
}
|
|
68
|
+
const { projectName } = req.params;
|
|
69
|
+
const { keyword } = req.query;
|
|
70
|
+
const parsedContextLength = req.query.context ? parseInt(req.query.context, 10) : 35;
|
|
71
|
+
const contextLength = Number.isFinite(parsedContextLength) ? parsedContextLength : 35;
|
|
72
|
+
if (!keyword) {
|
|
73
|
+
return res.status(400).json({ error: 'Keyword is required' });
|
|
74
|
+
}
|
|
75
|
+
const sessions = searchSessions(keyword, contextLength, projectName);
|
|
76
|
+
const totalMatches = sessions.reduce((sum, session) => sum + (session.matchCount || 0), 0);
|
|
77
|
+
res.json({ keyword, totalMatches, sessions });
|
|
78
|
+
} catch (err) {
|
|
79
|
+
console.error('[OMP API] Failed to search project sessions:', err);
|
|
80
|
+
res.status(500).json({ error: err.message });
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
router.get('/:projectName', (req, res) => {
|
|
85
|
+
try {
|
|
86
|
+
if (!isPiInstalled()) {
|
|
87
|
+
return res.status(404).json({ error: 'OMP CLI not installed' });
|
|
88
|
+
}
|
|
89
|
+
const { projectName } = req.params;
|
|
90
|
+
const sessions = getSessionsByProject(projectName);
|
|
91
|
+
const aliases = loadAliases();
|
|
92
|
+
const project = getProjects().find(p => p.name === projectName);
|
|
93
|
+
const totalSize = sessions.reduce((sum, session) => sum + (session.size || 0), 0);
|
|
94
|
+
res.json({
|
|
95
|
+
sessions,
|
|
96
|
+
totalSize,
|
|
97
|
+
aliases,
|
|
98
|
+
projectInfo: {
|
|
99
|
+
name: projectName,
|
|
100
|
+
fullPath: project?.fullPath || project?.path || projectName,
|
|
101
|
+
path: project?.path || projectName,
|
|
102
|
+
displayName: project?.displayName || projectName
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
} catch (err) {
|
|
106
|
+
console.error('[OMP API] Failed to get sessions:', err);
|
|
107
|
+
res.status(500).json({ error: err.message });
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
router.get('/:projectName/:sessionId/status', (req, res) => {
|
|
112
|
+
try {
|
|
113
|
+
if (!isPiInstalled()) {
|
|
114
|
+
return res.status(404).json({ error: 'OMP CLI not installed' });
|
|
115
|
+
}
|
|
116
|
+
const session = getSessionById(req.params.sessionId);
|
|
117
|
+
if (!session) {
|
|
118
|
+
return res.status(404).json({ error: 'Session not found' });
|
|
119
|
+
}
|
|
120
|
+
res.json({
|
|
121
|
+
sessionId: req.params.sessionId,
|
|
122
|
+
lastModified: session.mtime || null,
|
|
123
|
+
size: session.size || 0
|
|
124
|
+
});
|
|
125
|
+
} catch (err) {
|
|
126
|
+
console.error('[OMP API] Failed to get session status:', err);
|
|
127
|
+
res.status(500).json({ error: err.message });
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
router.get('/:projectName/:sessionId/outline', (req, res) => {
|
|
132
|
+
try {
|
|
133
|
+
if (!isPiInstalled()) {
|
|
134
|
+
return res.status(404).json({ error: 'OMP CLI not installed' });
|
|
135
|
+
}
|
|
136
|
+
const session = getSessionById(req.params.sessionId);
|
|
137
|
+
if (!session) {
|
|
138
|
+
return res.status(404).json({ error: 'Session not found' });
|
|
139
|
+
}
|
|
140
|
+
let userMessageNumber = 0;
|
|
141
|
+
const items = [];
|
|
142
|
+
for (const msg of getSessionMessages(req.params.sessionId)) {
|
|
143
|
+
if (msg.type !== 'user') continue;
|
|
144
|
+
const preview = typeof msg.content === 'string' ? msg.content.trim() : '';
|
|
145
|
+
if (!preview) continue;
|
|
146
|
+
userMessageNumber += 1;
|
|
147
|
+
items.push({
|
|
148
|
+
userMessageNumber,
|
|
149
|
+
preview: preview.length > 42 ? `${preview.slice(0, 42)}...` : preview,
|
|
150
|
+
timestamp: msg.timestamp || null
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
res.json({ sessionId: req.params.sessionId, items });
|
|
154
|
+
} catch (err) {
|
|
155
|
+
console.error('[OMP API] Failed to get session outline:', err);
|
|
156
|
+
res.status(500).json({ error: err.message });
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
router.get('/:projectName/:sessionId/messages', (req, res) => {
|
|
161
|
+
try {
|
|
162
|
+
if (!isPiInstalled()) {
|
|
163
|
+
return res.status(404).json({ error: 'OMP CLI not installed' });
|
|
164
|
+
}
|
|
165
|
+
const { sessionId } = req.params;
|
|
166
|
+
const { page = 1, limit = 20, order = 'desc' } = req.query;
|
|
167
|
+
const session = getSessionById(sessionId);
|
|
168
|
+
if (!session) {
|
|
169
|
+
return res.status(404).json({ error: 'Session not found' });
|
|
170
|
+
}
|
|
171
|
+
let userMessageNumber = 0;
|
|
172
|
+
const convertedMessages = getSessionMessages(sessionId).map((message) => {
|
|
173
|
+
if (message.type !== 'user') return message;
|
|
174
|
+
userMessageNumber += 1;
|
|
175
|
+
return { ...message, userMessageNumber };
|
|
176
|
+
});
|
|
177
|
+
const pageNum = parseInt(page, 10) || 1;
|
|
178
|
+
const limitNum = parseInt(limit, 10) || 20;
|
|
179
|
+
const orderedMessages = order === 'desc' ? [...convertedMessages].reverse() : convertedMessages;
|
|
180
|
+
const start = (pageNum - 1) * limitNum;
|
|
181
|
+
const end = start + limitNum;
|
|
182
|
+
res.json({
|
|
183
|
+
messages: orderedMessages.slice(start, end),
|
|
184
|
+
metadata: {
|
|
185
|
+
gitBranch: null,
|
|
186
|
+
gitRepository: null,
|
|
187
|
+
cwd: session?.directory || null,
|
|
188
|
+
provider: session?.provider || '',
|
|
189
|
+
model: session?.model || 'pi'
|
|
190
|
+
},
|
|
191
|
+
pagination: {
|
|
192
|
+
page: pageNum,
|
|
193
|
+
limit: limitNum,
|
|
194
|
+
total: orderedMessages.length,
|
|
195
|
+
hasMore: end < orderedMessages.length
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
} catch (err) {
|
|
199
|
+
console.error('[OMP API] Failed to get session messages:', err);
|
|
200
|
+
res.status(500).json({ error: err.message });
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
router.delete('/:projectName/:sessionId', (req, res) => {
|
|
205
|
+
try {
|
|
206
|
+
if (!isPiInstalled()) {
|
|
207
|
+
return res.status(404).json({ error: 'OMP CLI not installed' });
|
|
208
|
+
}
|
|
209
|
+
res.json(deleteSession(req.params.sessionId));
|
|
210
|
+
} catch (err) {
|
|
211
|
+
if (isNotFoundError(err)) {
|
|
212
|
+
return res.status(404).json({ error: err.message });
|
|
213
|
+
}
|
|
214
|
+
console.error('[OMP API] Failed to delete session:', err);
|
|
215
|
+
res.status(500).json({ error: err.message });
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
router.post('/:projectName/batch-delete', (req, res) => {
|
|
220
|
+
try {
|
|
221
|
+
if (!isPiInstalled()) {
|
|
222
|
+
return res.status(404).json({ error: 'OMP CLI not installed' });
|
|
223
|
+
}
|
|
224
|
+
const { sessionIds } = req.body || {};
|
|
225
|
+
if (!Array.isArray(sessionIds) || sessionIds.length === 0) {
|
|
226
|
+
return res.status(400).json({ error: 'sessionIds must be a non-empty array' });
|
|
227
|
+
}
|
|
228
|
+
const uniqueSessionIds = Array.from(new Set(
|
|
229
|
+
sessionIds
|
|
230
|
+
.filter(sessionId => typeof sessionId === 'string')
|
|
231
|
+
.map(sessionId => sessionId.trim())
|
|
232
|
+
.filter(Boolean)
|
|
233
|
+
));
|
|
234
|
+
if (uniqueSessionIds.length === 0) {
|
|
235
|
+
return res.status(400).json({ error: 'sessionIds must be a non-empty array' });
|
|
236
|
+
}
|
|
237
|
+
const deletedSessionIds = [];
|
|
238
|
+
const failed = [];
|
|
239
|
+
uniqueSessionIds.forEach((sessionId) => {
|
|
240
|
+
try {
|
|
241
|
+
deleteSession(sessionId);
|
|
242
|
+
deletedSessionIds.push(sessionId);
|
|
243
|
+
} catch (err) {
|
|
244
|
+
failed.push({ sessionId, error: err.message });
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
res.json({
|
|
248
|
+
success: failed.length === 0,
|
|
249
|
+
requestedCount: uniqueSessionIds.length,
|
|
250
|
+
deletedCount: deletedSessionIds.length,
|
|
251
|
+
deletedSessionIds,
|
|
252
|
+
failed
|
|
253
|
+
});
|
|
254
|
+
} catch (err) {
|
|
255
|
+
console.error('[OMP API] Failed to batch delete sessions:', err);
|
|
256
|
+
res.status(500).json({ error: err.message });
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
router.post('/:projectName/:sessionId/fork', (req, res) => {
|
|
261
|
+
try {
|
|
262
|
+
if (!isPiInstalled()) {
|
|
263
|
+
return res.status(404).json({ error: 'OMP CLI not installed' });
|
|
264
|
+
}
|
|
265
|
+
res.json(forkSession(req.params.sessionId, req.body || {}));
|
|
266
|
+
} catch (err) {
|
|
267
|
+
if (isNotFoundError(err)) {
|
|
268
|
+
return res.status(404).json({ error: err.message });
|
|
269
|
+
}
|
|
270
|
+
console.error('[OMP API] Failed to fork session:', err);
|
|
271
|
+
res.status(500).json({ error: err.message });
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
router.post('/:projectName/order', (req, res) => {
|
|
276
|
+
try {
|
|
277
|
+
if (!isPiInstalled()) {
|
|
278
|
+
return res.status(404).json({ error: 'OMP CLI not installed' });
|
|
279
|
+
}
|
|
280
|
+
const { order } = req.body || {};
|
|
281
|
+
if (!Array.isArray(order)) {
|
|
282
|
+
return res.status(400).json({ error: 'order must be an array' });
|
|
283
|
+
}
|
|
284
|
+
saveSessionOrder(req.params.projectName, order);
|
|
285
|
+
res.json({ success: true });
|
|
286
|
+
} catch (err) {
|
|
287
|
+
console.error('[OMP API] Failed to save session order:', err);
|
|
288
|
+
res.status(500).json({ error: err.message });
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
router.post('/:projectName/:sessionId/launch', (req, res) => {
|
|
293
|
+
try {
|
|
294
|
+
if (!isPiInstalled()) {
|
|
295
|
+
return res.status(404).json({ error: 'OMP CLI not installed' });
|
|
296
|
+
}
|
|
297
|
+
const { projectName, sessionId } = req.params;
|
|
298
|
+
const session = getSessionById(sessionId);
|
|
299
|
+
if (!session) {
|
|
300
|
+
return res.status(404).json({ error: 'Session not found' });
|
|
301
|
+
}
|
|
302
|
+
const project = getProjects().find(p => p.name === projectName);
|
|
303
|
+
const cwd = session.directory || project?.fullPath || HOME_DIR;
|
|
304
|
+
const command = buildLaunchCommand(sessionId, cwd, req.body || {});
|
|
305
|
+
const copyCommand = `cd ${quoteForShell(cwd)} && ${command}`;
|
|
306
|
+
|
|
307
|
+
broadcastLog({
|
|
308
|
+
type: 'action',
|
|
309
|
+
action: 'launch_pi_session',
|
|
310
|
+
message: `复制 OMP 会话启动命令 ${sessionId.substring(0, 8)}`,
|
|
311
|
+
sessionId,
|
|
312
|
+
tool: 'pi',
|
|
313
|
+
toolType: 'pi',
|
|
314
|
+
source: 'pi',
|
|
315
|
+
timestamp: Date.now()
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
res.json({
|
|
319
|
+
success: true,
|
|
320
|
+
cwd,
|
|
321
|
+
sessionFile: session.filePath,
|
|
322
|
+
sessionId,
|
|
323
|
+
tool: 'pi',
|
|
324
|
+
command,
|
|
325
|
+
copyCommand
|
|
326
|
+
});
|
|
327
|
+
} catch (err) {
|
|
328
|
+
console.error('[OMP API] Failed to prepare launch command:', err);
|
|
329
|
+
res.status(500).json({ error: err.message });
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
return router;
|
|
334
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const router = express.Router();
|
|
3
|
+
const {
|
|
4
|
+
getStatistics,
|
|
5
|
+
getDailyStatistics,
|
|
6
|
+
getTodayStatistics
|
|
7
|
+
} = require('../services/pi-statistics-service');
|
|
8
|
+
|
|
9
|
+
router.get('/summary', (req, res) => {
|
|
10
|
+
try {
|
|
11
|
+
res.json(getStatistics());
|
|
12
|
+
} catch (error) {
|
|
13
|
+
console.error('[OMP] Failed to get statistics:', error);
|
|
14
|
+
res.status(500).json({ error: 'Failed to get statistics' });
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
router.get('/today', (req, res) => {
|
|
19
|
+
try {
|
|
20
|
+
res.json(getTodayStatistics());
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error('[OMP] Failed to get today statistics:', error);
|
|
23
|
+
res.status(500).json({ error: 'Failed to get today statistics' });
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
router.get('/daily/:date', (req, res) => {
|
|
28
|
+
try {
|
|
29
|
+
const { date } = req.params;
|
|
30
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) {
|
|
31
|
+
return res.status(400).json({ error: 'Invalid date format. Expected YYYY-MM-DD' });
|
|
32
|
+
}
|
|
33
|
+
res.json(getDailyStatistics(date));
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.error('[OMP] Failed to get daily statistics:', error);
|
|
36
|
+
res.status(500).json({ error: 'Failed to get daily statistics' });
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
module.exports = router;
|
|
@@ -10,7 +10,7 @@ const { maskToken } = require('../services/oauth-utils');
|
|
|
10
10
|
const { sendApiError } = require('./validation-errors');
|
|
11
11
|
|
|
12
12
|
const router = express.Router();
|
|
13
|
-
const SUPPORTED_PLATFORMS = ['claude', 'codex', 'gemini', 'opencode'];
|
|
13
|
+
const SUPPORTED_PLATFORMS = ['claude', 'codex', 'gemini', 'opencode', 'pi'];
|
|
14
14
|
const pluginServices = new Map();
|
|
15
15
|
|
|
16
16
|
function resolvePlatform(rawPlatform) {
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
const express = require('express');
|
|
6
6
|
const router = express.Router();
|
|
7
7
|
const promptsService = require('../services/prompts-service');
|
|
8
|
+
const PROMPT_FILE_PLATFORMS = ['claude', 'codex', 'gemini', 'opencode'];
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* GET /api/prompts/presets
|
|
@@ -203,7 +204,7 @@ router.get('/platform/:platform', (req, res) => {
|
|
|
203
204
|
try {
|
|
204
205
|
const { platform } = req.params;
|
|
205
206
|
|
|
206
|
-
if (!
|
|
207
|
+
if (!PROMPT_FILE_PLATFORMS.includes(platform)) {
|
|
207
208
|
return res.status(400).json({
|
|
208
209
|
success: false,
|
|
209
210
|
error: `无效的平台: ${platform}`
|
|
@@ -234,7 +235,7 @@ router.post('/import/:platform', (req, res) => {
|
|
|
234
235
|
const { platform } = req.params;
|
|
235
236
|
const { name } = req.body;
|
|
236
237
|
|
|
237
|
-
if (!
|
|
238
|
+
if (!PROMPT_FILE_PLATFORMS.includes(platform)) {
|
|
238
239
|
return res.status(400).json({
|
|
239
240
|
success: false,
|
|
240
241
|
error: `无效的平台: ${platform}`
|
|
@@ -90,6 +90,7 @@ function handleSaveModelSettings(req, res) {
|
|
|
90
90
|
const config = loadConfig();
|
|
91
91
|
const newConfig = {
|
|
92
92
|
...config,
|
|
93
|
+
projectsDir: config.projectsDir.replace(require('os').homedir(), '~'),
|
|
93
94
|
modelMetadataOverrides: overrides && typeof overrides === 'object'
|
|
94
95
|
? overrides
|
|
95
96
|
: (config.modelMetadataOverrides || {})
|
|
@@ -123,6 +124,7 @@ function handleDeleteModelOverride(req, res) {
|
|
|
123
124
|
|
|
124
125
|
const newConfig = {
|
|
125
126
|
...config,
|
|
127
|
+
projectsDir: config.projectsDir.replace(require('os').homedir(), '~'),
|
|
126
128
|
modelMetadataOverrides: overrides
|
|
127
129
|
};
|
|
128
130
|
saveConfig(newConfig);
|
package/src/server/api/skills.js
CHANGED
|
@@ -8,7 +8,7 @@ const { maskToken } = require('../services/oauth-utils');
|
|
|
8
8
|
const { sendApiError } = require('./validation-errors');
|
|
9
9
|
|
|
10
10
|
const router = express.Router();
|
|
11
|
-
const SUPPORTED_PLATFORMS = ['claude', 'codex', 'gemini', 'opencode'];
|
|
11
|
+
const SUPPORTED_PLATFORMS = ['claude', 'codex', 'gemini', 'opencode', 'pi'];
|
|
12
12
|
const skillServices = new Map();
|
|
13
13
|
|
|
14
14
|
function resolvePlatform(rawPlatform) {
|
|
@@ -33,24 +33,6 @@ function validateBranchName(branchName) {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
function normalizeBranchMode(branchMode) {
|
|
37
|
-
if (typeof branchMode !== 'string' || !branchMode.trim()) {
|
|
38
|
-
return '';
|
|
39
|
-
}
|
|
40
|
-
return branchMode.trim();
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function validateBranchMode(branchMode) {
|
|
44
|
-
const normalized = normalizeBranchMode(branchMode);
|
|
45
|
-
if (!normalized) {
|
|
46
|
-
return { valid: true, normalized: '' };
|
|
47
|
-
}
|
|
48
|
-
if (normalized !== 'existing' && normalized !== 'new') {
|
|
49
|
-
return { valid: false, normalized, message: `分支模式不合法: ${normalized}` };
|
|
50
|
-
}
|
|
51
|
-
return { valid: true, normalized };
|
|
52
|
-
}
|
|
53
|
-
|
|
54
36
|
/**
|
|
55
37
|
* GET /api/workspaces
|
|
56
38
|
* 获取所有工作区列表
|
|
@@ -153,7 +135,7 @@ router.get('/check-git/*', (req, res) => {
|
|
|
153
135
|
|
|
154
136
|
/**
|
|
155
137
|
* GET /api/workspaces/available-projects
|
|
156
|
-
* 获取所有渠道(Claude/Codex/Gemini)的项目并集
|
|
138
|
+
* 获取所有渠道(Claude/Codex/Gemini/OpenCode/OMP)的项目并集
|
|
157
139
|
*/
|
|
158
140
|
router.get('/available-projects', async (req, res) => {
|
|
159
141
|
try {
|
|
@@ -244,17 +226,6 @@ router.post('/', (req, res) => {
|
|
|
244
226
|
});
|
|
245
227
|
}
|
|
246
228
|
|
|
247
|
-
const branchModeValidation = validateBranchMode(proj.branchMode);
|
|
248
|
-
if (!branchModeValidation.valid) {
|
|
249
|
-
return res.status(400).json({
|
|
250
|
-
success: false,
|
|
251
|
-
message: branchModeValidation.message
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
if (branchModeValidation.normalized) {
|
|
255
|
-
proj.branchMode = branchModeValidation.normalized;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
229
|
const normalizedBranch = normalizeBranchName(proj.branch);
|
|
259
230
|
if (normalizedBranch) {
|
|
260
231
|
const branchValidation = validateBranchName(normalizedBranch);
|
|
@@ -358,10 +329,9 @@ router.put('/:id/last-used', (req, res) => {
|
|
|
358
329
|
router.post('/:id/projects', (req, res) => {
|
|
359
330
|
try {
|
|
360
331
|
const { id } = req.params;
|
|
361
|
-
const { sourcePath, name, createWorktree, branch, baseBranch
|
|
332
|
+
const { sourcePath, name, createWorktree, branch, baseBranch } = req.body;
|
|
362
333
|
const normalizedBranch = normalizeBranchName(branch);
|
|
363
334
|
const normalizedBaseBranch = normalizeBranchName(baseBranch);
|
|
364
|
-
const branchModeValidation = validateBranchMode(branchMode);
|
|
365
335
|
|
|
366
336
|
if (!sourcePath || !sourcePath.trim()) {
|
|
367
337
|
return res.status(400).json({
|
|
@@ -370,13 +340,6 @@ router.post('/:id/projects', (req, res) => {
|
|
|
370
340
|
});
|
|
371
341
|
}
|
|
372
342
|
|
|
373
|
-
if (!branchModeValidation.valid) {
|
|
374
|
-
return res.status(400).json({
|
|
375
|
-
success: false,
|
|
376
|
-
message: branchModeValidation.message
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
|
-
|
|
380
343
|
if (normalizedBranch) {
|
|
381
344
|
const branchValidation = validateBranchName(normalizedBranch);
|
|
382
345
|
if (!branchValidation.valid) {
|
|
@@ -401,7 +364,6 @@ router.post('/:id/projects', (req, res) => {
|
|
|
401
364
|
sourcePath,
|
|
402
365
|
name,
|
|
403
366
|
createWorktree,
|
|
404
|
-
branchMode: branchModeValidation.normalized || branchMode,
|
|
405
367
|
branch: normalizedBranch || branch,
|
|
406
368
|
baseBranch: normalizedBaseBranch || baseBranch
|
|
407
369
|
});
|
|
@@ -449,7 +411,7 @@ router.delete('/:id/projects/:projectName', (req, res) => {
|
|
|
449
411
|
/**
|
|
450
412
|
* POST /api/workspaces/:id/launch
|
|
451
413
|
* 获取在工作区启动 CLI 工具的命令
|
|
452
|
-
* Body: { tool: 'claude' | 'codex' | 'gemini', projectName?: string }
|
|
414
|
+
* Body: { tool: 'claude' | 'codex' | 'gemini' | 'opencode' | 'pi' | 'omp', projectName?: string }
|
|
453
415
|
*/
|
|
454
416
|
router.post('/:id/launch', (req, res) => {
|
|
455
417
|
try {
|