@siteboon/claude-code-ui 1.25.2 → 1.26.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.
Files changed (43) hide show
  1. package/README.de.md +239 -0
  2. package/README.ja.md +115 -230
  3. package/README.ko.md +116 -231
  4. package/README.md +2 -1
  5. package/README.ru.md +75 -54
  6. package/README.zh-CN.md +121 -238
  7. package/dist/assets/index-C08k8QbP.css +32 -0
  8. package/dist/assets/{index-DF_FFT3b.js → index-DnXcHp5q.js} +249 -242
  9. package/dist/index.html +2 -2
  10. package/dist/sw.js +59 -3
  11. package/package.json +3 -2
  12. package/server/claude-sdk.js +106 -62
  13. package/server/cli.js +10 -7
  14. package/server/cursor-cli.js +59 -73
  15. package/server/database/db.js +142 -1
  16. package/server/database/init.sql +28 -1
  17. package/server/gemini-cli.js +46 -48
  18. package/server/gemini-response-handler.js +12 -73
  19. package/server/index.js +82 -55
  20. package/server/middleware/auth.js +2 -2
  21. package/server/openai-codex.js +43 -28
  22. package/server/projects.js +1 -1
  23. package/server/providers/claude/adapter.js +278 -0
  24. package/server/providers/codex/adapter.js +248 -0
  25. package/server/providers/cursor/adapter.js +353 -0
  26. package/server/providers/gemini/adapter.js +186 -0
  27. package/server/providers/registry.js +44 -0
  28. package/server/providers/types.js +119 -0
  29. package/server/providers/utils.js +29 -0
  30. package/server/routes/agent.js +7 -5
  31. package/server/routes/cli-auth.js +38 -0
  32. package/server/routes/codex.js +1 -19
  33. package/server/routes/gemini.js +0 -30
  34. package/server/routes/git.js +48 -20
  35. package/server/routes/messages.js +61 -0
  36. package/server/routes/plugins.js +5 -1
  37. package/server/routes/settings.js +99 -1
  38. package/server/routes/taskmaster.js +2 -2
  39. package/server/services/notification-orchestrator.js +227 -0
  40. package/server/services/vapid-keys.js +35 -0
  41. package/server/utils/plugin-loader.js +53 -4
  42. package/shared/networkHosts.js +22 -0
  43. package/dist/assets/index-WNTmA_ug.css +0 -32
@@ -0,0 +1,278 @@
1
+ /**
2
+ * Claude provider adapter.
3
+ *
4
+ * Normalizes Claude SDK session history into NormalizedMessage format.
5
+ * @module adapters/claude
6
+ */
7
+
8
+ import { getSessionMessages } from '../../projects.js';
9
+ import { createNormalizedMessage, generateMessageId } from '../types.js';
10
+ import { isInternalContent } from '../utils.js';
11
+
12
+ const PROVIDER = 'claude';
13
+
14
+ /**
15
+ * Normalize a raw JSONL message or realtime SDK event into NormalizedMessage(s).
16
+ * Handles both history entries (JSONL `{ message: { role, content } }`) and
17
+ * realtime streaming events (`content_block_delta`, `content_block_stop`, etc.).
18
+ * @param {object} raw - A single entry from JSONL or a live SDK event
19
+ * @param {string} sessionId
20
+ * @returns {import('../types.js').NormalizedMessage[]}
21
+ */
22
+ export function normalizeMessage(raw, sessionId) {
23
+ // ── Streaming events (realtime) ──────────────────────────────────────────
24
+ if (raw.type === 'content_block_delta' && raw.delta?.text) {
25
+ return [createNormalizedMessage({ kind: 'stream_delta', content: raw.delta.text, sessionId, provider: PROVIDER })];
26
+ }
27
+ if (raw.type === 'content_block_stop') {
28
+ return [createNormalizedMessage({ kind: 'stream_end', sessionId, provider: PROVIDER })];
29
+ }
30
+
31
+ // ── History / full-message events ────────────────────────────────────────
32
+ const messages = [];
33
+ const ts = raw.timestamp || new Date().toISOString();
34
+ const baseId = raw.uuid || generateMessageId('claude');
35
+
36
+ // User message
37
+ if (raw.message?.role === 'user' && raw.message?.content) {
38
+ if (Array.isArray(raw.message.content)) {
39
+ // Handle tool_result parts
40
+ for (const part of raw.message.content) {
41
+ if (part.type === 'tool_result') {
42
+ messages.push(createNormalizedMessage({
43
+ id: `${baseId}_tr_${part.tool_use_id}`,
44
+ sessionId,
45
+ timestamp: ts,
46
+ provider: PROVIDER,
47
+ kind: 'tool_result',
48
+ toolId: part.tool_use_id,
49
+ content: typeof part.content === 'string' ? part.content : JSON.stringify(part.content),
50
+ isError: Boolean(part.is_error),
51
+ subagentTools: raw.subagentTools,
52
+ toolUseResult: raw.toolUseResult,
53
+ }));
54
+ } else if (part.type === 'text') {
55
+ // Regular text parts from user
56
+ const text = part.text || '';
57
+ if (text && !isInternalContent(text)) {
58
+ messages.push(createNormalizedMessage({
59
+ id: `${baseId}_text`,
60
+ sessionId,
61
+ timestamp: ts,
62
+ provider: PROVIDER,
63
+ kind: 'text',
64
+ role: 'user',
65
+ content: text,
66
+ }));
67
+ }
68
+ }
69
+ }
70
+
71
+ // If no text parts were found, check if it's a pure user message
72
+ if (messages.length === 0) {
73
+ const textParts = raw.message.content
74
+ .filter(p => p.type === 'text')
75
+ .map(p => p.text)
76
+ .filter(Boolean)
77
+ .join('\n');
78
+ if (textParts && !isInternalContent(textParts)) {
79
+ messages.push(createNormalizedMessage({
80
+ id: `${baseId}_text`,
81
+ sessionId,
82
+ timestamp: ts,
83
+ provider: PROVIDER,
84
+ kind: 'text',
85
+ role: 'user',
86
+ content: textParts,
87
+ }));
88
+ }
89
+ }
90
+ } else if (typeof raw.message.content === 'string') {
91
+ const text = raw.message.content;
92
+ if (text && !isInternalContent(text)) {
93
+ messages.push(createNormalizedMessage({
94
+ id: baseId,
95
+ sessionId,
96
+ timestamp: ts,
97
+ provider: PROVIDER,
98
+ kind: 'text',
99
+ role: 'user',
100
+ content: text,
101
+ }));
102
+ }
103
+ }
104
+ return messages;
105
+ }
106
+
107
+ // Thinking message
108
+ if (raw.type === 'thinking' && raw.message?.content) {
109
+ messages.push(createNormalizedMessage({
110
+ id: baseId,
111
+ sessionId,
112
+ timestamp: ts,
113
+ provider: PROVIDER,
114
+ kind: 'thinking',
115
+ content: raw.message.content,
116
+ }));
117
+ return messages;
118
+ }
119
+
120
+ // Tool use result (codex-style in Claude)
121
+ if (raw.type === 'tool_use' && raw.toolName) {
122
+ messages.push(createNormalizedMessage({
123
+ id: baseId,
124
+ sessionId,
125
+ timestamp: ts,
126
+ provider: PROVIDER,
127
+ kind: 'tool_use',
128
+ toolName: raw.toolName,
129
+ toolInput: raw.toolInput,
130
+ toolId: raw.toolCallId || baseId,
131
+ }));
132
+ return messages;
133
+ }
134
+
135
+ if (raw.type === 'tool_result') {
136
+ messages.push(createNormalizedMessage({
137
+ id: baseId,
138
+ sessionId,
139
+ timestamp: ts,
140
+ provider: PROVIDER,
141
+ kind: 'tool_result',
142
+ toolId: raw.toolCallId || '',
143
+ content: raw.output || '',
144
+ isError: false,
145
+ }));
146
+ return messages;
147
+ }
148
+
149
+ // Assistant message
150
+ if (raw.message?.role === 'assistant' && raw.message?.content) {
151
+ if (Array.isArray(raw.message.content)) {
152
+ let partIndex = 0;
153
+ for (const part of raw.message.content) {
154
+ if (part.type === 'text' && part.text) {
155
+ messages.push(createNormalizedMessage({
156
+ id: `${baseId}_${partIndex}`,
157
+ sessionId,
158
+ timestamp: ts,
159
+ provider: PROVIDER,
160
+ kind: 'text',
161
+ role: 'assistant',
162
+ content: part.text,
163
+ }));
164
+ } else if (part.type === 'tool_use') {
165
+ messages.push(createNormalizedMessage({
166
+ id: `${baseId}_${partIndex}`,
167
+ sessionId,
168
+ timestamp: ts,
169
+ provider: PROVIDER,
170
+ kind: 'tool_use',
171
+ toolName: part.name,
172
+ toolInput: part.input,
173
+ toolId: part.id,
174
+ }));
175
+ } else if (part.type === 'thinking' && part.thinking) {
176
+ messages.push(createNormalizedMessage({
177
+ id: `${baseId}_${partIndex}`,
178
+ sessionId,
179
+ timestamp: ts,
180
+ provider: PROVIDER,
181
+ kind: 'thinking',
182
+ content: part.thinking,
183
+ }));
184
+ }
185
+ partIndex++;
186
+ }
187
+ } else if (typeof raw.message.content === 'string') {
188
+ messages.push(createNormalizedMessage({
189
+ id: baseId,
190
+ sessionId,
191
+ timestamp: ts,
192
+ provider: PROVIDER,
193
+ kind: 'text',
194
+ role: 'assistant',
195
+ content: raw.message.content,
196
+ }));
197
+ }
198
+ return messages;
199
+ }
200
+
201
+ return messages;
202
+ }
203
+
204
+ /**
205
+ * @type {import('../types.js').ProviderAdapter}
206
+ */
207
+ export const claudeAdapter = {
208
+ normalizeMessage,
209
+
210
+ /**
211
+ * Fetch session history from JSONL files, returning normalized messages.
212
+ */
213
+ async fetchHistory(sessionId, opts = {}) {
214
+ const { projectName, limit = null, offset = 0 } = opts;
215
+ if (!projectName) {
216
+ return { messages: [], total: 0, hasMore: false, offset: 0, limit: null };
217
+ }
218
+
219
+ let result;
220
+ try {
221
+ result = await getSessionMessages(projectName, sessionId, limit, offset);
222
+ } catch (error) {
223
+ console.warn(`[ClaudeAdapter] Failed to load session ${sessionId}:`, error.message);
224
+ return { messages: [], total: 0, hasMore: false, offset: 0, limit: null };
225
+ }
226
+
227
+ // getSessionMessages returns either an array (no limit) or { messages, total, hasMore }
228
+ const rawMessages = Array.isArray(result) ? result : (result.messages || []);
229
+ const total = Array.isArray(result) ? rawMessages.length : (result.total || 0);
230
+ const hasMore = Array.isArray(result) ? false : Boolean(result.hasMore);
231
+
232
+ // First pass: collect tool results for attachment to tool_use messages
233
+ const toolResultMap = new Map();
234
+ for (const raw of rawMessages) {
235
+ if (raw.message?.role === 'user' && Array.isArray(raw.message?.content)) {
236
+ for (const part of raw.message.content) {
237
+ if (part.type === 'tool_result') {
238
+ toolResultMap.set(part.tool_use_id, {
239
+ content: part.content,
240
+ isError: Boolean(part.is_error),
241
+ timestamp: raw.timestamp,
242
+ subagentTools: raw.subagentTools,
243
+ toolUseResult: raw.toolUseResult,
244
+ });
245
+ }
246
+ }
247
+ }
248
+ }
249
+
250
+ // Second pass: normalize all messages
251
+ const normalized = [];
252
+ for (const raw of rawMessages) {
253
+ const entries = normalizeMessage(raw, sessionId);
254
+ normalized.push(...entries);
255
+ }
256
+
257
+ // Attach tool results to their corresponding tool_use messages
258
+ for (const msg of normalized) {
259
+ if (msg.kind === 'tool_use' && msg.toolId && toolResultMap.has(msg.toolId)) {
260
+ const tr = toolResultMap.get(msg.toolId);
261
+ msg.toolResult = {
262
+ content: typeof tr.content === 'string' ? tr.content : JSON.stringify(tr.content),
263
+ isError: tr.isError,
264
+ toolUseResult: tr.toolUseResult,
265
+ };
266
+ msg.subagentTools = tr.subagentTools;
267
+ }
268
+ }
269
+
270
+ return {
271
+ messages: normalized,
272
+ total,
273
+ hasMore,
274
+ offset,
275
+ limit,
276
+ };
277
+ },
278
+ };
@@ -0,0 +1,248 @@
1
+ /**
2
+ * Codex (OpenAI) provider adapter.
3
+ *
4
+ * Normalizes Codex SDK session history into NormalizedMessage format.
5
+ * @module adapters/codex
6
+ */
7
+
8
+ import { getCodexSessionMessages } from '../../projects.js';
9
+ import { createNormalizedMessage, generateMessageId } from '../types.js';
10
+
11
+ const PROVIDER = 'codex';
12
+
13
+ /**
14
+ * Normalize a raw Codex JSONL message into NormalizedMessage(s).
15
+ * @param {object} raw - A single parsed message from Codex JSONL
16
+ * @param {string} sessionId
17
+ * @returns {import('../types.js').NormalizedMessage[]}
18
+ */
19
+ function normalizeCodexHistoryEntry(raw, sessionId) {
20
+ const ts = raw.timestamp || new Date().toISOString();
21
+ const baseId = raw.uuid || generateMessageId('codex');
22
+
23
+ // User message
24
+ if (raw.message?.role === 'user') {
25
+ const content = typeof raw.message.content === 'string'
26
+ ? raw.message.content
27
+ : Array.isArray(raw.message.content)
28
+ ? raw.message.content.map(p => typeof p === 'string' ? p : p?.text || '').filter(Boolean).join('\n')
29
+ : String(raw.message.content || '');
30
+ if (!content.trim()) return [];
31
+ return [createNormalizedMessage({
32
+ id: baseId,
33
+ sessionId,
34
+ timestamp: ts,
35
+ provider: PROVIDER,
36
+ kind: 'text',
37
+ role: 'user',
38
+ content,
39
+ })];
40
+ }
41
+
42
+ // Assistant message
43
+ if (raw.message?.role === 'assistant') {
44
+ const content = typeof raw.message.content === 'string'
45
+ ? raw.message.content
46
+ : Array.isArray(raw.message.content)
47
+ ? raw.message.content.map(p => typeof p === 'string' ? p : p?.text || '').filter(Boolean).join('\n')
48
+ : '';
49
+ if (!content.trim()) return [];
50
+ return [createNormalizedMessage({
51
+ id: baseId,
52
+ sessionId,
53
+ timestamp: ts,
54
+ provider: PROVIDER,
55
+ kind: 'text',
56
+ role: 'assistant',
57
+ content,
58
+ })];
59
+ }
60
+
61
+ // Thinking/reasoning
62
+ if (raw.type === 'thinking' || raw.isReasoning) {
63
+ return [createNormalizedMessage({
64
+ id: baseId,
65
+ sessionId,
66
+ timestamp: ts,
67
+ provider: PROVIDER,
68
+ kind: 'thinking',
69
+ content: raw.message?.content || '',
70
+ })];
71
+ }
72
+
73
+ // Tool use
74
+ if (raw.type === 'tool_use' || raw.toolName) {
75
+ return [createNormalizedMessage({
76
+ id: baseId,
77
+ sessionId,
78
+ timestamp: ts,
79
+ provider: PROVIDER,
80
+ kind: 'tool_use',
81
+ toolName: raw.toolName || 'Unknown',
82
+ toolInput: raw.toolInput,
83
+ toolId: raw.toolCallId || baseId,
84
+ })];
85
+ }
86
+
87
+ // Tool result
88
+ if (raw.type === 'tool_result') {
89
+ return [createNormalizedMessage({
90
+ id: baseId,
91
+ sessionId,
92
+ timestamp: ts,
93
+ provider: PROVIDER,
94
+ kind: 'tool_result',
95
+ toolId: raw.toolCallId || '',
96
+ content: raw.output || '',
97
+ isError: Boolean(raw.isError),
98
+ })];
99
+ }
100
+
101
+ return [];
102
+ }
103
+
104
+ /**
105
+ * Normalize a raw Codex event (history JSONL or transformed SDK event) into NormalizedMessage(s).
106
+ * @param {object} raw - A history entry (has raw.message.role) or transformed SDK event (has raw.type)
107
+ * @param {string} sessionId
108
+ * @returns {import('../types.js').NormalizedMessage[]}
109
+ */
110
+ export function normalizeMessage(raw, sessionId) {
111
+ // History format: has message.role
112
+ if (raw.message?.role) {
113
+ return normalizeCodexHistoryEntry(raw, sessionId);
114
+ }
115
+
116
+ const ts = raw.timestamp || new Date().toISOString();
117
+ const baseId = raw.uuid || generateMessageId('codex');
118
+
119
+ // SDK event format (output of transformCodexEvent)
120
+ if (raw.type === 'item') {
121
+ switch (raw.itemType) {
122
+ case 'agent_message':
123
+ return [createNormalizedMessage({
124
+ id: baseId, sessionId, timestamp: ts, provider: PROVIDER,
125
+ kind: 'text', role: 'assistant', content: raw.message?.content || '',
126
+ })];
127
+ case 'reasoning':
128
+ return [createNormalizedMessage({
129
+ id: baseId, sessionId, timestamp: ts, provider: PROVIDER,
130
+ kind: 'thinking', content: raw.message?.content || '',
131
+ })];
132
+ case 'command_execution':
133
+ return [createNormalizedMessage({
134
+ id: baseId, sessionId, timestamp: ts, provider: PROVIDER,
135
+ kind: 'tool_use', toolName: 'Bash', toolInput: { command: raw.command },
136
+ toolId: baseId,
137
+ output: raw.output, exitCode: raw.exitCode, status: raw.status,
138
+ })];
139
+ case 'file_change':
140
+ return [createNormalizedMessage({
141
+ id: baseId, sessionId, timestamp: ts, provider: PROVIDER,
142
+ kind: 'tool_use', toolName: 'FileChanges', toolInput: raw.changes,
143
+ toolId: baseId, status: raw.status,
144
+ })];
145
+ case 'mcp_tool_call':
146
+ return [createNormalizedMessage({
147
+ id: baseId, sessionId, timestamp: ts, provider: PROVIDER,
148
+ kind: 'tool_use', toolName: raw.tool || 'MCP', toolInput: raw.arguments,
149
+ toolId: baseId, server: raw.server, result: raw.result,
150
+ error: raw.error, status: raw.status,
151
+ })];
152
+ case 'web_search':
153
+ return [createNormalizedMessage({
154
+ id: baseId, sessionId, timestamp: ts, provider: PROVIDER,
155
+ kind: 'tool_use', toolName: 'WebSearch', toolInput: { query: raw.query },
156
+ toolId: baseId,
157
+ })];
158
+ case 'todo_list':
159
+ return [createNormalizedMessage({
160
+ id: baseId, sessionId, timestamp: ts, provider: PROVIDER,
161
+ kind: 'tool_use', toolName: 'TodoList', toolInput: { items: raw.items },
162
+ toolId: baseId,
163
+ })];
164
+ case 'error':
165
+ return [createNormalizedMessage({
166
+ id: baseId, sessionId, timestamp: ts, provider: PROVIDER,
167
+ kind: 'error', content: raw.message?.content || 'Unknown error',
168
+ })];
169
+ default:
170
+ // Unknown item type — pass through as generic tool_use
171
+ return [createNormalizedMessage({
172
+ id: baseId, sessionId, timestamp: ts, provider: PROVIDER,
173
+ kind: 'tool_use', toolName: raw.itemType || 'Unknown',
174
+ toolInput: raw.item || raw, toolId: baseId,
175
+ })];
176
+ }
177
+ }
178
+
179
+ if (raw.type === 'turn_complete') {
180
+ return [createNormalizedMessage({
181
+ id: baseId, sessionId, timestamp: ts, provider: PROVIDER,
182
+ kind: 'complete',
183
+ })];
184
+ }
185
+ if (raw.type === 'turn_failed') {
186
+ return [createNormalizedMessage({
187
+ id: baseId, sessionId, timestamp: ts, provider: PROVIDER,
188
+ kind: 'error', content: raw.error?.message || 'Turn failed',
189
+ })];
190
+ }
191
+
192
+ return [];
193
+ }
194
+
195
+ /**
196
+ * @type {import('../types.js').ProviderAdapter}
197
+ */
198
+ export const codexAdapter = {
199
+ normalizeMessage,
200
+ /**
201
+ * Fetch session history from Codex JSONL files.
202
+ */
203
+ async fetchHistory(sessionId, opts = {}) {
204
+ const { limit = null, offset = 0 } = opts;
205
+
206
+ let result;
207
+ try {
208
+ result = await getCodexSessionMessages(sessionId, limit, offset);
209
+ } catch (error) {
210
+ console.warn(`[CodexAdapter] Failed to load session ${sessionId}:`, error.message);
211
+ return { messages: [], total: 0, hasMore: false, offset: 0, limit: null };
212
+ }
213
+
214
+ const rawMessages = Array.isArray(result) ? result : (result.messages || []);
215
+ const total = Array.isArray(result) ? rawMessages.length : (result.total || 0);
216
+ const hasMore = Array.isArray(result) ? false : Boolean(result.hasMore);
217
+ const tokenUsage = result.tokenUsage || null;
218
+
219
+ const normalized = [];
220
+ for (const raw of rawMessages) {
221
+ const entries = normalizeCodexHistoryEntry(raw, sessionId);
222
+ normalized.push(...entries);
223
+ }
224
+
225
+ // Attach tool results to tool_use messages
226
+ const toolResultMap = new Map();
227
+ for (const msg of normalized) {
228
+ if (msg.kind === 'tool_result' && msg.toolId) {
229
+ toolResultMap.set(msg.toolId, msg);
230
+ }
231
+ }
232
+ for (const msg of normalized) {
233
+ if (msg.kind === 'tool_use' && msg.toolId && toolResultMap.has(msg.toolId)) {
234
+ const tr = toolResultMap.get(msg.toolId);
235
+ msg.toolResult = { content: tr.content, isError: tr.isError };
236
+ }
237
+ }
238
+
239
+ return {
240
+ messages: normalized,
241
+ total,
242
+ hasMore,
243
+ offset,
244
+ limit,
245
+ tokenUsage,
246
+ };
247
+ },
248
+ };