@myrialabs/clopen 0.0.7 → 0.1.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/backend/index.ts +28 -10
- package/backend/lib/chat/stream-manager.ts +130 -10
- package/backend/lib/database/queries/message-queries.ts +47 -0
- package/backend/lib/engine/adapters/claude/stream.ts +65 -1
- package/backend/lib/engine/adapters/opencode/message-converter.ts +35 -77
- package/backend/lib/engine/types.ts +6 -0
- package/backend/lib/files/file-operations.ts +2 -2
- package/backend/lib/files/file-reading.ts +2 -2
- package/backend/lib/files/path-browsing.ts +2 -2
- package/backend/lib/terminal/pty-session-manager.ts +1 -1
- package/backend/lib/terminal/shell-utils.ts +4 -4
- package/backend/lib/terminal/stream-manager.ts +6 -3
- package/backend/ws/chat/background.ts +3 -0
- package/backend/ws/chat/stream.ts +43 -1
- package/backend/ws/terminal/session.ts +48 -0
- package/bin/clopen.ts +10 -0
- package/bun.lock +258 -383
- package/frontend/lib/components/chat/ChatInterface.svelte +8 -1
- package/frontend/lib/components/chat/formatters/MessageFormatter.svelte +20 -0
- package/frontend/lib/components/chat/formatters/TextMessage.svelte +3 -15
- package/frontend/lib/components/chat/formatters/Tools.svelte +15 -8
- package/frontend/lib/components/chat/input/ChatInput.svelte +70 -21
- package/frontend/lib/components/chat/input/components/LoadingIndicator.svelte +23 -11
- package/frontend/lib/components/chat/input/composables/use-chat-actions.svelte.ts +1 -1
- package/frontend/lib/components/chat/message/ChatMessage.svelte +13 -1
- package/frontend/lib/components/chat/message/ChatMessages.svelte +2 -2
- package/frontend/lib/components/chat/message/DateSeparator.svelte +1 -1
- package/frontend/lib/components/chat/message/MessageBubble.svelte +2 -2
- package/frontend/lib/components/chat/message/MessageHeader.svelte +14 -12
- package/frontend/lib/components/chat/tools/AgentTool.svelte +95 -0
- package/frontend/lib/components/chat/tools/AskUserQuestionTool.svelte +396 -0
- package/frontend/lib/components/chat/tools/BashTool.svelte +9 -4
- package/frontend/lib/components/chat/tools/EnterPlanModeTool.svelte +24 -0
- package/frontend/lib/components/chat/tools/ExitPlanModeTool.svelte +4 -7
- package/frontend/lib/components/chat/tools/{KillShellTool.svelte → TaskStopTool.svelte} +6 -6
- package/frontend/lib/components/chat/tools/index.ts +5 -2
- package/frontend/lib/components/checkpoint/TimelineModal.svelte +7 -2
- package/frontend/lib/components/history/HistoryModal.svelte +13 -5
- package/frontend/lib/components/workspace/DesktopNavigator.svelte +2 -1
- package/frontend/lib/components/workspace/MobileNavigator.svelte +2 -1
- package/frontend/lib/services/chat/chat.service.ts +146 -12
- package/frontend/lib/services/terminal/project.service.ts +65 -10
- package/frontend/lib/services/terminal/terminal.service.ts +19 -0
- package/frontend/lib/stores/core/app.svelte.ts +77 -0
- package/frontend/lib/stores/features/terminal.svelte.ts +10 -0
- package/frontend/lib/utils/chat/message-grouper.ts +94 -12
- package/frontend/lib/utils/chat/message-processor.ts +37 -4
- package/frontend/lib/utils/chat/tool-handler.ts +96 -5
- package/package.json +4 -5
- package/shared/constants/engines.ts +1 -1
- package/shared/types/database/schema.ts +1 -0
- package/shared/types/messaging/index.ts +15 -13
- package/shared/types/messaging/tool.ts +185 -361
- package/shared/utils/message-formatter.ts +1 -0
|
@@ -42,7 +42,7 @@ export const TOOLS_WITH_RESULTS: ToolInput['name'][] = [
|
|
|
42
42
|
'ExitPlanMode',
|
|
43
43
|
'Glob',
|
|
44
44
|
'Grep',
|
|
45
|
-
'
|
|
45
|
+
'TaskStop',
|
|
46
46
|
'ListMcpResources',
|
|
47
47
|
'NotebookEdit',
|
|
48
48
|
'ReadMcpResource',
|
|
@@ -51,7 +51,12 @@ export const TOOLS_WITH_RESULTS: ToolInput['name'][] = [
|
|
|
51
51
|
'TodoWrite',
|
|
52
52
|
'WebFetch',
|
|
53
53
|
'WebSearch',
|
|
54
|
-
'Write'
|
|
54
|
+
'Write',
|
|
55
|
+
'AskUserQuestion',
|
|
56
|
+
'Config',
|
|
57
|
+
'EnterWorktree',
|
|
58
|
+
'Agent',
|
|
59
|
+
'EnterPlanMode'
|
|
55
60
|
];
|
|
56
61
|
|
|
57
62
|
// Tools that should be hidden from display
|
|
@@ -60,10 +65,38 @@ export const HIDDEN_TOOLS: ToolInput['name'][] = [
|
|
|
60
65
|
'TodoWrite'
|
|
61
66
|
];
|
|
62
67
|
|
|
68
|
+
// Message types that represent actual conversation content and should be rendered.
|
|
69
|
+
// All other types are transient/metadata and should be filtered out.
|
|
70
|
+
const RENDERABLE_MESSAGE_TYPES = new Set(['assistant', 'user', 'stream_event']);
|
|
71
|
+
|
|
72
|
+
// Check if this is a compact boundary message (system.compact_boundary)
|
|
73
|
+
export function isCompactBoundaryMessage(message: SDKMessageFormatter): boolean {
|
|
74
|
+
return message.type === 'system' && (message as any).subtype === 'compact_boundary';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Check if this is a synthetic user message (generated by system after compaction)
|
|
78
|
+
export function isSyntheticUserMessage(message: SDKMessageFormatter): boolean {
|
|
79
|
+
return message.type === 'user' && (message as any).isSynthetic === true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Check if this is a sub-agent user message (prompt sent to a sub-agent)
|
|
83
|
+
export function isSubAgentUserMessage(message: SDKMessageFormatter): boolean {
|
|
84
|
+
return message.type === 'user' &&
|
|
85
|
+
'parent_tool_use_id' in message &&
|
|
86
|
+
(message as any).parent_tool_use_id !== null;
|
|
87
|
+
}
|
|
88
|
+
|
|
63
89
|
// Check if a message should be filtered out
|
|
64
90
|
export function shouldFilterMessage(message: SDKMessageFormatter): boolean {
|
|
65
|
-
//
|
|
66
|
-
if (message
|
|
91
|
+
// Allow compact boundary messages (displayed as separator in chat)
|
|
92
|
+
if (isCompactBoundaryMessage(message)) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Whitelist approach: only render conversation content types.
|
|
97
|
+
// Filters out: system, result, rate_limit_event, tool_progress,
|
|
98
|
+
// auth_status, tool_use_summary, prompt_suggestion, and any future unknown types.
|
|
99
|
+
if (!RENDERABLE_MESSAGE_TYPES.has(message.type)) {
|
|
67
100
|
return true;
|
|
68
101
|
}
|
|
69
102
|
|
|
@@ -7,31 +7,44 @@ import type {
|
|
|
7
7
|
ToolGroup,
|
|
8
8
|
BackgroundBashData
|
|
9
9
|
} from './message-grouper';
|
|
10
|
+
import type { SDKMessageFormatter } from '$shared/types/database/schema';
|
|
11
|
+
import type { SubAgentActivity } from '$shared/types/messaging';
|
|
10
12
|
|
|
11
|
-
// Extended ToolUse with embedded result
|
|
13
|
+
// Extended ToolUse with embedded result and metadata
|
|
12
14
|
export interface ToolUseWithResult {
|
|
13
15
|
type: 'tool_use';
|
|
14
16
|
id: string;
|
|
15
17
|
name: string;
|
|
16
18
|
input: any;
|
|
17
19
|
$result?: any;
|
|
20
|
+
$subMessages?: SubAgentActivity[];
|
|
21
|
+
metadata?: Record<string, unknown>;
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
// Process a tool message with embedded results
|
|
21
25
|
export function processToolMessage(
|
|
22
26
|
message: ProcessedMessage,
|
|
23
27
|
toolUseMap: Map<string, ToolGroup>,
|
|
24
|
-
backgroundBashMap: Map<string, BackgroundBashData
|
|
28
|
+
backgroundBashMap: Map<string, BackgroundBashData>,
|
|
29
|
+
subAgentMap: Map<string, SDKMessageFormatter[]>
|
|
25
30
|
): ProcessedMessage {
|
|
26
31
|
const messageAny = message as any;
|
|
27
32
|
const content = messageAny.message?.content ?
|
|
28
33
|
(Array.isArray(messageAny.message.content) ? messageAny.message.content : [messageAny.message.content]) : [];
|
|
29
34
|
|
|
35
|
+
// Check if parent message is marked as interrupted
|
|
36
|
+
const isInterrupted = !!(messageAny.metadata?.interrupted);
|
|
37
|
+
|
|
30
38
|
// Create modified content with embedded tool_result in tool_use objects
|
|
31
39
|
const modifiedContent = content
|
|
32
40
|
.map((item: any): any => {
|
|
33
41
|
if (typeof item === 'object' && item && 'type' in item && item.type === 'tool_use') {
|
|
34
|
-
|
|
42
|
+
const processed = processToolUse(item, toolUseMap, backgroundBashMap, subAgentMap);
|
|
43
|
+
// Propagate message-level interrupted flag to ALL tool_use blocks
|
|
44
|
+
if (processed && isInterrupted) {
|
|
45
|
+
return { ...processed, metadata: { ...processed.metadata, interrupted: true } };
|
|
46
|
+
}
|
|
47
|
+
return processed;
|
|
35
48
|
}
|
|
36
49
|
return item;
|
|
37
50
|
})
|
|
@@ -51,7 +64,8 @@ export function processToolMessage(
|
|
|
51
64
|
function processToolUse(
|
|
52
65
|
item: any,
|
|
53
66
|
toolUseMap: Map<string, ToolGroup>,
|
|
54
|
-
backgroundBashMap: Map<string, BackgroundBashData
|
|
67
|
+
backgroundBashMap: Map<string, BackgroundBashData>,
|
|
68
|
+
subAgentMap: Map<string, SDKMessageFormatter[]>
|
|
55
69
|
): ToolUseWithResult | null {
|
|
56
70
|
// Hide certain tools completely
|
|
57
71
|
if (shouldHideTool(item.name)) {
|
|
@@ -66,6 +80,11 @@ function processToolUse(
|
|
|
66
80
|
return handleBackgroundBash(item, toolUseMap, backgroundBashMap);
|
|
67
81
|
}
|
|
68
82
|
|
|
83
|
+
// Special handling for Agent tool — embed sub-agent activities
|
|
84
|
+
if (item.name === 'Agent' && item.id && subAgentMap.has(item.id)) {
|
|
85
|
+
return handleAgentTool(item, toolUseMap, subAgentMap);
|
|
86
|
+
}
|
|
87
|
+
|
|
69
88
|
// Regular tool handling
|
|
70
89
|
if (item.id && item.name && shouldEmbedResult(item.name) && toolUseMap.has(item.id)) {
|
|
71
90
|
return handleRegularTool(item, toolUseMap);
|
|
@@ -74,6 +93,78 @@ function processToolUse(
|
|
|
74
93
|
return item;
|
|
75
94
|
}
|
|
76
95
|
|
|
96
|
+
// Handle Agent tool — process sub-agent messages into activities
|
|
97
|
+
function handleAgentTool(
|
|
98
|
+
item: any,
|
|
99
|
+
toolUseMap: Map<string, ToolGroup>,
|
|
100
|
+
subAgentMap: Map<string, SDKMessageFormatter[]>
|
|
101
|
+
): ToolUseWithResult {
|
|
102
|
+
const subMessages = subAgentMap.get(item.id) || [];
|
|
103
|
+
const activities = processSubAgentMessages(subMessages);
|
|
104
|
+
|
|
105
|
+
// Also embed the $result if available
|
|
106
|
+
let result: any = undefined;
|
|
107
|
+
if (item.id && toolUseMap.has(item.id)) {
|
|
108
|
+
const group = toolUseMap.get(item.id);
|
|
109
|
+
if (group?.toolResultMessage) {
|
|
110
|
+
const resultMessage = group.toolResultMessage as any;
|
|
111
|
+
const resultContent = resultMessage.message ?
|
|
112
|
+
(Array.isArray(resultMessage.message.content) ? resultMessage.message.content : [resultMessage.message.content]) : [];
|
|
113
|
+
result = findToolResult(resultContent, item.id);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
...item,
|
|
119
|
+
...(result ? { $result: result } : {}),
|
|
120
|
+
...(activities.length > 0 ? { $subMessages: activities } : {})
|
|
121
|
+
} as ToolUseWithResult;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Process sub-agent messages into a flat activity list
|
|
125
|
+
function processSubAgentMessages(messages: SDKMessageFormatter[]): SubAgentActivity[] {
|
|
126
|
+
const activities: SubAgentActivity[] = [];
|
|
127
|
+
const toolResultMap = new Map<string, any>();
|
|
128
|
+
|
|
129
|
+
// First pass: collect all tool_results from user messages
|
|
130
|
+
for (const msg of messages) {
|
|
131
|
+
if (msg.type === 'user' && 'message' in msg && msg.message?.content) {
|
|
132
|
+
const content = Array.isArray(msg.message.content) ? msg.message.content : [msg.message.content];
|
|
133
|
+
for (const item of content) {
|
|
134
|
+
if (typeof item === 'object' && item && item.type === 'tool_result' && item.tool_use_id) {
|
|
135
|
+
toolResultMap.set(item.tool_use_id, item);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Second pass: build activity list from assistant messages
|
|
142
|
+
for (const msg of messages) {
|
|
143
|
+
if (msg.type === 'assistant' && 'message' in msg && msg.message?.content) {
|
|
144
|
+
const content = Array.isArray(msg.message.content) ? msg.message.content : [msg.message.content];
|
|
145
|
+
for (const item of content) {
|
|
146
|
+
if (typeof item === 'object' && item) {
|
|
147
|
+
if (item.type === 'tool_use') {
|
|
148
|
+
activities.push({
|
|
149
|
+
type: 'tool_use',
|
|
150
|
+
toolName: item.name,
|
|
151
|
+
toolInput: item.input,
|
|
152
|
+
toolResult: toolResultMap.get(item.id) || undefined
|
|
153
|
+
});
|
|
154
|
+
} else if (item.type === 'text' && item.text?.trim()) {
|
|
155
|
+
activities.push({
|
|
156
|
+
type: 'text',
|
|
157
|
+
text: item.text
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return activities;
|
|
166
|
+
}
|
|
167
|
+
|
|
77
168
|
// Handle background bash commands
|
|
78
169
|
function handleBackgroundBash(
|
|
79
170
|
item: any,
|
|
@@ -158,4 +249,4 @@ function findToolResult(
|
|
|
158
249
|
'tool_use_id' in resultItem &&
|
|
159
250
|
resultItem.tool_use_id === toolUseId
|
|
160
251
|
);
|
|
161
|
-
}
|
|
252
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myrialabs/clopen",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
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",
|
|
@@ -74,15 +74,14 @@
|
|
|
74
74
|
"vite": "^7.0.4"
|
|
75
75
|
},
|
|
76
76
|
"dependencies": {
|
|
77
|
-
"@anthropic-ai/claude-agent-sdk": "^0.2.
|
|
78
|
-
"@anthropic-ai/sdk": "^0.
|
|
77
|
+
"@anthropic-ai/claude-agent-sdk": "^0.2.63",
|
|
78
|
+
"@anthropic-ai/sdk": "^0.78.0",
|
|
79
79
|
"@elysiajs/cors": "^1.4.0",
|
|
80
|
-
"@elysiajs/static": "^1.4.7",
|
|
81
80
|
"@iconify-json/lucide": "^1.2.57",
|
|
82
81
|
"@iconify-json/material-icon-theme": "^1.2.16",
|
|
83
82
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
84
83
|
"@monaco-editor/loader": "^1.5.0",
|
|
85
|
-
"@opencode-ai/sdk": "^1.2.
|
|
84
|
+
"@opencode-ai/sdk": "^1.2.15",
|
|
86
85
|
"@tailwindcss/typography": "^0.5.16",
|
|
87
86
|
"@types/marked": "^5.0.2",
|
|
88
87
|
"@types/node": "^24.0.14",
|
|
@@ -52,7 +52,7 @@ export const CLAUDE_CODE_MODELS: EngineModel[] = [
|
|
|
52
52
|
id: 'claude-code:sonnet',
|
|
53
53
|
engine: 'claude-code',
|
|
54
54
|
modelId: 'sonnet',
|
|
55
|
-
name: 'Sonnet 4.
|
|
55
|
+
name: 'Sonnet 4.6',
|
|
56
56
|
provider: 'anthropic',
|
|
57
57
|
description: 'High-performance model with excellent coding capabilities and extended thinking',
|
|
58
58
|
capabilities: ['Reasoning', 'Attachments'],
|
|
@@ -69,6 +69,7 @@ export type SDKMessageFormatter = EngineSDKMessage & {
|
|
|
69
69
|
parent_message_id?: string | null; // Git-like parent pointer
|
|
70
70
|
engine?: string; // Engine type that produced this message (claude-code, opencode)
|
|
71
71
|
reasoning?: boolean; // Whether this message is a reasoning/thinking message
|
|
72
|
+
interrupted?: boolean; // Whether the stream ended before all tools got results
|
|
72
73
|
};
|
|
73
74
|
};
|
|
74
75
|
|
|
@@ -65,6 +65,7 @@ import type { SDKUserMessage as _SDKUserMessage } from '@anthropic-ai/claude-age
|
|
|
65
65
|
export type EngineSDKMessage = SDKMessage & {
|
|
66
66
|
metadata?: {
|
|
67
67
|
reasoning?: boolean;
|
|
68
|
+
interrupted?: boolean;
|
|
68
69
|
};
|
|
69
70
|
};
|
|
70
71
|
|
|
@@ -113,7 +114,7 @@ export type {
|
|
|
113
114
|
ExitPlanModeToolInput,
|
|
114
115
|
GlobToolInput,
|
|
115
116
|
GrepToolInput,
|
|
116
|
-
|
|
117
|
+
TaskStopToolInput,
|
|
117
118
|
ListMcpResourcesToolInput,
|
|
118
119
|
NotebookEditToolInput,
|
|
119
120
|
ReadMcpResourceToolInput,
|
|
@@ -123,30 +124,31 @@ export type {
|
|
|
123
124
|
WebFetchToolInput,
|
|
124
125
|
WebSearchToolInput,
|
|
125
126
|
WriteToolInput,
|
|
127
|
+
AskUserQuestionToolInput,
|
|
128
|
+
ConfigToolInput,
|
|
129
|
+
EnterWorktreeToolInput,
|
|
130
|
+
AgentToolInput,
|
|
131
|
+
EnterPlanModeToolInput,
|
|
132
|
+
SubAgentActivity,
|
|
126
133
|
ToolInput,
|
|
127
134
|
|
|
128
|
-
// Tool output types
|
|
135
|
+
// Tool output types (from SDK)
|
|
129
136
|
TaskOutput,
|
|
137
|
+
AskUserQuestionOutput,
|
|
130
138
|
BashOutput,
|
|
131
|
-
|
|
139
|
+
ConfigOutput,
|
|
140
|
+
EnterWorktreeOutput,
|
|
132
141
|
EditOutput,
|
|
133
|
-
|
|
134
|
-
ImageFileOutput,
|
|
135
|
-
PDFFileOutput,
|
|
136
|
-
NotebookFileOutput,
|
|
142
|
+
ExitPlanModeOutput,
|
|
137
143
|
ReadOutput,
|
|
144
|
+
WriteOutput,
|
|
138
145
|
GlobOutput,
|
|
139
146
|
GrepOutput,
|
|
140
|
-
GrepContentOutput,
|
|
141
|
-
GrepFilesOutput,
|
|
142
|
-
GrepCountOutput,
|
|
143
147
|
WebFetchOutput,
|
|
144
148
|
WebSearchOutput,
|
|
145
|
-
WriteOutput,
|
|
146
149
|
NotebookEditOutput,
|
|
147
150
|
TodoWriteOutput,
|
|
148
|
-
|
|
149
|
-
KillShellOutput,
|
|
151
|
+
TaskStopOutput,
|
|
150
152
|
ListMcpResourcesOutput,
|
|
151
153
|
ReadMcpResourceOutput,
|
|
152
154
|
ToolOutput,
|