@myrialabs/clopen 0.2.12 → 0.2.13
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/backend/chat/stream-manager.ts +3 -0
- package/backend/engine/adapters/claude/stream.ts +2 -1
- package/backend/engine/types.ts +9 -0
- package/backend/mcp/config.ts +32 -6
- package/backend/terminal/stream-manager.ts +106 -155
- package/backend/ws/projects/crud.ts +3 -3
- package/backend/ws/terminal/persistence.ts +19 -33
- package/backend/ws/terminal/session.ts +37 -19
- package/bun.lock +6 -0
- package/frontend/components/chat/input/ChatInput.svelte +8 -0
- package/frontend/components/chat/input/composables/use-animations.svelte.ts +127 -111
- package/frontend/components/chat/input/composables/use-textarea-resize.svelte.ts +11 -1
- package/frontend/components/chat/widgets/FloatingTodoList.svelte +2 -2
- package/frontend/components/git/ChangesSection.svelte +104 -13
- package/frontend/components/terminal/Terminal.svelte +5 -1
- package/frontend/services/chat/chat.service.ts +8 -10
- package/frontend/services/terminal/project.service.ts +4 -60
- package/frontend/services/terminal/terminal.service.ts +18 -27
- package/frontend/stores/core/sessions.svelte.ts +6 -0
- package/package.json +4 -2
|
@@ -43,16 +43,14 @@ class ChatService {
|
|
|
43
43
|
|
|
44
44
|
static loadingTexts: string[] = [
|
|
45
45
|
'thinking', 'processing', 'analyzing', 'calculating', 'computing',
|
|
46
|
-
'strategizing', 'learningpatterns', '
|
|
47
|
-
'
|
|
48
|
-
'
|
|
49
|
-
'
|
|
50
|
-
'
|
|
51
|
-
'
|
|
52
|
-
'
|
|
53
|
-
'
|
|
54
|
-
'bootingreasoning', 'activatingmodules', 'triggeringaction', 'deployinglogic',
|
|
55
|
-
'maintainingstate', 'clearingcache', 'updating', 'reflecting', 'syncinglogic',
|
|
46
|
+
'strategizing', 'learningpatterns', 'adaptingmodels', 'evaluatingoptions',
|
|
47
|
+
'executingplans', 'simulatingscenarios', 'predictingoutcomes', 'planningactions',
|
|
48
|
+
'processinginputs', 'optimizing', 'generatingresponses', 'refininglogic',
|
|
49
|
+
'validatingoutputs', 'modulatingresponse', 'updatingmemory', 'recognizingpatterns',
|
|
50
|
+
'switchingcontext', 'allocatingresources', 'prioritizingtasks',
|
|
51
|
+
'developingawareness', 'buildingstrategies', 'assessingscenarios',
|
|
52
|
+
'bootingreasoning', 'triggeringaction', 'deployinglogic', 'synthesizinginformation',
|
|
53
|
+
'maintainingstate', 'updating', 'reflecting', 'syncinglogic',
|
|
56
54
|
'connectingdots', 'compilingideas', 'brainstorming', 'schedulingtasks'
|
|
57
55
|
].map(text => text + '...');
|
|
58
56
|
|
|
@@ -262,66 +262,10 @@ class TerminalProjectManager {
|
|
|
262
262
|
}
|
|
263
263
|
}
|
|
264
264
|
|
|
265
|
-
//
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
// First, restore base output from context (input/output sebelumnya)
|
|
270
|
-
if (context.sessionOutputs.has(sessionId)) {
|
|
271
|
-
const savedOutput = context.sessionOutputs.get(sessionId);
|
|
272
|
-
if (savedOutput) {
|
|
273
|
-
baseOutput = savedOutput.map(output => ({
|
|
274
|
-
content: output.content,
|
|
275
|
-
type: output.type as any,
|
|
276
|
-
timestamp: output.timestamp
|
|
277
|
-
}));
|
|
278
|
-
// Restored base output lines for session
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// Second, get NEW output from server that was generated while we were away
|
|
283
|
-
// We need to track when we saved the output to know what's new
|
|
284
|
-
if (hasActiveStream && activeStreamInfo) {
|
|
285
|
-
try {
|
|
286
|
-
// Get the saved output count from context metadata
|
|
287
|
-
// This tells us how much output we had when we switched away
|
|
288
|
-
let savedOutputCount = 0;
|
|
289
|
-
const savedMetadata = context.sessionOutputs.get(`${sessionId}-metadata`);
|
|
290
|
-
if (savedMetadata && typeof savedMetadata === 'object' && 'outputCount' in savedMetadata) {
|
|
291
|
-
savedOutputCount = (savedMetadata as any).outputCount || 0;
|
|
292
|
-
} else {
|
|
293
|
-
// Fallback: count actual output lines in baseOutput
|
|
294
|
-
for (const line of baseOutput) {
|
|
295
|
-
if (line.type === 'output' || line.type === 'error') {
|
|
296
|
-
savedOutputCount++;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// Get only NEW output from server (skip what we already have)
|
|
302
|
-
const data = await terminalService.getMissedOutput(
|
|
303
|
-
sessionId,
|
|
304
|
-
activeStreamInfo.streamId,
|
|
305
|
-
savedOutputCount
|
|
306
|
-
);
|
|
307
|
-
if (data.success && data.output && data.output.length > 0) {
|
|
308
|
-
// Convert server output to terminal lines
|
|
309
|
-
backgroundOutput = data.output.map((content: string) => ({
|
|
310
|
-
content: content,
|
|
311
|
-
type: 'output',
|
|
312
|
-
timestamp: new Date()
|
|
313
|
-
}));
|
|
314
|
-
debug.log('terminal', `Restored ${backgroundOutput.length} new output lines for session ${sessionId}`);
|
|
315
|
-
}
|
|
316
|
-
} catch (error) {
|
|
317
|
-
debug.error('terminal', 'Failed to fetch missed output:', error);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// Combine base output with background output from server
|
|
322
|
-
// Base output contains previous input/output saved in context
|
|
323
|
-
// Background output contains new output from server (if any)
|
|
324
|
-
terminalSession.lines = [...baseOutput, ...backgroundOutput];
|
|
265
|
+
// Always start with empty lines.
|
|
266
|
+
// The create-session replay from headless xterm will provide the
|
|
267
|
+
// accurate current terminal state (respects clear, scrollback, etc.)
|
|
268
|
+
terminalSession.lines = [];
|
|
325
269
|
|
|
326
270
|
// Restore command history from context (persisted) rather than manager (temporary)
|
|
327
271
|
const savedCommandHistory = context.sessionCommandHistories.get(sessionId);
|
|
@@ -62,20 +62,6 @@ export class TerminalService {
|
|
|
62
62
|
// Create unique stream ID for this connection
|
|
63
63
|
const streamId = `${sessionId}-${Date.now()}`;
|
|
64
64
|
|
|
65
|
-
// Get current output count to mark where new output starts
|
|
66
|
-
let outputStartIndex = 0;
|
|
67
|
-
if (typeof window !== 'undefined') {
|
|
68
|
-
try {
|
|
69
|
-
const terminalStoreModule = await import('$frontend/stores/features/terminal.svelte');
|
|
70
|
-
const termSession = terminalStoreModule.terminalStore.getSession(sessionId);
|
|
71
|
-
if (termSession && termSession.lines) {
|
|
72
|
-
outputStartIndex = termSession.lines.length;
|
|
73
|
-
}
|
|
74
|
-
} catch {
|
|
75
|
-
// Ignore error, use default 0
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
65
|
// Setup WebSocket listeners for this session
|
|
80
66
|
const listeners: Array<() => void> = [];
|
|
81
67
|
|
|
@@ -173,8 +159,7 @@ export class TerminalService {
|
|
|
173
159
|
workingDirectory: session.workingDirectory,
|
|
174
160
|
projectPath,
|
|
175
161
|
cols: terminalSize?.cols || 80,
|
|
176
|
-
rows: terminalSize?.rows || 24
|
|
177
|
-
outputStartIndex
|
|
162
|
+
rows: terminalSize?.rows || 24
|
|
178
163
|
});
|
|
179
164
|
|
|
180
165
|
debug.log('terminal', `✅ Terminal session created:`, response);
|
|
@@ -230,6 +215,17 @@ export class TerminalService {
|
|
|
230
215
|
}
|
|
231
216
|
}
|
|
232
217
|
|
|
218
|
+
/**
|
|
219
|
+
* Clear headless terminal on backend (sync with frontend clear)
|
|
220
|
+
*/
|
|
221
|
+
async clearHeadlessTerminal(sessionId: string): Promise<void> {
|
|
222
|
+
try {
|
|
223
|
+
await ws.http('terminal:clear', { sessionId });
|
|
224
|
+
} catch {
|
|
225
|
+
// Silently handle - non-critical
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
233
229
|
/**
|
|
234
230
|
* Resize terminal for a specific session
|
|
235
231
|
*/
|
|
@@ -301,39 +297,34 @@ export class TerminalService {
|
|
|
301
297
|
}
|
|
302
298
|
|
|
303
299
|
/**
|
|
304
|
-
* Get missed output for a session
|
|
300
|
+
* Get missed output for a session (serialized terminal state)
|
|
305
301
|
*/
|
|
306
302
|
async getMissedOutput(
|
|
307
303
|
sessionId: string,
|
|
308
|
-
streamId?: string
|
|
309
|
-
fromIndex: number = 0
|
|
304
|
+
streamId?: string
|
|
310
305
|
): Promise<{
|
|
311
306
|
success: boolean;
|
|
312
|
-
output: string
|
|
313
|
-
outputCount: number;
|
|
307
|
+
output: string;
|
|
314
308
|
status: string;
|
|
315
309
|
}> {
|
|
316
310
|
try {
|
|
317
|
-
const data = await ws.http('terminal:missed-output', { sessionId, streamId
|
|
311
|
+
const data = await ws.http('terminal:missed-output', { sessionId, streamId }, 5000);
|
|
318
312
|
if (data.sessionId === sessionId) {
|
|
319
313
|
return {
|
|
320
314
|
success: true,
|
|
321
315
|
output: data.output,
|
|
322
|
-
outputCount: data.outputCount,
|
|
323
316
|
status: data.status
|
|
324
317
|
};
|
|
325
318
|
}
|
|
326
319
|
return {
|
|
327
320
|
success: false,
|
|
328
|
-
output:
|
|
329
|
-
outputCount: 0,
|
|
321
|
+
output: '',
|
|
330
322
|
status: 'invalid_session'
|
|
331
323
|
};
|
|
332
324
|
} catch {
|
|
333
325
|
return {
|
|
334
326
|
success: false,
|
|
335
|
-
output:
|
|
336
|
-
outputCount: 0,
|
|
327
|
+
output: '',
|
|
337
328
|
status: 'timeout'
|
|
338
329
|
};
|
|
339
330
|
}
|
|
@@ -436,6 +436,12 @@ export async function initializeSessions() {
|
|
|
436
436
|
setupCollaborativeListeners();
|
|
437
437
|
setupEditModeListener();
|
|
438
438
|
|
|
439
|
+
// Skip loading if no project is active — both calls require WS project context
|
|
440
|
+
if (!projectState.currentProject) {
|
|
441
|
+
debug.log('session', 'No active project, skipping session load');
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
|
|
439
445
|
// Load sessions and restore edit mode in parallel
|
|
440
446
|
// Both only need WS project context (already set by initializeProjects)
|
|
441
447
|
await Promise.all([
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myrialabs/clopen",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.13",
|
|
4
4
|
"description": "All-in-one web workspace for Claude Code & OpenCode — chat, terminal, git, browser preview, checkpoints, and real-time collaboration",
|
|
5
5
|
"author": "Myria Labs",
|
|
6
6
|
"license": "MIT",
|
|
@@ -77,17 +77,19 @@
|
|
|
77
77
|
"dependencies": {
|
|
78
78
|
"@anthropic-ai/claude-agent-sdk": "0.2.63",
|
|
79
79
|
"@anthropic-ai/sdk": "0.78.0",
|
|
80
|
-
"@opencode-ai/sdk": "1.2.15",
|
|
81
80
|
"@elysiajs/cors": "^1.4.0",
|
|
82
81
|
"@iconify-json/lucide": "^1.2.57",
|
|
83
82
|
"@iconify-json/material-icon-theme": "^1.2.16",
|
|
84
83
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
85
84
|
"@monaco-editor/loader": "^1.5.0",
|
|
85
|
+
"@opencode-ai/sdk": "1.2.15",
|
|
86
86
|
"@xterm/addon-clipboard": "^0.2.0",
|
|
87
87
|
"@xterm/addon-fit": "^0.11.0",
|
|
88
88
|
"@xterm/addon-ligatures": "^0.10.0",
|
|
89
|
+
"@xterm/addon-serialize": "^0.14.0",
|
|
89
90
|
"@xterm/addon-unicode11": "^0.9.0",
|
|
90
91
|
"@xterm/addon-web-links": "^0.12.0",
|
|
92
|
+
"@xterm/headless": "^6.0.0",
|
|
91
93
|
"@xterm/xterm": "^6.0.0",
|
|
92
94
|
"bun-pty": "^0.4.2",
|
|
93
95
|
"cloudflared": "^0.7.1",
|