@myrialabs/clopen 0.1.2 → 0.1.3
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/lib/engine/adapters/opencode/message-converter.ts +53 -2
- package/backend/lib/engine/adapters/opencode/stream.ts +89 -5
- package/backend/lib/project/status-manager.ts +221 -181
- package/frontend/lib/components/chat/message/ChatMessages.svelte +16 -4
- package/frontend/lib/components/chat/tools/AgentTool.svelte +12 -11
- package/frontend/lib/components/chat/tools/BashOutputTool.svelte +3 -3
- package/frontend/lib/components/chat/tools/BashTool.svelte +4 -4
- package/frontend/lib/components/chat/tools/CustomMcpTool.svelte +3 -1
- package/frontend/lib/components/chat/tools/EditTool.svelte +6 -6
- package/frontend/lib/components/chat/tools/ExitPlanModeTool.svelte +1 -1
- package/frontend/lib/components/chat/tools/GlobTool.svelte +12 -12
- package/frontend/lib/components/chat/tools/GrepTool.svelte +5 -5
- package/frontend/lib/components/chat/tools/ListMcpResourcesTool.svelte +1 -1
- package/frontend/lib/components/chat/tools/NotebookEditTool.svelte +6 -6
- package/frontend/lib/components/chat/tools/ReadMcpResourceTool.svelte +2 -2
- package/frontend/lib/components/chat/tools/ReadTool.svelte +4 -4
- package/frontend/lib/components/chat/tools/TaskStopTool.svelte +1 -1
- package/frontend/lib/components/chat/tools/TaskTool.svelte +1 -1
- package/frontend/lib/components/chat/tools/TodoWriteTool.svelte +4 -4
- package/frontend/lib/components/chat/tools/WebSearchTool.svelte +1 -1
- package/frontend/lib/components/chat/tools/WriteTool.svelte +3 -3
- package/frontend/lib/components/chat/tools/components/CodeBlock.svelte +3 -3
- package/frontend/lib/components/chat/tools/components/DiffBlock.svelte +2 -2
- package/frontend/lib/components/chat/tools/components/FileHeader.svelte +1 -1
- package/frontend/lib/components/chat/tools/components/InfoLine.svelte +2 -2
- package/frontend/lib/components/chat/tools/components/StatsBadges.svelte +1 -1
- package/frontend/lib/components/chat/tools/components/TerminalCommand.svelte +5 -5
- package/frontend/lib/components/common/Button.svelte +1 -1
- package/frontend/lib/components/common/Card.svelte +3 -3
- package/frontend/lib/components/common/Input.svelte +3 -3
- package/frontend/lib/components/common/LoadingSpinner.svelte +1 -1
- package/frontend/lib/components/common/Select.svelte +6 -6
- package/frontend/lib/components/common/Textarea.svelte +3 -3
- package/frontend/lib/components/files/FileViewer.svelte +1 -1
- package/frontend/lib/components/git/ChangesSection.svelte +2 -4
- package/frontend/lib/components/preview/browser/BrowserPreview.svelte +9 -29
- package/frontend/lib/components/preview/browser/components/Container.svelte +17 -0
- package/frontend/lib/components/preview/browser/components/VirtualCursor.svelte +2 -2
- package/frontend/lib/components/settings/appearance/AppearanceSettings.svelte +0 -6
- package/frontend/lib/components/settings/appearance/LayoutPresetSettings.svelte +15 -15
- package/frontend/lib/components/settings/appearance/LayoutPreview.svelte +2 -2
- package/frontend/lib/components/workspace/DesktopNavigator.svelte +380 -383
- package/frontend/lib/components/workspace/MobileNavigator.svelte +391 -395
- package/frontend/lib/components/workspace/PanelHeader.svelte +115 -4
- package/frontend/lib/components/workspace/ViewMenu.svelte +9 -25
- package/frontend/lib/components/workspace/layout/split-pane/Layout.svelte +29 -4
- package/frontend/lib/services/notification/global-stream-monitor.ts +77 -86
- package/frontend/lib/services/project/status.service.ts +160 -159
- package/frontend/lib/stores/ui/workspace.svelte.ts +326 -283
- package/package.json +1 -1
|
@@ -127,6 +127,8 @@ const TOOL_NAME_MAP: Record<string, string> = {
|
|
|
127
127
|
'todo_write': 'TodoWrite',
|
|
128
128
|
'todowrite': 'TodoWrite',
|
|
129
129
|
'todoread': 'TodoWrite',
|
|
130
|
+
// Agent / sub-agent
|
|
131
|
+
'task': 'Agent',
|
|
130
132
|
// User interaction
|
|
131
133
|
'question': 'AskUserQuestion',
|
|
132
134
|
// Code intelligence & utilities
|
|
@@ -385,6 +387,7 @@ interface AssistantMessageParams {
|
|
|
385
387
|
sessionId: string;
|
|
386
388
|
stopReason: string | null;
|
|
387
389
|
uuid: string;
|
|
390
|
+
parentToolUseId?: string | null;
|
|
388
391
|
}
|
|
389
392
|
|
|
390
393
|
/** Build a single SDKAssistantMessage from content blocks */
|
|
@@ -402,7 +405,7 @@ function buildAssistantSDKMessage(params: AssistantMessageParams): SDKMessage {
|
|
|
402
405
|
...(params.usage && { usage: params.usage }),
|
|
403
406
|
context_management: null
|
|
404
407
|
},
|
|
405
|
-
parent_tool_use_id: null,
|
|
408
|
+
parent_tool_use_id: params.parentToolUseId ?? null,
|
|
406
409
|
session_id: params.sessionId,
|
|
407
410
|
uuid: params.uuid
|
|
408
411
|
} as unknown as SDKMessage;
|
|
@@ -495,6 +498,20 @@ export function convertAssistantMessages(
|
|
|
495
498
|
|
|
496
499
|
allBlocks.push(block);
|
|
497
500
|
}
|
|
501
|
+
else if (part.type === 'subtask') {
|
|
502
|
+
// Subtask = Agent/sub-agent invocation
|
|
503
|
+
const subtaskPart = part as any;
|
|
504
|
+
allBlocks.push({
|
|
505
|
+
type: 'tool_use',
|
|
506
|
+
id: subtaskPart.id || crypto.randomUUID(),
|
|
507
|
+
name: 'Agent',
|
|
508
|
+
input: {
|
|
509
|
+
prompt: subtaskPart.prompt || '',
|
|
510
|
+
description: subtaskPart.description || '',
|
|
511
|
+
subagent_type: subtaskPart.agent || 'general-purpose',
|
|
512
|
+
} as NormalizedToolInput,
|
|
513
|
+
});
|
|
514
|
+
}
|
|
498
515
|
// Skip: reasoning, step-start, step-finish, snapshot, patch, agent, retry, compaction
|
|
499
516
|
}
|
|
500
517
|
|
|
@@ -682,6 +699,7 @@ export function convertToolUseOnly(
|
|
|
682
699
|
toolPart: ToolPart,
|
|
683
700
|
ocMessage: OCMessage,
|
|
684
701
|
sessionId: string,
|
|
702
|
+
parentToolUseId: string | null = null,
|
|
685
703
|
): SDKMessage {
|
|
686
704
|
const claudeName = mapToolName(toolPart.tool || 'unknown');
|
|
687
705
|
const resolvedInput = getToolInput(toolPart);
|
|
@@ -703,6 +721,7 @@ export function convertToolUseOnly(
|
|
|
703
721
|
sessionId,
|
|
704
722
|
stopReason: 'tool_use',
|
|
705
723
|
uuid: crypto.randomUUID(),
|
|
724
|
+
parentToolUseId,
|
|
706
725
|
});
|
|
707
726
|
}
|
|
708
727
|
|
|
@@ -783,6 +802,37 @@ export function convertReasoningStreamStop(sessionId: string): EngineSDKMessage
|
|
|
783
802
|
} as unknown as EngineSDKMessage;
|
|
784
803
|
}
|
|
785
804
|
|
|
805
|
+
/**
|
|
806
|
+
* Convert a subtask part → assistant message with Agent tool_use.
|
|
807
|
+
* Used for progressive rendering when OpenCode emits subtask parts.
|
|
808
|
+
*/
|
|
809
|
+
export function convertSubtaskToolUseOnly(
|
|
810
|
+
subtaskPart: { id: string; prompt: string; description: string; agent: string },
|
|
811
|
+
ocMessage: OCMessage,
|
|
812
|
+
sessionId: string,
|
|
813
|
+
): SDKMessage {
|
|
814
|
+
const assistantMsg = ocMessage.role === 'assistant' ? ocMessage as AssistantMessage : null;
|
|
815
|
+
const modelId = assistantMsg ? `${assistantMsg.providerID}/${assistantMsg.modelID}` : '';
|
|
816
|
+
|
|
817
|
+
return buildAssistantSDKMessage({
|
|
818
|
+
content: [{
|
|
819
|
+
type: 'tool_use',
|
|
820
|
+
id: subtaskPart.id || crypto.randomUUID(),
|
|
821
|
+
name: 'Agent',
|
|
822
|
+
input: {
|
|
823
|
+
prompt: subtaskPart.prompt || '',
|
|
824
|
+
description: subtaskPart.description || '',
|
|
825
|
+
subagent_type: subtaskPart.agent || 'general-purpose',
|
|
826
|
+
} as NormalizedToolInput,
|
|
827
|
+
}],
|
|
828
|
+
ocMessage,
|
|
829
|
+
modelId,
|
|
830
|
+
sessionId,
|
|
831
|
+
stopReason: 'tool_use',
|
|
832
|
+
uuid: crypto.randomUUID(),
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
|
|
786
836
|
/**
|
|
787
837
|
* Convert a completed/errored tool part → user message with tool_result.
|
|
788
838
|
* Sent after the tool finishes executing, matching Claude Code's pattern
|
|
@@ -791,6 +841,7 @@ export function convertReasoningStreamStop(sessionId: string): EngineSDKMessage
|
|
|
791
841
|
export function convertToolResultOnly(
|
|
792
842
|
toolPart: ToolPart,
|
|
793
843
|
sessionId: string,
|
|
844
|
+
parentToolUseId: string | null = null,
|
|
794
845
|
): SDKMessage {
|
|
795
846
|
const toolUseId = toolPart.callID || toolPart.id || crypto.randomUUID();
|
|
796
847
|
|
|
@@ -807,7 +858,7 @@ export function convertToolResultOnly(
|
|
|
807
858
|
type: 'user',
|
|
808
859
|
uuid: crypto.randomUUID(),
|
|
809
860
|
session_id: sessionId,
|
|
810
|
-
parent_tool_use_id:
|
|
861
|
+
parent_tool_use_id: parentToolUseId,
|
|
811
862
|
message: {
|
|
812
863
|
role: 'user',
|
|
813
864
|
content: [{
|
|
@@ -37,6 +37,7 @@ import {
|
|
|
37
37
|
convertPartialReasoningDelta,
|
|
38
38
|
convertReasoningStreamStart,
|
|
39
39
|
convertReasoningStreamStop,
|
|
40
|
+
convertSubtaskToolUseOnly,
|
|
40
41
|
getToolInput,
|
|
41
42
|
} from './message-converter';
|
|
42
43
|
import { ensureClient, getClient } from './server';
|
|
@@ -236,6 +237,13 @@ export class OpenCodeEngine implements AIEngine {
|
|
|
236
237
|
let reasoningStreamActive = false; // Whether reasoning is currently streaming
|
|
237
238
|
let reasoningText = ''; // Accumulated reasoning text
|
|
238
239
|
|
|
240
|
+
// Child session tracking (for Agent tool sub-messages)
|
|
241
|
+
const childSessionToAgentTool = new Map<string, string>(); // child sessionID → agent tool callID
|
|
242
|
+
const childAssistantMessages = new Map<string, OCMessage>(); // child msgID → message
|
|
243
|
+
const childEmittedToolParts = new Set<string>();
|
|
244
|
+
const childCompletedToolParts = new Set<string>();
|
|
245
|
+
let lastAgentToolCallId: string | null = null;
|
|
246
|
+
|
|
239
247
|
/**
|
|
240
248
|
* Flush active reasoning stream: stop reasoning stream, emit final reasoning message.
|
|
241
249
|
*/
|
|
@@ -266,9 +274,10 @@ export class OpenCodeEngine implements AIEngine {
|
|
|
266
274
|
yield convertStreamStop(sessionId);
|
|
267
275
|
|
|
268
276
|
const parts = messageParts.get(msgId) || [];
|
|
269
|
-
// Filter out tool parts and reasoning parts already emitted
|
|
277
|
+
// Filter out tool parts, subtask parts, and reasoning parts already emitted
|
|
270
278
|
const remainingParts = parts.filter(p => {
|
|
271
279
|
if (p.type === 'tool') return !emittedToolParts.has(p.id);
|
|
280
|
+
if (p.type === 'subtask') return !emittedToolParts.has(p.id);
|
|
272
281
|
if (p.type === 'reasoning') return false; // Already emitted as reasoning message
|
|
273
282
|
return true;
|
|
274
283
|
});
|
|
@@ -312,6 +321,15 @@ export class OpenCodeEngine implements AIEngine {
|
|
|
312
321
|
}
|
|
313
322
|
currentAssistantId = info.id;
|
|
314
323
|
}
|
|
324
|
+
|
|
325
|
+
// Track child session assistant messages (for Agent tool sub-messages)
|
|
326
|
+
if (info.role === 'assistant' && info.sessionID !== sessionId && lastAgentToolCallId) {
|
|
327
|
+
if (!childSessionToAgentTool.has(info.sessionID)) {
|
|
328
|
+
childSessionToAgentTool.set(info.sessionID, lastAgentToolCallId);
|
|
329
|
+
debug.log('engine', `[OC] child session detected: ${info.sessionID} → agent tool ${lastAgentToolCallId}`);
|
|
330
|
+
}
|
|
331
|
+
childAssistantMessages.set(info.id, info);
|
|
332
|
+
}
|
|
315
333
|
break;
|
|
316
334
|
}
|
|
317
335
|
|
|
@@ -321,8 +339,36 @@ export class OpenCodeEngine implements AIEngine {
|
|
|
321
339
|
|
|
322
340
|
debug.log('engine', `[OC] part.updated: type=${part.type}, partId=${part.id}, msgId=${part.messageID}, session=${part.sessionID === sessionId ? 'match' : 'skip'}`);
|
|
323
341
|
|
|
324
|
-
//
|
|
325
|
-
if (part.sessionID !== sessionId)
|
|
342
|
+
// Handle child session parts (sub-agent tool activities)
|
|
343
|
+
if (part.sessionID !== sessionId) {
|
|
344
|
+
const agentCallId = childSessionToAgentTool.get(part.sessionID);
|
|
345
|
+
if (agentCallId && childAssistantMessages.has(part.messageID)) {
|
|
346
|
+
const childMsg = childAssistantMessages.get(part.messageID)!;
|
|
347
|
+
|
|
348
|
+
if (part.type === 'tool') {
|
|
349
|
+
const childToolPart = part as ToolPart;
|
|
350
|
+
if (!childEmittedToolParts.has(part.id)) {
|
|
351
|
+
const resolvedInput = getToolInput(childToolPart);
|
|
352
|
+
const hasInput = Object.keys(resolvedInput).length > 0
|
|
353
|
+
|| childToolPart.state.status !== 'pending';
|
|
354
|
+
if (hasInput) {
|
|
355
|
+
childEmittedToolParts.add(part.id);
|
|
356
|
+
yield convertToolUseOnly(childToolPart, childMsg, sessionId, agentCallId);
|
|
357
|
+
if (childToolPart.state.status === 'completed' || childToolPart.state.status === 'error') {
|
|
358
|
+
childCompletedToolParts.add(part.id);
|
|
359
|
+
yield convertToolResultOnly(childToolPart, sessionId, agentCallId);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
} else if (!childCompletedToolParts.has(part.id)) {
|
|
363
|
+
if (childToolPart.state.status === 'completed' || childToolPart.state.status === 'error') {
|
|
364
|
+
childCompletedToolParts.add(part.id);
|
|
365
|
+
yield convertToolResultOnly(childToolPart, sessionId, agentCallId);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
326
372
|
|
|
327
373
|
// Only process parts for tracked assistant messages (skip user message parts)
|
|
328
374
|
if (!assistantMessages.has(part.messageID)) {
|
|
@@ -364,6 +410,11 @@ export class OpenCodeEngine implements AIEngine {
|
|
|
364
410
|
if (hasInput) {
|
|
365
411
|
emittedToolParts.add(part.id);
|
|
366
412
|
|
|
413
|
+
// Track agent tool callID for child session linking
|
|
414
|
+
if (toolPart.tool === 'task') {
|
|
415
|
+
lastAgentToolCallId = toolPart.callID || toolPart.id;
|
|
416
|
+
}
|
|
417
|
+
|
|
367
418
|
yield convertStreamStop(sessionId);
|
|
368
419
|
yield convertToolUseOnly(toolPart, msg, sessionId);
|
|
369
420
|
|
|
@@ -412,6 +463,25 @@ export class OpenCodeEngine implements AIEngine {
|
|
|
412
463
|
}
|
|
413
464
|
}
|
|
414
465
|
|
|
466
|
+
// Handle subtask parts — convert to Agent tool_use for progressive rendering
|
|
467
|
+
if (part.type === 'subtask') {
|
|
468
|
+
const subtaskPart = part as any;
|
|
469
|
+
const msg = assistantMessages.get(msgId);
|
|
470
|
+
if (msg && !emittedToolParts.has(part.id)) {
|
|
471
|
+
emittedToolParts.add(part.id);
|
|
472
|
+
// Track for child session linking
|
|
473
|
+
lastAgentToolCallId = part.id;
|
|
474
|
+
if (reasoningStreamActive) {
|
|
475
|
+
yield* flushReasoning(msg);
|
|
476
|
+
}
|
|
477
|
+
yield convertStreamStop(sessionId);
|
|
478
|
+
yield convertSubtaskToolUseOnly(subtaskPart, msg, sessionId);
|
|
479
|
+
yield convertStreamStart(sessionId);
|
|
480
|
+
streamingText = '';
|
|
481
|
+
}
|
|
482
|
+
break;
|
|
483
|
+
}
|
|
484
|
+
|
|
415
485
|
// Skip non-text/non-reasoning parts (step-start, step-finish, etc.)
|
|
416
486
|
if (part.type !== 'text') {
|
|
417
487
|
debug.log('engine', `[OC] part.updated: skipped non-text type=${part.type}`);
|
|
@@ -455,6 +525,12 @@ export class OpenCodeEngine implements AIEngine {
|
|
|
455
525
|
}
|
|
456
526
|
|
|
457
527
|
case 'session.idle': {
|
|
528
|
+
// Only handle idle for our session (sub-agent sessions have different IDs)
|
|
529
|
+
const idleProps = (event as EventSessionIdle).properties;
|
|
530
|
+
if (idleProps.sessionID !== sessionId) {
|
|
531
|
+
debug.log('engine', `[OC] session.idle: ignored (session=${idleProps.sessionID}, ours=${sessionId})`);
|
|
532
|
+
break;
|
|
533
|
+
}
|
|
458
534
|
// Session finished — flush reasoning and emit the last assistant message
|
|
459
535
|
if (currentAssistantId && !emittedMessageIds.has(currentAssistantId)) {
|
|
460
536
|
const msg = assistantMessages.get(currentAssistantId);
|
|
@@ -468,6 +544,7 @@ export class OpenCodeEngine implements AIEngine {
|
|
|
468
544
|
// Filter out tool parts and reasoning parts already emitted
|
|
469
545
|
const remainingParts = parts.filter(p => {
|
|
470
546
|
if (p.type === 'tool') return !emittedToolParts.has(p.id);
|
|
547
|
+
if (p.type === 'subtask') return !emittedToolParts.has(p.id);
|
|
471
548
|
if (p.type === 'reasoning') return false;
|
|
472
549
|
return true;
|
|
473
550
|
});
|
|
@@ -495,7 +572,10 @@ export class OpenCodeEngine implements AIEngine {
|
|
|
495
572
|
}
|
|
496
573
|
|
|
497
574
|
case 'session.status': {
|
|
498
|
-
const
|
|
575
|
+
const statusProps = (event as EventSessionStatus).properties;
|
|
576
|
+
// Only handle status for our session (sub-agent sessions have different IDs)
|
|
577
|
+
if (statusProps.sessionID !== sessionId) break;
|
|
578
|
+
const { status } = statusProps;
|
|
499
579
|
if (status.type === 'idle') {
|
|
500
580
|
// Same as session.idle
|
|
501
581
|
if (currentAssistantId && !emittedMessageIds.has(currentAssistantId)) {
|
|
@@ -508,6 +588,7 @@ export class OpenCodeEngine implements AIEngine {
|
|
|
508
588
|
const parts = messageParts.get(currentAssistantId) || [];
|
|
509
589
|
const remainingParts = parts.filter(p => {
|
|
510
590
|
if (p.type === 'tool') return !emittedToolParts.has(p.id);
|
|
591
|
+
if (p.type === 'subtask') return !emittedToolParts.has(p.id);
|
|
511
592
|
if (p.type === 'reasoning') return false;
|
|
512
593
|
return true;
|
|
513
594
|
});
|
|
@@ -584,7 +665,10 @@ export class OpenCodeEngine implements AIEngine {
|
|
|
584
665
|
}
|
|
585
666
|
|
|
586
667
|
case 'session.error': {
|
|
587
|
-
const
|
|
668
|
+
const errorProps = (event as EventSessionError).properties;
|
|
669
|
+
// Only handle errors for our session (sessionID is optional on errors)
|
|
670
|
+
if (errorProps.sessionID && errorProps.sessionID !== sessionId) break;
|
|
671
|
+
const { error } = errorProps;
|
|
588
672
|
// Extract a human-readable error message.
|
|
589
673
|
// OpenCode SDK errors follow: { name: string, data: { message, statusCode?, providerID?, responseBody? } }
|
|
590
674
|
// Don't prepend error class names (e.g. "APIError") — those are SDK
|