@cloudcli-ai/cloudcli 1.34.0 → 1.35.1
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.de.md +9 -1
- package/README.ja.md +8 -0
- package/README.ko.md +9 -1
- package/README.md +23 -5
- package/README.ru.md +9 -1
- package/README.tr.md +8 -1
- package/README.zh-CN.md +9 -1
- package/README.zh-TW.md +9 -1
- package/dist/api-docs.html +36 -18
- package/dist/assets/index-BvX30NW5.css +32 -0
- package/dist/assets/index-D1IdCTT6.js +1418 -0
- package/dist/index.html +12 -4
- package/dist-server/server/browser-use-mcp.js +350 -0
- package/dist-server/server/browser-use-mcp.js.map +1 -0
- package/dist-server/server/claude-sdk.js +25 -5
- package/dist-server/server/claude-sdk.js.map +1 -1
- package/dist-server/server/cli.js +14 -6
- package/dist-server/server/cli.js.map +1 -1
- package/dist-server/server/cursor-cli.js +27 -11
- package/dist-server/server/cursor-cli.js.map +1 -1
- package/dist-server/server/gemini-cli.js +17 -2
- package/dist-server/server/gemini-cli.js.map +1 -1
- package/dist-server/server/index.js +136 -40
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/load-env.js +1 -1
- package/dist-server/server/load-env.js.map +1 -1
- package/dist-server/server/modules/browser-use/browser-use-mcp.routes.js +107 -0
- package/dist-server/server/modules/browser-use/browser-use-mcp.routes.js.map +1 -0
- package/dist-server/server/modules/browser-use/browser-use.routes.js +93 -0
- package/dist-server/server/modules/browser-use/browser-use.routes.js.map +1 -0
- package/dist-server/server/modules/browser-use/browser-use.service.js +688 -0
- package/dist-server/server/modules/browser-use/browser-use.service.js.map +1 -0
- package/dist-server/server/modules/browser-use/tests/browser-use.service.test.js +8 -0
- package/dist-server/server/modules/browser-use/tests/browser-use.service.test.js.map +1 -0
- package/dist-server/server/modules/database/index.js +1 -0
- package/dist-server/server/modules/database/index.js.map +1 -1
- package/dist-server/server/modules/database/migrations.js +23 -1
- package/dist-server/server/modules/database/migrations.js.map +1 -1
- package/dist-server/server/modules/database/repositories/notification-channel-endpoints.js +100 -0
- package/dist-server/server/modules/database/repositories/notification-channel-endpoints.js.map +1 -0
- package/dist-server/server/modules/database/repositories/notification-preferences.js +8 -0
- package/dist-server/server/modules/database/repositories/notification-preferences.js.map +1 -1
- package/dist-server/server/modules/database/repositories/sessions.db.js +173 -17
- package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -1
- package/dist-server/server/modules/database/schema.js +26 -0
- package/dist-server/server/modules/database/schema.js.map +1 -1
- package/dist-server/server/modules/database/tests/projects.db.integration.test.js.map +1 -0
- package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js +78 -0
- package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js.map +1 -0
- package/dist-server/server/modules/database/{repositories → tests}/sessions.db.integration.test.js +10 -0
- package/dist-server/server/modules/database/tests/sessions.db.integration.test.js.map +1 -0
- package/dist-server/server/modules/notifications/index.js +4 -0
- package/dist-server/server/modules/notifications/index.js.map +1 -0
- package/dist-server/server/modules/notifications/notifications.routes.js +115 -0
- package/dist-server/server/modules/notifications/notifications.routes.js.map +1 -0
- package/dist-server/server/modules/notifications/services/desktop-notification-clients.service.js +97 -0
- package/dist-server/server/modules/notifications/services/desktop-notification-clients.service.js.map +1 -0
- package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js +229 -0
- package/dist-server/server/modules/notifications/services/notification-orchestrator.service.js.map +1 -0
- package/dist-server/server/modules/notifications/websocket/desktop-notifications-websocket.service.js +79 -0
- package/dist-server/server/modules/notifications/websocket/desktop-notifications-websocket.service.js.map +1 -0
- package/dist-server/server/modules/projects/services/project-management.service.js +0 -4
- package/dist-server/server/modules/projects/services/project-management.service.js.map +1 -1
- package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js +10 -38
- package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js.map +1 -1
- package/dist-server/server/modules/providers/index.js +1 -0
- package/dist-server/server/modules/providers/index.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js +24 -1
- package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +9 -12
- package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js +7 -0
- package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +7 -3
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +3 -9
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js +7 -0
- package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js +13 -22
- package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-skills.provider.js +7 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-skills.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-models.provider.js +5 -8
- package/dist-server/server/modules/providers/list/gemini/gemini-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js +6 -6
- package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-skills.provider.js +7 -0
- package/dist-server/server/modules/providers/list/gemini/gemini-skills.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +17 -3
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js +9 -10
- package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/provider.routes.js +123 -1
- package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
- package/dist-server/server/modules/providers/services/mcp.service.js +23 -0
- package/dist-server/server/modules/providers/services/mcp.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/provider-capabilities.service.js +65 -0
- package/dist-server/server/modules/providers/services/provider-capabilities.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/provider-models.service.js +1 -1
- package/dist-server/server/modules/providers/services/provider-models.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js +62 -23
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/sessions.service.js +62 -4
- package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/skills.service.js +11 -0
- package/dist-server/server/modules/providers/services/skills.service.js.map +1 -1
- package/dist-server/server/modules/providers/shared/skills/skills.provider.js +163 -1
- package/dist-server/server/modules/providers/shared/skills/skills.provider.js.map +1 -1
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +45 -0
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/skills.test.js +160 -0
- package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -1
- package/dist-server/server/modules/websocket/index.js +1 -0
- package/dist-server/server/modules/websocket/index.js.map +1 -1
- package/dist-server/server/modules/websocket/services/chat-run-registry.service.js +252 -0
- package/dist-server/server/modules/websocket/services/chat-run-registry.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/chat-session-writer.service.js +105 -0
- package/dist-server/server/modules/websocket/services/chat-session-writer.service.js.map +1 -0
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js +218 -156
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js +83 -31
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
- package/dist-server/server/modules/websocket/services/websocket-server.service.js +5 -0
- package/dist-server/server/modules/websocket/services/websocket-server.service.js.map +1 -1
- package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js +216 -0
- package/dist-server/server/modules/websocket/tests/chat-run-registry.test.js.map +1 -0
- package/dist-server/server/openai-codex.js +24 -14
- package/dist-server/server/openai-codex.js.map +1 -1
- package/dist-server/server/opencode-cli.js +21 -8
- package/dist-server/server/opencode-cli.js.map +1 -1
- package/dist-server/server/opencode-cli.test.js +19 -1
- package/dist-server/server/opencode-cli.test.js.map +1 -1
- package/dist-server/server/routes/settings.js +1 -1
- package/dist-server/server/routes/settings.js.map +1 -1
- package/dist-server/server/services/notification-orchestrator.js +1 -176
- package/dist-server/server/services/notification-orchestrator.js.map +1 -1
- package/dist-server/server/services/tests/notification-orchestrator.test.js +64 -0
- package/dist-server/server/services/tests/notification-orchestrator.test.js.map +1 -0
- package/dist-server/server/shared/{claude-cli-path.test.js → tests/claude-cli-path.test.js} +1 -1
- package/dist-server/server/shared/tests/claude-cli-path.test.js.map +1 -0
- package/dist-server/server/shared/tests/slice-tail-page.test.js +35 -0
- package/dist-server/server/shared/tests/slice-tail-page.test.js.map +1 -0
- package/dist-server/server/shared/utils.js +73 -1
- package/dist-server/server/shared/utils.js.map +1 -1
- package/dist-server/server/voice-proxy.js +226 -0
- package/dist-server/server/voice-proxy.js.map +1 -0
- package/electron/assets/logo-macos.icns +0 -0
- package/electron/assets/logo-macos.png +0 -0
- package/electron/assets/logo-windows.ico +0 -0
- package/electron/cloud.js +260 -0
- package/electron/desktopNotifications.js +378 -0
- package/electron/desktopWindow.js +766 -0
- package/electron/launcher/index.html +14 -0
- package/electron/launcher/launcher.css +801 -0
- package/electron/launcher/launcher.js +687 -0
- package/electron/localServer.js +550 -0
- package/electron/main.js +944 -0
- package/electron/preload.cjs +60 -0
- package/electron/scripts/generate-macos-icon.js +62 -0
- package/electron/serverInstaller.js +277 -0
- package/electron/tabs.js +87 -0
- package/electron/viewHost.js +331 -0
- package/package.json +79 -3
- package/public/api-docs.html +36 -18
- package/scripts/release/build-server-bundle.js +176 -0
- package/scripts/release/prepare-desktop-app.js +152 -0
- package/server/browser-use-mcp.ts +384 -0
- package/server/claude-sdk.js +27 -5
- package/server/cli.js +15 -6
- package/server/cursor-cli.js +27 -11
- package/server/gemini-cli.js +17 -2
- package/server/index.js +137 -47
- package/server/load-env.js +1 -1
- package/server/modules/browser-use/browser-use-mcp.routes.ts +120 -0
- package/server/modules/browser-use/browser-use.routes.ts +96 -0
- package/server/modules/browser-use/browser-use.service.ts +836 -0
- package/server/modules/browser-use/tests/browser-use.service.test.ts +10 -0
- package/server/modules/database/index.ts +1 -0
- package/server/modules/database/migrations.ts +25 -0
- package/server/modules/database/repositories/notification-channel-endpoints.ts +153 -0
- package/server/modules/database/repositories/notification-preferences.ts +12 -1
- package/server/modules/database/repositories/sessions.db.ts +227 -23
- package/server/modules/database/schema.ts +27 -0
- package/server/modules/database/tests/sessions-provider-mapping.test.ts +108 -0
- package/server/modules/database/{repositories → tests}/sessions.db.integration.test.ts +12 -0
- package/server/modules/notifications/index.ts +13 -0
- package/server/modules/notifications/notifications.routes.ts +127 -0
- package/server/modules/notifications/services/desktop-notification-clients.service.ts +124 -0
- package/server/modules/notifications/services/notification-orchestrator.service.js +288 -0
- package/server/modules/notifications/websocket/desktop-notifications-websocket.service.ts +109 -0
- package/server/modules/projects/services/project-management.service.ts +0 -8
- package/server/modules/projects/services/projects-with-sessions-fetch.service.ts +12 -53
- package/server/modules/providers/README.md +1 -1
- package/server/modules/providers/index.ts +1 -0
- package/server/modules/providers/list/claude/claude-session-synchronizer.provider.ts +26 -1
- package/server/modules/providers/list/claude/claude-sessions.provider.ts +9 -14
- package/server/modules/providers/list/claude/claude-skills.provider.ts +8 -0
- package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +7 -3
- package/server/modules/providers/list/codex/codex-sessions.provider.ts +3 -12
- package/server/modules/providers/list/codex/codex-skills.provider.ts +8 -0
- package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +13 -22
- package/server/modules/providers/list/cursor/cursor-skills.provider.ts +8 -0
- package/server/modules/providers/list/gemini/gemini-models.provider.ts +5 -8
- package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +6 -6
- package/server/modules/providers/list/gemini/gemini-skills.provider.ts +8 -0
- package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +18 -4
- package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +9 -12
- package/server/modules/providers/provider.routes.ts +167 -1
- package/server/modules/providers/services/mcp.service.ts +26 -0
- package/server/modules/providers/services/provider-capabilities.service.ts +91 -0
- package/server/modules/providers/services/provider-models.service.ts +1 -1
- package/server/modules/providers/services/sessions-watcher.service.ts +71 -25
- package/server/modules/providers/services/sessions.service.ts +79 -4
- package/server/modules/providers/services/skills.service.ts +25 -1
- package/server/modules/providers/shared/skills/skills.provider.ts +223 -0
- package/server/modules/providers/tests/opencode-sessions.test.ts +49 -0
- package/server/modules/providers/tests/skills.test.ts +212 -0
- package/server/modules/websocket/README.md +33 -27
- package/server/modules/websocket/index.ts +1 -0
- package/server/modules/websocket/services/chat-run-registry.service.ts +327 -0
- package/server/modules/websocket/services/chat-session-writer.service.ts +145 -0
- package/server/modules/websocket/services/chat-websocket.service.ts +292 -207
- package/server/modules/websocket/services/shell-websocket.service.ts +108 -32
- package/server/modules/websocket/services/websocket-server.service.ts +6 -0
- package/server/modules/websocket/tests/chat-run-registry.test.ts +244 -0
- package/server/openai-codex.js +24 -14
- package/server/opencode-cli.js +21 -8
- package/server/opencode-cli.test.js +20 -1
- package/server/routes/settings.js +6 -1
- package/server/services/notification-orchestrator.js +3 -224
- package/server/services/tests/notification-orchestrator.test.js +80 -0
- package/server/shared/interfaces.ts +15 -0
- package/server/shared/tests/slice-tail-page.test.ts +42 -0
- package/server/shared/types.ts +84 -1
- package/server/shared/utils.ts +95 -1
- package/server/voice-proxy.js +224 -0
- package/dist/assets/index-73Xw3i9v.css +0 -32
- package/dist/assets/index-BIHaviaA.js +0 -1361
- package/dist/modelConstants.js +0 -854
- package/dist-server/server/modules/database/repositories/projects.db.integration.test.js.map +0 -1
- package/dist-server/server/modules/database/repositories/sessions.db.integration.test.js.map +0 -1
- package/dist-server/server/shared/claude-cli-path.test.js.map +0 -1
- package/public/modelConstants.js +0 -854
- /package/dist-server/server/modules/database/{repositories → tests}/projects.db.integration.test.js +0 -0
- /package/server/modules/database/{repositories → tests}/projects.db.integration.test.ts +0 -0
- /package/server/shared/{claude-cli-path.test.ts → tests/claude-cli-path.test.ts} +0 -0
|
@@ -5,7 +5,7 @@ import readline from 'node:readline';
|
|
|
5
5
|
|
|
6
6
|
import type { IProviderSessions } from '@/shared/interfaces.js';
|
|
7
7
|
import type { AnyRecord, FetchHistoryOptions, FetchHistoryResult, NormalizedMessage } from '@/shared/types.js';
|
|
8
|
-
import { createNormalizedMessage, generateMessageId, readObjectRecord } from '@/shared/utils.js';
|
|
8
|
+
import { createNormalizedMessage, generateMessageId, readObjectRecord, sliceTailPage } from '@/shared/utils.js';
|
|
9
9
|
import { sessionsDb } from '@/modules/database/index.js';
|
|
10
10
|
|
|
11
11
|
const PROVIDER = 'claude';
|
|
@@ -103,10 +103,13 @@ async function parseAgentTools(filePath: string): Promise<AnyRecord[]> {
|
|
|
103
103
|
|
|
104
104
|
async function getSessionMessages(
|
|
105
105
|
sessionId: string,
|
|
106
|
+
providerSessionId: string,
|
|
106
107
|
limit: number | null,
|
|
107
108
|
offset: number,
|
|
108
109
|
): Promise<ClaudeHistoryMessagesResult> {
|
|
109
110
|
try {
|
|
111
|
+
// The DB row is keyed by the app-facing session id, while the JSONL rows
|
|
112
|
+
// on disk carry the provider-native id — both ids are needed here.
|
|
110
113
|
const jsonLPath = sessionsDb.getSessionById(sessionId)?.jsonl_path;
|
|
111
114
|
|
|
112
115
|
if (!jsonLPath) {
|
|
@@ -133,7 +136,7 @@ async function getSessionMessages(
|
|
|
133
136
|
|
|
134
137
|
try {
|
|
135
138
|
const entry = JSON.parse(line) as AnyRecord;
|
|
136
|
-
if (entry.sessionId ===
|
|
139
|
+
if (entry.sessionId === providerSessionId) {
|
|
137
140
|
messages.push(entry);
|
|
138
141
|
}
|
|
139
142
|
} catch {
|
|
@@ -553,12 +556,13 @@ export class ClaudeSessionsProvider implements IProviderSessions {
|
|
|
553
556
|
options: FetchHistoryOptions = {},
|
|
554
557
|
): Promise<FetchHistoryResult> {
|
|
555
558
|
const { limit = null, offset = 0 } = options;
|
|
559
|
+
const providerSessionId = options.providerSessionId ?? sessionId;
|
|
556
560
|
|
|
557
561
|
let result: ClaudeHistoryResult;
|
|
558
562
|
try {
|
|
559
563
|
// Load full history first so `total` reflects frontend-normalized messages,
|
|
560
564
|
// not raw JSONL records.
|
|
561
|
-
result = await getSessionMessages(sessionId, null, 0);
|
|
565
|
+
result = await getSessionMessages(sessionId, providerSessionId, null, 0);
|
|
562
566
|
} catch (error) {
|
|
563
567
|
const message = error instanceof Error ? error.message : String(error);
|
|
564
568
|
console.warn(`[ClaudeProvider] Failed to load session ${sessionId}:`, message);
|
|
@@ -606,7 +610,6 @@ export class ClaudeSessionsProvider implements IProviderSessions {
|
|
|
606
610
|
}
|
|
607
611
|
}
|
|
608
612
|
|
|
609
|
-
const totalNormalized = normalized.length;
|
|
610
613
|
let total = 0;
|
|
611
614
|
for (const msg of normalized) {
|
|
612
615
|
if (msg.kind !== 'tool_result') {
|
|
@@ -615,18 +618,10 @@ export class ClaudeSessionsProvider implements IProviderSessions {
|
|
|
615
618
|
}
|
|
616
619
|
const normalizedOffset = Math.max(0, offset);
|
|
617
620
|
const normalizedLimit = limit === null ? null : Math.max(0, limit);
|
|
618
|
-
const
|
|
619
|
-
? normalized
|
|
620
|
-
: normalized.slice(
|
|
621
|
-
Math.max(0, totalNormalized - normalizedOffset - normalizedLimit),
|
|
622
|
-
Math.max(0, totalNormalized - normalizedOffset),
|
|
623
|
-
);
|
|
624
|
-
const hasMore = normalizedLimit === null
|
|
625
|
-
? false
|
|
626
|
-
: Math.max(0, totalNormalized - normalizedOffset - normalizedLimit) > 0;
|
|
621
|
+
const { page, hasMore } = sliceTailPage(normalized, normalizedLimit, normalizedOffset);
|
|
627
622
|
|
|
628
623
|
return {
|
|
629
|
-
messages,
|
|
624
|
+
messages: page,
|
|
630
625
|
total,
|
|
631
626
|
hasMore,
|
|
632
627
|
offset: normalizedOffset,
|
|
@@ -99,6 +99,14 @@ export class ClaudeSkillsProvider extends SkillsProvider {
|
|
|
99
99
|
];
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
protected async getGlobalSkillSource(): Promise<ProviderSkillSource> {
|
|
103
|
+
return {
|
|
104
|
+
scope: 'user',
|
|
105
|
+
rootDir: path.join(getClaudeHomePath(), 'skills'),
|
|
106
|
+
commandPrefix: '/',
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
102
110
|
private async listPluginSkills(claudeHomePath: string): Promise<ProviderSkill[]> {
|
|
103
111
|
const settings = await readJsonConfig(path.join(claudeHomePath, 'settings.json'));
|
|
104
112
|
const enabledPlugins = readObjectRecord(settings.enabledPlugins);
|
|
@@ -43,11 +43,12 @@ export class CodexSessionSynchronizer implements IProviderSessionSynchronizer {
|
|
|
43
43
|
continue;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
const existingSession = sessionsDb.
|
|
46
|
+
const existingSession = sessionsDb.getSessionByProviderSessionId(parsed.sessionId)
|
|
47
|
+
?? sessionsDb.getSessionById(parsed.sessionId);
|
|
47
48
|
if (existingSession) {
|
|
48
49
|
// If session name is untitled and we now have a name, update it
|
|
49
50
|
if (existingSession.custom_name === 'Untitled Codex Session' && parsed.sessionName && parsed.sessionName !== 'Untitled Codex Session') {
|
|
50
|
-
sessionsDb.updateSessionCustomName(
|
|
51
|
+
sessionsDb.updateSessionCustomName(existingSession.session_id, parsed.sessionName);
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
|
|
@@ -120,7 +121,10 @@ export class CodexSessionSynchronizer implements IProviderSessionSynchronizer {
|
|
|
120
121
|
return null;
|
|
121
122
|
}
|
|
122
123
|
|
|
123
|
-
|
|
124
|
+
// App-created sessions are keyed by an app id, so disk-discovered provider
|
|
125
|
+
// ids must be resolved through the provider-id mapping first.
|
|
126
|
+
const existingSession = sessionsDb.getSessionByProviderSessionId(parsed.sessionId)
|
|
127
|
+
?? sessionsDb.getSessionById(parsed.sessionId);
|
|
124
128
|
const existingSessionName = existingSession?.custom_name;
|
|
125
129
|
if (existingSessionName && existingSessionName !== 'Untitled Codex Session') {
|
|
126
130
|
return {
|
|
@@ -4,7 +4,7 @@ import readline from 'node:readline';
|
|
|
4
4
|
import { sessionsDb } from '@/modules/database/index.js';
|
|
5
5
|
import type { IProviderSessions } from '@/shared/interfaces.js';
|
|
6
6
|
import type { AnyRecord, FetchHistoryOptions, FetchHistoryResult, NormalizedMessage } from '@/shared/types.js';
|
|
7
|
-
import { createNormalizedMessage, generateMessageId, readObjectRecord } from '@/shared/utils.js';
|
|
7
|
+
import { createNormalizedMessage, generateMessageId, readObjectRecord, sliceTailPage } from '@/shared/utils.js';
|
|
8
8
|
|
|
9
9
|
const PROVIDER = 'codex';
|
|
10
10
|
|
|
@@ -552,7 +552,6 @@ export class CodexSessionsProvider implements IProviderSessions {
|
|
|
552
552
|
}
|
|
553
553
|
}
|
|
554
554
|
|
|
555
|
-
const totalNormalized = normalized.length;
|
|
556
555
|
let total = 0;
|
|
557
556
|
for (const msg of normalized) {
|
|
558
557
|
if (msg.kind !== 'tool_result') {
|
|
@@ -561,18 +560,10 @@ export class CodexSessionsProvider implements IProviderSessions {
|
|
|
561
560
|
}
|
|
562
561
|
const normalizedOffset = Math.max(0, offset);
|
|
563
562
|
const normalizedLimit = limit === null ? null : Math.max(0, limit);
|
|
564
|
-
const
|
|
565
|
-
? normalized
|
|
566
|
-
: normalized.slice(
|
|
567
|
-
Math.max(0, totalNormalized - normalizedOffset - normalizedLimit),
|
|
568
|
-
Math.max(0, totalNormalized - normalizedOffset),
|
|
569
|
-
);
|
|
570
|
-
const hasMore = normalizedLimit === null
|
|
571
|
-
? false
|
|
572
|
-
: Math.max(0, totalNormalized - normalizedOffset - normalizedLimit) > 0;
|
|
563
|
+
const { page, hasMore } = sliceTailPage(normalized, normalizedLimit, normalizedOffset);
|
|
573
564
|
|
|
574
565
|
return {
|
|
575
|
-
messages,
|
|
566
|
+
messages: page,
|
|
576
567
|
total,
|
|
577
568
|
hasMore,
|
|
578
569
|
offset: normalizedOffset,
|
|
@@ -57,4 +57,12 @@ export class CodexSkillsProvider extends SkillsProvider {
|
|
|
57
57
|
|
|
58
58
|
return sources;
|
|
59
59
|
}
|
|
60
|
+
|
|
61
|
+
protected async getGlobalSkillSource(): Promise<ProviderSkillSource> {
|
|
62
|
+
return {
|
|
63
|
+
scope: 'user',
|
|
64
|
+
rootDir: path.join(os.homedir(), '.agents', 'skills'),
|
|
65
|
+
commandPrefix: '$',
|
|
66
|
+
};
|
|
67
|
+
}
|
|
60
68
|
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
generateMessageId,
|
|
10
10
|
readObjectRecord,
|
|
11
11
|
sanitizeLeafDirectoryName,
|
|
12
|
+
sliceTailPage,
|
|
12
13
|
} from '@/shared/utils.js';
|
|
13
14
|
|
|
14
15
|
const PROVIDER = 'cursor';
|
|
@@ -363,42 +364,32 @@ export class CursorSessionsProvider implements IProviderSessions {
|
|
|
363
364
|
|
|
364
365
|
/**
|
|
365
366
|
* Fetches and paginates Cursor session history from its project-scoped store.db.
|
|
367
|
+
*
|
|
368
|
+
* Pagination follows the shared tail contract (`sliceTailPage`): offset 0 is
|
|
369
|
+
* the most recent page, matching every other provider.
|
|
366
370
|
*/
|
|
367
371
|
async fetchHistory(
|
|
368
372
|
sessionId: string,
|
|
369
373
|
options: FetchHistoryOptions = {},
|
|
370
374
|
): Promise<FetchHistoryResult> {
|
|
371
375
|
const { projectPath = '', limit = null, offset = 0 } = options;
|
|
376
|
+
// The store.db folder on disk is named after the provider-native id, not
|
|
377
|
+
// the app-facing session id this method is addressed with.
|
|
378
|
+
const providerSessionId = options.providerSessionId ?? sessionId;
|
|
372
379
|
|
|
373
380
|
try {
|
|
374
|
-
const blobs = await this.loadCursorBlobs(
|
|
381
|
+
const blobs = await this.loadCursorBlobs(providerSessionId, projectPath);
|
|
375
382
|
const allNormalized = this.normalizeCursorBlobs(blobs, sessionId);
|
|
376
383
|
const renderableMessages = allNormalized.filter((msg) => msg.kind !== 'tool_result');
|
|
377
384
|
const total = renderableMessages.length;
|
|
378
|
-
|
|
379
|
-
if (limit !== null) {
|
|
380
|
-
const start = offset;
|
|
381
|
-
const page = limit === 0
|
|
382
|
-
? []
|
|
383
|
-
: renderableMessages.slice(start, start + limit);
|
|
384
|
-
const hasMore = limit === 0
|
|
385
|
-
? start < total
|
|
386
|
-
: start + limit < total;
|
|
387
|
-
return {
|
|
388
|
-
messages: page,
|
|
389
|
-
total,
|
|
390
|
-
hasMore,
|
|
391
|
-
offset,
|
|
392
|
-
limit,
|
|
393
|
-
};
|
|
394
|
-
}
|
|
385
|
+
const { page, hasMore } = sliceTailPage(renderableMessages, limit, offset);
|
|
395
386
|
|
|
396
387
|
return {
|
|
397
|
-
messages:
|
|
388
|
+
messages: page,
|
|
398
389
|
total,
|
|
399
|
-
hasMore
|
|
400
|
-
offset
|
|
401
|
-
limit
|
|
390
|
+
hasMore,
|
|
391
|
+
offset,
|
|
392
|
+
limit,
|
|
402
393
|
};
|
|
403
394
|
} catch (error) {
|
|
404
395
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -28,4 +28,12 @@ export class CursorSkillsProvider extends SkillsProvider {
|
|
|
28
28
|
},
|
|
29
29
|
];
|
|
30
30
|
}
|
|
31
|
+
|
|
32
|
+
protected async getGlobalSkillSource(): Promise<ProviderSkillSource> {
|
|
33
|
+
return {
|
|
34
|
+
scope: 'user',
|
|
35
|
+
rootDir: path.join(os.homedir(), '.cursor', 'skills'),
|
|
36
|
+
commandPrefix: '/',
|
|
37
|
+
};
|
|
38
|
+
}
|
|
31
39
|
}
|
|
@@ -12,17 +12,14 @@ import {
|
|
|
12
12
|
|
|
13
13
|
export const GEMINI_FALLBACK_MODELS: ProviderModelsDefinition = {
|
|
14
14
|
OPTIONS: [
|
|
15
|
-
{ value: 'gemini-3.1-pro-preview', label: 'Gemini 3.1 Pro Preview' },
|
|
16
|
-
{ value: 'gemini-3-pro-preview', label: 'Gemini 3 Pro Preview' },
|
|
17
15
|
{ value: 'gemini-3-flash-preview', label: 'Gemini 3 Flash Preview' },
|
|
16
|
+
{ value: 'gemini-3.1-flash-lite-preview', label: 'Gemini 3.1 Flash Lite Preview' },
|
|
18
17
|
{ value: 'gemini-2.5-flash', label: 'Gemini 2.5 Flash' },
|
|
19
|
-
{ value: 'gemini-2.5-
|
|
20
|
-
{ value: '
|
|
21
|
-
{ value: '
|
|
22
|
-
{ value: 'gemini-2.0-pro-exp', label: 'Gemini 2.0 Pro Experimental' },
|
|
23
|
-
{ value: 'gemini-2.0-flash-thinking-exp', label: 'Gemini 2.0 Flash Thinking' },
|
|
18
|
+
{ value: 'gemini-2.5-flash-lite', label: 'Gemini 2.5 Flash Lite' },
|
|
19
|
+
{ value: 'gemma-4-31b-it', label: 'Gemma 4 31B IT' },
|
|
20
|
+
{ value: 'gemma-4-26b-a4b-it', label: 'Gemma 4 26B A4B IT' },
|
|
24
21
|
],
|
|
25
|
-
DEFAULT: 'gemini-3
|
|
22
|
+
DEFAULT: 'gemini-3-flash-preview',
|
|
26
23
|
};
|
|
27
24
|
|
|
28
25
|
export class GeminiProviderModels implements IProviderModels {
|
|
@@ -5,7 +5,7 @@ import readline from 'node:readline';
|
|
|
5
5
|
import { sessionsDb } from '@/modules/database/index.js';
|
|
6
6
|
import type { IProviderSessions } from '@/shared/interfaces.js';
|
|
7
7
|
import type { AnyRecord, FetchHistoryOptions, FetchHistoryResult, NormalizedMessage } from '@/shared/types.js';
|
|
8
|
-
import { createNormalizedMessage, generateMessageId, readObjectRecord } from '@/shared/utils.js';
|
|
8
|
+
import { createNormalizedMessage, generateMessageId, readObjectRecord, sliceTailPage } from '@/shared/utils.js';
|
|
9
9
|
|
|
10
10
|
const PROVIDER = 'gemini';
|
|
11
11
|
|
|
@@ -518,9 +518,9 @@ export class GeminiSessionsProvider implements IProviderSessions {
|
|
|
518
518
|
|
|
519
519
|
const start = Math.max(0, offset);
|
|
520
520
|
const pageLimit = limit === null ? null : Math.max(0, limit);
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
521
|
+
// Tail pagination via the shared contract: offset 0 returns the most
|
|
522
|
+
// recent page, matching every other provider.
|
|
523
|
+
const { page, hasMore } = sliceTailPage(normalized, pageLimit, start);
|
|
524
524
|
let total = 0;
|
|
525
525
|
for (const msg of normalized) {
|
|
526
526
|
if (msg.kind !== 'tool_result') {
|
|
@@ -529,9 +529,9 @@ export class GeminiSessionsProvider implements IProviderSessions {
|
|
|
529
529
|
}
|
|
530
530
|
|
|
531
531
|
return {
|
|
532
|
-
messages,
|
|
532
|
+
messages: page,
|
|
533
533
|
total,
|
|
534
|
-
hasMore
|
|
534
|
+
hasMore,
|
|
535
535
|
offset: start,
|
|
536
536
|
limit: pageLimit,
|
|
537
537
|
tokenUsage: result.tokenUsage,
|
|
@@ -33,4 +33,12 @@ export class GeminiSkillsProvider extends SkillsProvider {
|
|
|
33
33
|
},
|
|
34
34
|
];
|
|
35
35
|
}
|
|
36
|
+
|
|
37
|
+
protected async getGlobalSkillSource(): Promise<ProviderSkillSource> {
|
|
38
|
+
return {
|
|
39
|
+
scope: 'user',
|
|
40
|
+
rootDir: path.join(os.homedir(), '.gemini', 'skills'),
|
|
41
|
+
commandPrefix: '/',
|
|
42
|
+
};
|
|
43
|
+
}
|
|
36
44
|
}
|
|
@@ -112,7 +112,21 @@ export class OpenCodeSessionSynchronizer implements IProviderSessionSynchronizer
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
const fallbackTitle = 'Untitled OpenCode Session';
|
|
115
|
-
const
|
|
115
|
+
const pendingAppSession = sessionsDb.getSessionByProviderSessionId(sessionId)
|
|
116
|
+
?? sessionsDb.getSessionById(sessionId)
|
|
117
|
+
?? sessionsDb.findLatestPendingAppSession(this.provider, projectPath);
|
|
118
|
+
if (pendingAppSession && !pendingAppSession.provider_session_id) {
|
|
119
|
+
// Slow networks can let the sqlite watcher index opencode.db before the
|
|
120
|
+
// runtime reports its provider id back through the websocket mapping.
|
|
121
|
+
// Bind that id to the fresh app row first so the watcher does not create
|
|
122
|
+
// a temporary provider-id sidebar entry for the same session.
|
|
123
|
+
sessionsDb.assignProviderSessionId(pendingAppSession.session_id, sessionId);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// App-created sessions are keyed by an app id, so disk-discovered provider
|
|
127
|
+
// ids must be resolved through the provider-id mapping first.
|
|
128
|
+
const existingSession = sessionsDb.getSessionByProviderSessionId(sessionId)
|
|
129
|
+
?? sessionsDb.getSessionById(sessionId);
|
|
116
130
|
const existingName = existingSession?.custom_name;
|
|
117
131
|
const nextName = existingName && existingName !== fallbackTitle
|
|
118
132
|
? existingName
|
|
@@ -120,7 +134,9 @@ export class OpenCodeSessionSynchronizer implements IProviderSessionSynchronizer
|
|
|
120
134
|
|
|
121
135
|
// OpenCode stores every session in one shared sqlite database, so jsonl_path
|
|
122
136
|
// must stay null to avoid deleting opencode.db when one app session is removed.
|
|
123
|
-
|
|
137
|
+
// Return the canonical stored row id so watcher-triggered sidebar updates
|
|
138
|
+
// stay on the app session once provider_session_id has already been mapped.
|
|
139
|
+
return sessionsDb.createSession(
|
|
124
140
|
sessionId,
|
|
125
141
|
this.provider,
|
|
126
142
|
projectPath,
|
|
@@ -129,8 +145,6 @@ export class OpenCodeSessionSynchronizer implements IProviderSessionSynchronizer
|
|
|
129
145
|
normalizeProviderTimestamp(row.time_updated ?? row.time_created),
|
|
130
146
|
null,
|
|
131
147
|
);
|
|
132
|
-
|
|
133
|
-
return sessionId;
|
|
134
148
|
}
|
|
135
149
|
|
|
136
150
|
private readFirstUserText(db: Database.Database, sessionId: string): string | undefined {
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
readObjectRecord,
|
|
13
13
|
readJsonRecord,
|
|
14
14
|
readOptionalString,
|
|
15
|
+
sliceTailPage,
|
|
15
16
|
} from '@/shared/utils.js';
|
|
16
17
|
|
|
17
18
|
const PROVIDER = 'opencode';
|
|
@@ -325,6 +326,9 @@ export class OpenCodeSessionsProvider implements IProviderSessions {
|
|
|
325
326
|
options: FetchHistoryOptions = {},
|
|
326
327
|
): Promise<FetchHistoryResult> {
|
|
327
328
|
const { limit = null, offset = 0 } = options;
|
|
329
|
+
// OpenCode's shared sqlite database keys messages by the provider-native
|
|
330
|
+
// session id, not the app-facing id this method is addressed with.
|
|
331
|
+
const providerSessionId = options.providerSessionId ?? sessionId;
|
|
328
332
|
const db = openOpenCodeDatabase();
|
|
329
333
|
if (!db) {
|
|
330
334
|
return { messages: [], total: 0, hasMore: false, offset: 0, limit: null };
|
|
@@ -349,27 +353,20 @@ export class OpenCodeSessionsProvider implements IProviderSessions {
|
|
|
349
353
|
m.id,
|
|
350
354
|
COALESCE(p.time_created, 0),
|
|
351
355
|
p.id
|
|
352
|
-
`).all(
|
|
356
|
+
`).all(providerSessionId) as OpenCodeHistoryRow[];
|
|
353
357
|
|
|
354
358
|
const normalized = this.normalizeHistoryRows(rows, sessionId);
|
|
355
|
-
const tokenUsage = aggregateOpenCodeSessionTokenUsage(db,
|
|
359
|
+
const tokenUsage = aggregateOpenCodeSessionTokenUsage(db, providerSessionId);
|
|
356
360
|
|
|
357
361
|
const normalizedOffset = Math.max(0, offset);
|
|
358
362
|
const normalizedLimit = limit === null ? null : Math.max(0, limit);
|
|
359
363
|
const total = normalized.length;
|
|
360
|
-
const
|
|
361
|
-
? normalized
|
|
362
|
-
: normalized.slice(
|
|
363
|
-
Math.max(0, total - normalizedOffset - normalizedLimit),
|
|
364
|
-
Math.max(0, total - normalizedOffset),
|
|
365
|
-
);
|
|
364
|
+
const { page, hasMore } = sliceTailPage(normalized, normalizedLimit, normalizedOffset);
|
|
366
365
|
|
|
367
366
|
return {
|
|
368
|
-
messages,
|
|
367
|
+
messages: page,
|
|
369
368
|
total,
|
|
370
|
-
hasMore
|
|
371
|
-
? false
|
|
372
|
-
: Math.max(0, total - normalizedOffset - normalizedLimit) > 0,
|
|
369
|
+
hasMore,
|
|
373
370
|
offset: normalizedOffset,
|
|
374
371
|
limit: normalizedLimit,
|
|
375
372
|
tokenUsage,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import express, { type Request, type Response } from 'express';
|
|
2
2
|
|
|
3
3
|
import { providerAuthService } from '@/modules/providers/services/provider-auth.service.js';
|
|
4
|
+
import { providerCapabilitiesService } from '@/modules/providers/services/provider-capabilities.service.js';
|
|
4
5
|
import { providerMcpService } from '@/modules/providers/services/mcp.service.js';
|
|
5
6
|
import { providerModelsService } from '@/modules/providers/services/provider-models.service.js';
|
|
6
7
|
import { providerSkillsService } from '@/modules/providers/services/skills.service.js';
|
|
@@ -11,6 +12,8 @@ import type {
|
|
|
11
12
|
McpScope,
|
|
12
13
|
McpTransport,
|
|
13
14
|
ProviderChangeActiveModelInput,
|
|
15
|
+
ProviderSkillCreateFile,
|
|
16
|
+
ProviderSkillCreateInput,
|
|
14
17
|
UpsertProviderMcpServerInput,
|
|
15
18
|
} from '@/shared/types.js';
|
|
16
19
|
import { AppError, asyncHandler, createApiSuccessResponse } from '@/shared/utils.js';
|
|
@@ -178,6 +181,104 @@ const parseMcpUpsertPayload = (payload: unknown): UpsertProviderMcpServerInput =
|
|
|
178
181
|
};
|
|
179
182
|
};
|
|
180
183
|
|
|
184
|
+
const parseProviderSkillCreatePayload = (payload: unknown): ProviderSkillCreateInput => {
|
|
185
|
+
if (!payload || typeof payload !== 'object') {
|
|
186
|
+
throw new AppError('Request body must be an object.', {
|
|
187
|
+
code: 'INVALID_REQUEST_BODY',
|
|
188
|
+
statusCode: 400,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const body = payload as Record<string, unknown>;
|
|
193
|
+
const rawEntries = Array.isArray(body.entries)
|
|
194
|
+
? body.entries
|
|
195
|
+
: typeof body.content === 'string'
|
|
196
|
+
? [{
|
|
197
|
+
content: body.content,
|
|
198
|
+
directoryName: body.directoryName,
|
|
199
|
+
fileName: body.fileName,
|
|
200
|
+
files: body.files,
|
|
201
|
+
}]
|
|
202
|
+
: null;
|
|
203
|
+
|
|
204
|
+
if (!rawEntries || rawEntries.length === 0) {
|
|
205
|
+
throw new AppError('At least one skill entry is required.', {
|
|
206
|
+
code: 'PROVIDER_SKILLS_REQUIRED',
|
|
207
|
+
statusCode: 400,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const entries = rawEntries.map((entry, index) => {
|
|
212
|
+
if (!entry || typeof entry !== 'object') {
|
|
213
|
+
throw new AppError(`Skill entry ${index + 1} must be an object.`, {
|
|
214
|
+
code: 'INVALID_REQUEST_BODY',
|
|
215
|
+
statusCode: 400,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const record = entry as Record<string, unknown>;
|
|
220
|
+
const content = typeof record.content === 'string' ? record.content : '';
|
|
221
|
+
const directoryName = readOptionalQueryString(record.directoryName);
|
|
222
|
+
const fileName = readOptionalQueryString(record.fileName);
|
|
223
|
+
const rawFiles = record.files;
|
|
224
|
+
|
|
225
|
+
if (!content.trim()) {
|
|
226
|
+
throw new AppError(`Skill entry ${index + 1} must include markdown content.`, {
|
|
227
|
+
code: 'PROVIDER_SKILL_CONTENT_REQUIRED',
|
|
228
|
+
statusCode: 400,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (rawFiles !== undefined && !Array.isArray(rawFiles)) {
|
|
233
|
+
throw new AppError(`Skill entry ${index + 1} files must be an array.`, {
|
|
234
|
+
code: 'INVALID_REQUEST_BODY',
|
|
235
|
+
statusCode: 400,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const files: ProviderSkillCreateFile[] | undefined = rawFiles?.map((file, fileIndex) => {
|
|
240
|
+
if (!file || typeof file !== 'object') {
|
|
241
|
+
throw new AppError(`Skill entry ${index + 1} file ${fileIndex + 1} must be an object.`, {
|
|
242
|
+
code: 'INVALID_REQUEST_BODY',
|
|
243
|
+
statusCode: 400,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const fileRecord = file as Record<string, unknown>;
|
|
248
|
+
const relativePath = readOptionalQueryString(fileRecord.relativePath);
|
|
249
|
+
const fileContent = typeof fileRecord.content === 'string' ? fileRecord.content : null;
|
|
250
|
+
const encoding = fileRecord.encoding === 'utf8' || fileRecord.encoding === 'base64'
|
|
251
|
+
? fileRecord.encoding
|
|
252
|
+
: null;
|
|
253
|
+
|
|
254
|
+
if (!relativePath || fileContent === null || !encoding) {
|
|
255
|
+
throw new AppError(
|
|
256
|
+
`Skill entry ${index + 1} file ${fileIndex + 1} requires relativePath, content, and encoding.`,
|
|
257
|
+
{
|
|
258
|
+
code: 'INVALID_REQUEST_BODY',
|
|
259
|
+
statusCode: 400,
|
|
260
|
+
},
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
relativePath,
|
|
266
|
+
content: fileContent,
|
|
267
|
+
encoding,
|
|
268
|
+
};
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
content,
|
|
273
|
+
directoryName,
|
|
274
|
+
fileName,
|
|
275
|
+
files,
|
|
276
|
+
};
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
return { entries };
|
|
280
|
+
};
|
|
281
|
+
|
|
181
282
|
const parseProvider = (value: unknown): LLMProvider => {
|
|
182
283
|
const normalized = normalizeProviderParam(value);
|
|
183
284
|
if (
|
|
@@ -319,6 +420,27 @@ router.get(
|
|
|
319
420
|
}),
|
|
320
421
|
);
|
|
321
422
|
|
|
423
|
+
router.post(
|
|
424
|
+
'/:provider/skills',
|
|
425
|
+
asyncHandler(async (req: Request, res: Response) => {
|
|
426
|
+
const provider = parseProvider(req.params.provider);
|
|
427
|
+
const input = parseProviderSkillCreatePayload(req.body);
|
|
428
|
+
const skills = await providerSkillsService.addProviderSkills(provider, input);
|
|
429
|
+
res.json(createApiSuccessResponse({ provider, skills }));
|
|
430
|
+
}),
|
|
431
|
+
);
|
|
432
|
+
|
|
433
|
+
router.delete(
|
|
434
|
+
'/:provider/skills/:directoryName',
|
|
435
|
+
asyncHandler(async (req: Request, res: Response) => {
|
|
436
|
+
const provider = parseProvider(req.params.provider);
|
|
437
|
+
const result = await providerSkillsService.removeProviderSkill(provider, {
|
|
438
|
+
directoryName: readPathParam(req.params.directoryName, 'directoryName'),
|
|
439
|
+
});
|
|
440
|
+
res.json(createApiSuccessResponse(result));
|
|
441
|
+
}),
|
|
442
|
+
);
|
|
443
|
+
|
|
322
444
|
// ----------------- MCP routes -----------------
|
|
323
445
|
router.get(
|
|
324
446
|
'/:provider/mcp/servers',
|
|
@@ -382,7 +504,51 @@ router.post(
|
|
|
382
504
|
}),
|
|
383
505
|
);
|
|
384
506
|
|
|
507
|
+
router.get(
|
|
508
|
+
'/capabilities',
|
|
509
|
+
asyncHandler(async (_req: Request, res: Response) => {
|
|
510
|
+
res.json(createApiSuccessResponse({
|
|
511
|
+
providers: providerCapabilitiesService.listAllProviderCapabilities(),
|
|
512
|
+
}));
|
|
513
|
+
}),
|
|
514
|
+
);
|
|
515
|
+
|
|
516
|
+
router.get(
|
|
517
|
+
'/:provider/capabilities',
|
|
518
|
+
asyncHandler(async (req: Request, res: Response) => {
|
|
519
|
+
const provider = parseProvider(req.params.provider);
|
|
520
|
+
res.json(createApiSuccessResponse(
|
|
521
|
+
providerCapabilitiesService.getProviderCapabilities(provider),
|
|
522
|
+
));
|
|
523
|
+
}),
|
|
524
|
+
);
|
|
525
|
+
|
|
385
526
|
// ----------------- Session routes -----------------
|
|
527
|
+
/**
|
|
528
|
+
* Session gateway entry point: allocates the stable app-facing session id for
|
|
529
|
+
* a brand-new chat. The frontend must call this before the first `chat.send`
|
|
530
|
+
* so the session id in the URL, the store, and the websocket all agree from
|
|
531
|
+
* the very first message — there is no client-visible session-id handoff.
|
|
532
|
+
*/
|
|
533
|
+
router.post(
|
|
534
|
+
'/sessions',
|
|
535
|
+
asyncHandler(async (req: Request, res: Response) => {
|
|
536
|
+
const body = (req.body ?? {}) as Record<string, unknown>;
|
|
537
|
+
const provider = parseProvider(body.provider);
|
|
538
|
+
const projectPath = typeof body.projectPath === 'string' ? body.projectPath : '';
|
|
539
|
+
const result = sessionsService.createAppSession(provider, projectPath);
|
|
540
|
+
res.status(201).json(createApiSuccessResponse(result));
|
|
541
|
+
}),
|
|
542
|
+
);
|
|
543
|
+
|
|
544
|
+
router.get(
|
|
545
|
+
'/sessions/running',
|
|
546
|
+
asyncHandler(async (_req: Request, res: Response) => {
|
|
547
|
+
const sessions = sessionsService.listRunningSessions();
|
|
548
|
+
res.json(createApiSuccessResponse({ sessions }));
|
|
549
|
+
}),
|
|
550
|
+
);
|
|
551
|
+
|
|
386
552
|
router.get(
|
|
387
553
|
'/sessions/archived',
|
|
388
554
|
asyncHandler(async (_req: Request, res: Response) => {
|
|
@@ -459,7 +625,7 @@ router.get(
|
|
|
459
625
|
limit,
|
|
460
626
|
offset,
|
|
461
627
|
});
|
|
462
|
-
res.json(result);
|
|
628
|
+
res.json(createApiSuccessResponse(result));
|
|
463
629
|
}),
|
|
464
630
|
);
|
|
465
631
|
|
|
@@ -80,4 +80,30 @@ export const providerMcpService = {
|
|
|
80
80
|
|
|
81
81
|
return results;
|
|
82
82
|
},
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Removes one MCP server from every provider. Mirrors `addMcpServerToAllProviders`
|
|
86
|
+
* by iterating the live provider registry, so callers stay in sync with which
|
|
87
|
+
* providers exist instead of maintaining their own provider list.
|
|
88
|
+
*/
|
|
89
|
+
async removeMcpServerFromAllProviders(
|
|
90
|
+
input: { name: string; scope?: McpScope; workspacePath?: string },
|
|
91
|
+
): Promise<Array<{ provider: LLMProvider; removed: boolean; error?: string }>> {
|
|
92
|
+
const results: Array<{ provider: LLMProvider; removed: boolean; error?: string }> = [];
|
|
93
|
+
const providers = providerRegistry.listProviders();
|
|
94
|
+
for (const provider of providers) {
|
|
95
|
+
try {
|
|
96
|
+
const result = await provider.mcp.removeServer(input);
|
|
97
|
+
results.push({ provider: provider.id, removed: result.removed });
|
|
98
|
+
} catch (error) {
|
|
99
|
+
results.push({
|
|
100
|
+
provider: provider.id,
|
|
101
|
+
removed: false,
|
|
102
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return results;
|
|
108
|
+
},
|
|
83
109
|
};
|