codevf 1.0.0 → 1.0.2
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/LICENSE +30 -21
- package/README.md +7 -2
- package/bin/codevf-mcp.js +11 -0
- package/dist/commands/fix.d.ts +5 -1
- package/dist/commands/fix.d.ts.map +1 -1
- package/dist/commands/fix.js +170 -13
- package/dist/commands/fix.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +72 -2
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/mcp-tools.d.ts +17 -0
- package/dist/commands/mcp-tools.d.ts.map +1 -0
- package/dist/commands/mcp-tools.js +237 -0
- package/dist/commands/mcp-tools.js.map +1 -0
- package/dist/commands/setup.d.ts +8 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +250 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/welcome.d.ts +9 -0
- package/dist/commands/welcome.d.ts.map +1 -0
- package/dist/commands/welcome.js +175 -0
- package/dist/commands/welcome.js.map +1 -0
- package/dist/index.js +263 -207
- package/dist/index.js.map +1 -1
- package/dist/lib/api/client.d.ts +28 -0
- package/dist/lib/api/client.d.ts.map +1 -0
- package/dist/lib/api/client.js +66 -0
- package/dist/lib/api/client.js.map +1 -0
- package/dist/lib/api/projects.d.ts +32 -0
- package/dist/lib/api/projects.d.ts.map +1 -0
- package/dist/lib/api/projects.js +61 -0
- package/dist/lib/api/projects.js.map +1 -0
- package/dist/lib/api/tasks.d.ts +36 -0
- package/dist/lib/api/tasks.d.ts.map +1 -0
- package/dist/lib/api/tasks.js +62 -0
- package/dist/lib/api/tasks.js.map +1 -0
- package/dist/lib/api/websocket.d.ts +50 -0
- package/dist/lib/api/websocket.d.ts.map +1 -0
- package/dist/lib/api/websocket.js +153 -0
- package/dist/lib/api/websocket.js.map +1 -0
- package/dist/lib/auth/oauth-flow.d.ts +37 -0
- package/dist/lib/auth/oauth-flow.d.ts.map +1 -0
- package/dist/lib/auth/oauth-flow.js +119 -0
- package/dist/lib/auth/oauth-flow.js.map +1 -0
- package/dist/lib/auth/token-manager.d.ts +26 -0
- package/dist/lib/auth/token-manager.d.ts.map +1 -0
- package/dist/lib/auth/token-manager.js +87 -0
- package/dist/lib/auth/token-manager.js.map +1 -0
- package/dist/lib/config/manager.d.ts +50 -0
- package/dist/lib/config/manager.d.ts.map +1 -0
- package/dist/lib/config/manager.js +84 -0
- package/dist/lib/config/manager.js.map +1 -0
- package/dist/lib/utils/errors.d.ts +28 -0
- package/dist/lib/utils/errors.d.ts.map +1 -0
- package/dist/lib/utils/errors.js +44 -0
- package/dist/lib/utils/errors.js.map +1 -0
- package/dist/lib/utils/logger.d.ts +20 -0
- package/dist/lib/utils/logger.d.ts.map +1 -0
- package/dist/lib/utils/logger.js +40 -0
- package/dist/lib/utils/logger.js.map +1 -0
- package/dist/mcp/index.d.ts +7 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +160 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/tools/chat.d.ts +30 -0
- package/dist/mcp/tools/chat.d.ts.map +1 -0
- package/dist/mcp/tools/chat.js +82 -0
- package/dist/mcp/tools/chat.js.map +1 -0
- package/dist/mcp/tools/instant.d.ts +38 -0
- package/dist/mcp/tools/instant.d.ts.map +1 -0
- package/dist/mcp/tools/instant.js +106 -0
- package/dist/mcp/tools/instant.js.map +1 -0
- package/dist/modules/aiAgent.d.ts +75 -0
- package/dist/modules/aiAgent.d.ts.map +1 -0
- package/dist/modules/aiAgent.js +707 -0
- package/dist/modules/aiAgent.js.map +1 -0
- package/dist/modules/api.d.ts +7 -0
- package/dist/modules/api.d.ts.map +1 -1
- package/dist/modules/api.js +13 -4
- package/dist/modules/api.js.map +1 -1
- package/dist/modules/commandHandler.d.ts +40 -0
- package/dist/modules/commandHandler.d.ts.map +1 -0
- package/dist/modules/commandHandler.js +328 -0
- package/dist/modules/commandHandler.js.map +1 -0
- package/dist/modules/config.d.ts +2 -0
- package/dist/modules/config.d.ts.map +1 -1
- package/dist/modules/config.js +9 -0
- package/dist/modules/config.js.map +1 -1
- package/dist/modules/constants.d.ts +83 -0
- package/dist/modules/constants.d.ts.map +1 -0
- package/dist/modules/constants.js +75 -0
- package/dist/modules/constants.js.map +1 -0
- package/dist/modules/permissions.d.ts +14 -0
- package/dist/modules/permissions.d.ts.map +1 -1
- package/dist/modules/permissions.js +94 -0
- package/dist/modules/permissions.js.map +1 -1
- package/dist/modules/toolRegistry.d.ts +50 -0
- package/dist/modules/toolRegistry.d.ts.map +1 -0
- package/dist/modules/toolRegistry.js +114 -0
- package/dist/modules/toolRegistry.js.map +1 -0
- package/dist/modules/tunnel.d.ts +33 -0
- package/dist/modules/tunnel.d.ts.map +1 -0
- package/dist/modules/tunnel.js +79 -0
- package/dist/modules/tunnel.js.map +1 -0
- package/dist/modules/vibeHelper.d.ts +16 -0
- package/dist/modules/vibeHelper.d.ts.map +1 -0
- package/dist/modules/vibeHelper.js +38 -0
- package/dist/modules/vibeHelper.js.map +1 -0
- package/dist/modules/websocket.d.ts +9 -0
- package/dist/modules/websocket.d.ts.map +1 -1
- package/dist/modules/websocket.js +70 -0
- package/dist/modules/websocket.js.map +1 -1
- package/dist/tools/consultEngineer.d.ts +13 -0
- package/dist/tools/consultEngineer.d.ts.map +1 -0
- package/dist/tools/consultEngineer.js +161 -0
- package/dist/tools/consultEngineer.js.map +1 -0
- package/dist/tools/realtimeChat.d.ts +9 -0
- package/dist/tools/realtimeChat.d.ts.map +1 -0
- package/dist/tools/realtimeChat.js +101 -0
- package/dist/tools/realtimeChat.js.map +1 -0
- package/dist/types/index.d.ts +183 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/dist/ui/InteractiveApp.d.ts +13 -0
- package/dist/ui/InteractiveApp.d.ts.map +1 -0
- package/dist/ui/InteractiveApp.js +84 -0
- package/dist/ui/InteractiveApp.js.map +1 -0
- package/dist/ui/InteractivePrompt.d.ts +53 -0
- package/dist/ui/InteractivePrompt.d.ts.map +1 -0
- package/dist/ui/InteractivePrompt.js +422 -0
- package/dist/ui/InteractivePrompt.js.map +1 -0
- package/dist/ui/LiveSession.d.ts +2 -0
- package/dist/ui/LiveSession.d.ts.map +1 -1
- package/dist/ui/LiveSession.js +461 -180
- package/dist/ui/LiveSession.js.map +1 -1
- package/dist/ui/PromptInput.d.ts +14 -0
- package/dist/ui/PromptInput.d.ts.map +1 -0
- package/dist/ui/PromptInput.js +206 -0
- package/dist/ui/PromptInput.js.map +1 -0
- package/dist/ui/SessionUI.d.ts +40 -0
- package/dist/ui/SessionUI.d.ts.map +1 -0
- package/dist/ui/SessionUI.js +218 -0
- package/dist/ui/SessionUI.js.map +1 -0
- package/dist/ui/input/Command.d.ts +22 -0
- package/dist/ui/input/Command.d.ts.map +1 -0
- package/dist/ui/input/Command.js +30 -0
- package/dist/ui/input/Command.js.map +1 -0
- package/dist/ui/input/CustomInput.d.ts +15 -0
- package/dist/ui/input/CustomInput.d.ts.map +1 -0
- package/dist/ui/input/CustomInput.js +182 -0
- package/dist/ui/input/CustomInput.js.map +1 -0
- package/dist/ui/input/handlers/handleCursor.d.ts +22 -0
- package/dist/ui/input/handlers/handleCursor.d.ts.map +1 -0
- package/dist/ui/input/handlers/handleCursor.js +53 -0
- package/dist/ui/input/handlers/handleCursor.js.map +1 -0
- package/dist/ui/input/handlers/handleEdit.d.ts +18 -0
- package/dist/ui/input/handlers/handleEdit.d.ts.map +1 -0
- package/dist/ui/input/handlers/handleEdit.js +55 -0
- package/dist/ui/input/handlers/handleEdit.js.map +1 -0
- package/dist/ui/input/handlers/handleHistory.d.ts +18 -0
- package/dist/ui/input/handlers/handleHistory.d.ts.map +1 -0
- package/dist/ui/input/handlers/handleHistory.js +85 -0
- package/dist/ui/input/handlers/handleHistory.js.map +1 -0
- package/dist/ui/input/handlers/handlePaste.d.ts +19 -0
- package/dist/ui/input/handlers/handlePaste.d.ts.map +1 -0
- package/dist/ui/input/handlers/handlePaste.js +49 -0
- package/dist/ui/input/handlers/handlePaste.js.map +1 -0
- package/dist/ui/input/handlers/handleSubmit.d.ts +18 -0
- package/dist/ui/input/handlers/handleSubmit.d.ts.map +1 -0
- package/dist/ui/input/handlers/handleSubmit.js +39 -0
- package/dist/ui/input/handlers/handleSubmit.js.map +1 -0
- package/dist/ui/input/helpers.d.ts +4 -0
- package/dist/ui/input/helpers.d.ts.map +1 -0
- package/dist/ui/input/helpers.js +13 -0
- package/dist/ui/input/helpers.js.map +1 -0
- package/dist/ui/input/keyMatchers.d.ts +14 -0
- package/dist/ui/input/keyMatchers.d.ts.map +1 -0
- package/dist/ui/input/keyMatchers.js +49 -0
- package/dist/ui/input/keyMatchers.js.map +1 -0
- package/dist/ui/input/types.d.ts +33 -0
- package/dist/ui/input/types.d.ts.map +1 -0
- package/dist/ui/input/types.js +2 -0
- package/dist/ui/input/types.js.map +1 -0
- package/dist/ui/promptWithModes.d.ts +12 -0
- package/dist/ui/promptWithModes.d.ts.map +1 -0
- package/dist/ui/promptWithModes.js +24 -0
- package/dist/ui/promptWithModes.js.map +1 -0
- package/dist/ui/renderPrompt.d.ts +12 -0
- package/dist/ui/renderPrompt.d.ts.map +1 -0
- package/dist/ui/renderPrompt.js +14 -0
- package/dist/ui/renderPrompt.js.map +1 -0
- package/dist/ui/simplePrompt.d.ts +7 -0
- package/dist/ui/simplePrompt.d.ts.map +1 -0
- package/dist/ui/simplePrompt.js +38 -0
- package/dist/ui/simplePrompt.js.map +1 -0
- package/dist/ui/spinner.d.ts +7 -0
- package/dist/ui/spinner.d.ts.map +1 -0
- package/dist/ui/spinner.js +13 -0
- package/dist/ui/spinner.js.map +1 -0
- package/package.json +36 -26
- package/ARCHITECTURE.md +0 -285
- package/BUILD_SUMMARY.md +0 -340
- package/QUICKSTART.md +0 -180
package/dist/ui/LiveSession.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState, useEffect } from 'react';
|
|
1
|
+
import React, { useState, useEffect, useCallback } from 'react';
|
|
2
2
|
import { Box, Text, useInput, useApp } from 'ink';
|
|
3
3
|
import * as fs from 'fs';
|
|
4
4
|
import { exec } from 'child_process';
|
|
@@ -6,44 +6,189 @@ import { promisify } from 'util';
|
|
|
6
6
|
import { useRef } from 'react';
|
|
7
7
|
const execAsync = promisify(exec);
|
|
8
8
|
const COMMANDS = [
|
|
9
|
-
{ cmd: '/
|
|
10
|
-
{ cmd: '/
|
|
11
|
-
{ cmd: '
|
|
9
|
+
{ cmd: '/hybrid', description: 'Hybrid mode (AI → Human) (active)' },
|
|
10
|
+
{ cmd: '/ai', description: 'Use AI mode only' },
|
|
11
|
+
{ cmd: '/human', description: 'Request human engineer' },
|
|
12
12
|
{ cmd: '/shell', description: 'Switch to local shell (not shared)' },
|
|
13
|
+
{ cmd: '/tunnel <port>', description: 'Share a local port over the internet' },
|
|
14
|
+
{ cmd: '/close-tunnel', description: 'Close the active tunnel' },
|
|
13
15
|
{ cmd: '/resume', description: 'Return from shell to session' },
|
|
16
|
+
{ cmd: '/?', description: 'Show all commands' },
|
|
17
|
+
{ cmd: '/help', description: 'Show all commands' },
|
|
18
|
+
{ cmd: '/end', description: 'End the session' },
|
|
14
19
|
];
|
|
15
|
-
export const LiveSession = ({ taskId, wsClient, apiClient, permissionManager, }) => {
|
|
20
|
+
export const LiveSession = ({ taskId, wsClient, apiClient, permissionManager, tunnelManager, }) => {
|
|
16
21
|
const [messages, setMessages] = useState([]);
|
|
17
22
|
const [engineerName, setEngineerName] = useState(null);
|
|
18
23
|
const [engineerTitle, setEngineerTitle] = useState('');
|
|
19
24
|
const [creditsUsed, setCreditsUsed] = useState(0);
|
|
20
|
-
const [
|
|
25
|
+
const [_sessionDuration, setSessionDuration] = useState(0);
|
|
21
26
|
const [inputBuffer, setInputBuffer] = useState('');
|
|
22
27
|
const [isProcessingRequest, setIsProcessingRequest] = useState(false);
|
|
23
28
|
const { exit } = useApp();
|
|
24
29
|
const endingRef = useRef(false);
|
|
25
30
|
const [mode, setMode] = useState('session');
|
|
31
|
+
const [isPasteMode, setIsPasteMode] = useState(false);
|
|
32
|
+
const [activeTunnel, setActiveTunnel] = useState(null);
|
|
33
|
+
const lastInputTimeRef = useRef(0);
|
|
34
|
+
const blockReturnsUntilRef = useRef(0);
|
|
35
|
+
const shareTunnel = useCallback(async (port, subdomain, reason, requestedByEngineer = false) => {
|
|
36
|
+
if (requestedByEngineer) {
|
|
37
|
+
const approved = await permissionManager.requestTunnelPermission(port, reason);
|
|
38
|
+
if (!approved) {
|
|
39
|
+
setMessages((prev) => [
|
|
40
|
+
...prev,
|
|
41
|
+
{
|
|
42
|
+
timestamp: new Date().toISOString(),
|
|
43
|
+
sender: 'system',
|
|
44
|
+
content: `✗ Tunnel request denied for port ${port}`,
|
|
45
|
+
},
|
|
46
|
+
]);
|
|
47
|
+
try {
|
|
48
|
+
wsClient.send({
|
|
49
|
+
type: 'tunnel_error',
|
|
50
|
+
timestamp: new Date().toISOString(),
|
|
51
|
+
payload: { port, message: 'Denied by customer' },
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
// best-effort
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
setIsProcessingRequest(true);
|
|
61
|
+
try {
|
|
62
|
+
const tunnel = await tunnelManager.createTunnel({ port, subdomain, taskId });
|
|
63
|
+
setActiveTunnel(tunnel);
|
|
64
|
+
const tunnelMessage = `🔗 Tunnel shared: ${tunnel.url} (port ${tunnel.port})`;
|
|
65
|
+
setMessages((prev) => [
|
|
66
|
+
...prev,
|
|
67
|
+
{
|
|
68
|
+
timestamp: new Date().toISOString(),
|
|
69
|
+
sender: 'system',
|
|
70
|
+
content: tunnelMessage,
|
|
71
|
+
},
|
|
72
|
+
]);
|
|
73
|
+
try {
|
|
74
|
+
wsClient.send({
|
|
75
|
+
type: 'tunnel_shared',
|
|
76
|
+
timestamp: new Date().toISOString(),
|
|
77
|
+
payload: { port: tunnel.port, url: tunnel.url },
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
// best-effort to notify engineer
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
await apiClient.sendMessage(taskId, tunnelMessage);
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
// sending message is best-effort; continue
|
|
88
|
+
}
|
|
89
|
+
return tunnel;
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
const message = error?.message || 'Failed to create tunnel';
|
|
93
|
+
setMessages((prev) => [
|
|
94
|
+
...prev,
|
|
95
|
+
{
|
|
96
|
+
timestamp: new Date().toISOString(),
|
|
97
|
+
sender: 'system',
|
|
98
|
+
content: `✗ ${message}`,
|
|
99
|
+
},
|
|
100
|
+
]);
|
|
101
|
+
try {
|
|
102
|
+
wsClient.send({
|
|
103
|
+
type: 'tunnel_error',
|
|
104
|
+
timestamp: new Date().toISOString(),
|
|
105
|
+
payload: { port, message },
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
catch (sendError) {
|
|
109
|
+
// ignore
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
setIsProcessingRequest(false);
|
|
115
|
+
}
|
|
116
|
+
}, [apiClient, permissionManager, taskId, tunnelManager, wsClient]);
|
|
117
|
+
const closeActiveTunnel = useCallback(async () => {
|
|
118
|
+
if (!activeTunnel) {
|
|
119
|
+
setMessages((prev) => [
|
|
120
|
+
...prev,
|
|
121
|
+
{
|
|
122
|
+
timestamp: new Date().toISOString(),
|
|
123
|
+
sender: 'system',
|
|
124
|
+
content: 'No active tunnel to close.',
|
|
125
|
+
},
|
|
126
|
+
]);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
setIsProcessingRequest(true);
|
|
130
|
+
try {
|
|
131
|
+
await tunnelManager.closeTunnel();
|
|
132
|
+
setActiveTunnel(null);
|
|
133
|
+
setMessages((prev) => [
|
|
134
|
+
...prev,
|
|
135
|
+
{
|
|
136
|
+
timestamp: new Date().toISOString(),
|
|
137
|
+
sender: 'system',
|
|
138
|
+
content: `Tunnel closed for port ${activeTunnel.port}`,
|
|
139
|
+
},
|
|
140
|
+
]);
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
setIsProcessingRequest(false);
|
|
144
|
+
}
|
|
145
|
+
}, [activeTunnel, tunnelManager]);
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
// Enable bracketed paste mode
|
|
148
|
+
process.stdout.write('\x1b[?2004h');
|
|
149
|
+
return () => {
|
|
150
|
+
// Disable bracketed paste mode on unmount
|
|
151
|
+
process.stdout.write('\x1b[?2004l');
|
|
152
|
+
};
|
|
153
|
+
}, []);
|
|
26
154
|
useEffect(() => {
|
|
27
|
-
// Add initial message
|
|
155
|
+
// Add initial message with status and commands
|
|
156
|
+
const initMessage = `[initialized] • 🤖 [Hybrid+Vibe: 240 credits max]
|
|
157
|
+
|
|
158
|
+
AI=OpenCode (free w/ limits), Vibe=2-3 credits, Human=2 credits/min • /? for info
|
|
159
|
+
|
|
160
|
+
Quick commands:
|
|
161
|
+
/hybrid - Hybrid mode (AI → Human) (active)
|
|
162
|
+
/ai - Use AI mode only
|
|
163
|
+
/human - Request human engineer
|
|
164
|
+
/shell - Local shell (not shared)
|
|
165
|
+
/tunnel <port> - Share a local port via secure tunnel
|
|
166
|
+
/? - Show all commands
|
|
167
|
+
/end - End session
|
|
168
|
+
|
|
169
|
+
🔗 Connecting to engineer...`;
|
|
28
170
|
setMessages([
|
|
29
171
|
{
|
|
30
172
|
timestamp: new Date().toISOString(),
|
|
31
173
|
sender: 'system',
|
|
32
|
-
content:
|
|
174
|
+
content: initMessage,
|
|
33
175
|
},
|
|
34
176
|
]);
|
|
35
177
|
// WebSocket event handlers
|
|
36
178
|
wsClient.on('engineer_connected', (data) => {
|
|
37
179
|
setEngineerName(data.payload.engineerName);
|
|
38
180
|
setEngineerTitle(data.payload.engineerTitle);
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
181
|
+
// Only add message if not already connected (prevent duplicates)
|
|
182
|
+
if (!engineerName) {
|
|
183
|
+
setMessages((prev) => [
|
|
184
|
+
...prev,
|
|
185
|
+
{
|
|
186
|
+
timestamp: new Date().toISOString(),
|
|
187
|
+
sender: 'system',
|
|
188
|
+
content: 'Engineer connected',
|
|
189
|
+
},
|
|
190
|
+
]);
|
|
191
|
+
}
|
|
47
192
|
});
|
|
48
193
|
wsClient.on('engineer_message', (data) => {
|
|
49
194
|
setMessages((prev) => [
|
|
@@ -162,6 +307,10 @@ export const LiveSession = ({ taskId, wsClient, apiClient, permissionManager, })
|
|
|
162
307
|
}
|
|
163
308
|
setIsProcessingRequest(false);
|
|
164
309
|
});
|
|
310
|
+
wsClient.on('request_tunnel', async (data) => {
|
|
311
|
+
const { port, reason, subdomain } = data.payload;
|
|
312
|
+
await shareTunnel(port, subdomain, reason, true);
|
|
313
|
+
});
|
|
165
314
|
wsClient.on('screenshare_request', (data) => {
|
|
166
315
|
setMessages((prev) => [
|
|
167
316
|
...prev,
|
|
@@ -183,14 +332,18 @@ export const LiveSession = ({ taskId, wsClient, apiClient, permissionManager, })
|
|
|
183
332
|
]);
|
|
184
333
|
});
|
|
185
334
|
wsClient.on('session_end', async (data) => {
|
|
335
|
+
const endedBy = data?.payload?.endedBy || 'engineer';
|
|
336
|
+
const endMessage = endedBy === 'customer' ? 'Session ended by User' : 'Session ended by engineer';
|
|
186
337
|
setMessages((prev) => [
|
|
187
338
|
...prev,
|
|
188
339
|
{
|
|
189
340
|
timestamp: new Date().toISOString(),
|
|
190
341
|
sender: 'system',
|
|
191
|
-
content:
|
|
342
|
+
content: endMessage,
|
|
192
343
|
},
|
|
193
344
|
]);
|
|
345
|
+
// Ensure tunnels are closed when session ends
|
|
346
|
+
void tunnelManager.closeTunnel().then(() => setActiveTunnel(null));
|
|
194
347
|
// Show summary and exit after a delay
|
|
195
348
|
setTimeout(() => exit(), 2000);
|
|
196
349
|
});
|
|
@@ -214,193 +367,317 @@ export const LiveSession = ({ taskId, wsClient, apiClient, permissionManager, })
|
|
|
214
367
|
},
|
|
215
368
|
]);
|
|
216
369
|
});
|
|
370
|
+
wsClient.on('disconnected', () => {
|
|
371
|
+
setMessages((prev) => [
|
|
372
|
+
...prev,
|
|
373
|
+
{
|
|
374
|
+
timestamp: new Date().toISOString(),
|
|
375
|
+
sender: 'system',
|
|
376
|
+
content: 'Connection lost. Any active tunnels will be closed.',
|
|
377
|
+
},
|
|
378
|
+
]);
|
|
379
|
+
// Close tunnel best-effort
|
|
380
|
+
void tunnelManager.closeTunnel().then(() => setActiveTunnel(null));
|
|
381
|
+
});
|
|
217
382
|
return () => {
|
|
218
383
|
wsClient.removeAllListeners();
|
|
219
384
|
};
|
|
220
|
-
}, [wsClient, taskId, apiClient, permissionManager, exit]);
|
|
221
|
-
const
|
|
222
|
-
if (
|
|
385
|
+
}, [wsClient, taskId, apiClient, permissionManager, exit, tunnelManager, shareTunnel]);
|
|
386
|
+
const processReturnKey = () => {
|
|
387
|
+
if (!inputBuffer.trim()) {
|
|
223
388
|
return;
|
|
224
|
-
endingRef.current = true;
|
|
225
|
-
setMessages((prev) => [
|
|
226
|
-
...prev,
|
|
227
|
-
{
|
|
228
|
-
timestamp: new Date().toISOString(),
|
|
229
|
-
sender: 'system',
|
|
230
|
-
content: 'Ending session...',
|
|
231
|
-
},
|
|
232
|
-
]);
|
|
233
|
-
try {
|
|
234
|
-
wsClient.send({
|
|
235
|
-
type: 'end_session',
|
|
236
|
-
timestamp: new Date().toISOString(),
|
|
237
|
-
payload: { reason, endedBy: 'customer' },
|
|
238
|
-
});
|
|
239
389
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
// ignore
|
|
254
|
-
}
|
|
255
|
-
setTimeout(() => exit(), 200);
|
|
256
|
-
};
|
|
257
|
-
useInput((input, key) => {
|
|
258
|
-
if (key.return && inputBuffer.trim()) {
|
|
259
|
-
const message = inputBuffer.trim();
|
|
260
|
-
// Shell mode handling (local only)
|
|
261
|
-
if (mode === 'shell') {
|
|
262
|
-
if (message === '/resume' || message === '/session') {
|
|
263
|
-
setMode('session');
|
|
264
|
-
setMessages((prev) => [
|
|
265
|
-
...prev,
|
|
266
|
-
{
|
|
267
|
-
timestamp: new Date().toISOString(),
|
|
268
|
-
sender: 'system',
|
|
269
|
-
content: 'Back to session (messages will be shared with engineer).',
|
|
270
|
-
},
|
|
271
|
-
]);
|
|
272
|
-
setInputBuffer('');
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
if (message === '/end') {
|
|
276
|
-
endSession();
|
|
277
|
-
setInputBuffer('');
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
if (message === '/shell') {
|
|
281
|
-
setMessages((prev) => [
|
|
282
|
-
...prev,
|
|
283
|
-
{
|
|
284
|
-
timestamp: new Date().toISOString(),
|
|
285
|
-
sender: 'system',
|
|
286
|
-
content: 'Already in shell mode. Type /resume to return to session.',
|
|
287
|
-
},
|
|
288
|
-
]);
|
|
289
|
-
setInputBuffer('');
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
if (message === '/help' || message === '/?') {
|
|
293
|
-
setMessages((prev) => [
|
|
294
|
-
...prev,
|
|
295
|
-
...COMMANDS.map((c) => ({
|
|
296
|
-
timestamp: new Date().toISOString(),
|
|
297
|
-
sender: 'system',
|
|
298
|
-
content: `${c.cmd} — ${c.description}`,
|
|
299
|
-
})),
|
|
300
|
-
{
|
|
301
|
-
timestamp: new Date().toISOString(),
|
|
302
|
-
sender: 'system',
|
|
303
|
-
content: '/resume — return to session mode',
|
|
304
|
-
},
|
|
305
|
-
]);
|
|
306
|
-
setInputBuffer('');
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
if (message.startsWith('/')) {
|
|
310
|
-
setMessages((prev) => [
|
|
311
|
-
...prev,
|
|
312
|
-
{
|
|
313
|
-
timestamp: new Date().toISOString(),
|
|
314
|
-
sender: 'system',
|
|
315
|
-
content: `Unknown command in shell: ${message}`,
|
|
316
|
-
},
|
|
317
|
-
]);
|
|
318
|
-
setInputBuffer('');
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
// Execute local command
|
|
322
|
-
(async () => {
|
|
323
|
-
try {
|
|
324
|
-
const { stdout, stderr } = await execAsync(message, { cwd: process.cwd() });
|
|
325
|
-
setMessages((prev) => [
|
|
326
|
-
...prev,
|
|
327
|
-
{
|
|
328
|
-
timestamp: new Date().toISOString(),
|
|
329
|
-
sender: 'system',
|
|
330
|
-
content: `$ ${message}\n${stdout || ''}${stderr ? `\n${stderr}` : ''}`.trim(),
|
|
331
|
-
},
|
|
332
|
-
]);
|
|
333
|
-
}
|
|
334
|
-
catch (error) {
|
|
335
|
-
setMessages((prev) => [
|
|
336
|
-
...prev,
|
|
337
|
-
{
|
|
338
|
-
timestamp: new Date().toISOString(),
|
|
339
|
-
sender: 'system',
|
|
340
|
-
content: `$ ${message}\n${error.stdout || ''}${error.stderr || error.message || 'Command failed'}`,
|
|
341
|
-
},
|
|
342
|
-
]);
|
|
343
|
-
}
|
|
344
|
-
})();
|
|
390
|
+
const message = inputBuffer.trim();
|
|
391
|
+
// Shell mode handling
|
|
392
|
+
if (mode === 'shell') {
|
|
393
|
+
if (message === '/resume' || message === '/session') {
|
|
394
|
+
setMode('session');
|
|
395
|
+
setMessages((prev) => [
|
|
396
|
+
...prev,
|
|
397
|
+
{
|
|
398
|
+
timestamp: new Date().toISOString(),
|
|
399
|
+
sender: 'system',
|
|
400
|
+
content: 'Back to session (messages will be shared with engineer).',
|
|
401
|
+
},
|
|
402
|
+
]);
|
|
345
403
|
setInputBuffer('');
|
|
346
404
|
return;
|
|
347
405
|
}
|
|
348
|
-
// Session mode handling
|
|
349
406
|
if (message === '/end') {
|
|
350
407
|
endSession();
|
|
351
408
|
setInputBuffer('');
|
|
352
409
|
return;
|
|
353
410
|
}
|
|
354
|
-
if (message === '/
|
|
411
|
+
if (message === '/shell') {
|
|
355
412
|
setMessages((prev) => [
|
|
356
413
|
...prev,
|
|
357
|
-
|
|
414
|
+
{
|
|
358
415
|
timestamp: new Date().toISOString(),
|
|
359
416
|
sender: 'system',
|
|
360
|
-
content:
|
|
361
|
-
}
|
|
417
|
+
content: 'Already in shell mode. Type /resume to return to session.',
|
|
418
|
+
},
|
|
419
|
+
]);
|
|
420
|
+
setInputBuffer('');
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
if (message === '/help' || message === '/?') {
|
|
424
|
+
setMessages((prev) => [
|
|
425
|
+
...prev,
|
|
362
426
|
{
|
|
363
427
|
timestamp: new Date().toISOString(),
|
|
364
428
|
sender: 'system',
|
|
365
|
-
content: '
|
|
429
|
+
content: 'Available commands:',
|
|
366
430
|
},
|
|
431
|
+
...COMMANDS.map((c) => ({
|
|
432
|
+
timestamp: new Date().toISOString(),
|
|
433
|
+
sender: 'system',
|
|
434
|
+
content: `${c.cmd} — ${c.description}`,
|
|
435
|
+
})),
|
|
367
436
|
]);
|
|
368
437
|
setInputBuffer('');
|
|
369
438
|
return;
|
|
370
439
|
}
|
|
371
|
-
if (message
|
|
372
|
-
setMode('shell');
|
|
440
|
+
if (message.startsWith('/')) {
|
|
373
441
|
setMessages((prev) => [
|
|
374
442
|
...prev,
|
|
375
443
|
{
|
|
376
444
|
timestamp: new Date().toISOString(),
|
|
377
445
|
sender: 'system',
|
|
378
|
-
content:
|
|
446
|
+
content: `Unknown command in shell: ${message}`,
|
|
379
447
|
},
|
|
380
448
|
]);
|
|
381
449
|
setInputBuffer('');
|
|
382
450
|
return;
|
|
383
451
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
452
|
+
// Execute local command
|
|
453
|
+
(async () => {
|
|
454
|
+
try {
|
|
455
|
+
const { stdout, stderr } = await execAsync(message, { cwd: process.cwd() });
|
|
456
|
+
setMessages((prev) => [
|
|
457
|
+
...prev,
|
|
458
|
+
{
|
|
459
|
+
timestamp: new Date().toISOString(),
|
|
460
|
+
sender: 'system',
|
|
461
|
+
content: `$ ${message}\n${stdout || ''}${stderr ? `\n${stderr}` : ''}`.trim(),
|
|
462
|
+
},
|
|
463
|
+
]);
|
|
464
|
+
}
|
|
465
|
+
catch (error) {
|
|
466
|
+
setMessages((prev) => [
|
|
467
|
+
...prev,
|
|
468
|
+
{
|
|
469
|
+
timestamp: new Date().toISOString(),
|
|
470
|
+
sender: 'system',
|
|
471
|
+
content: `$ ${message}\n${error.stdout || ''}${error.stderr || error.message || 'Command failed'}`,
|
|
472
|
+
},
|
|
473
|
+
]);
|
|
474
|
+
}
|
|
475
|
+
})();
|
|
476
|
+
setInputBuffer('');
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
// Session mode handling
|
|
480
|
+
if (message === '/end') {
|
|
481
|
+
endSession();
|
|
482
|
+
setInputBuffer('');
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
if (message === '/hybrid') {
|
|
389
486
|
setMessages((prev) => [
|
|
390
487
|
...prev,
|
|
391
488
|
{
|
|
392
489
|
timestamp: new Date().toISOString(),
|
|
393
|
-
sender: '
|
|
394
|
-
content:
|
|
490
|
+
sender: 'system',
|
|
491
|
+
content: 'Switched to Hybrid mode (AI → Human fallback). This is the default mode.',
|
|
395
492
|
},
|
|
396
493
|
]);
|
|
397
494
|
setInputBuffer('');
|
|
495
|
+
return;
|
|
398
496
|
}
|
|
399
|
-
|
|
400
|
-
|
|
497
|
+
if (message === '/ai') {
|
|
498
|
+
setMessages((prev) => [
|
|
499
|
+
...prev,
|
|
500
|
+
{
|
|
501
|
+
timestamp: new Date().toISOString(),
|
|
502
|
+
sender: 'system',
|
|
503
|
+
content: 'Switched to AI-only mode. Only AI assistance will be used.',
|
|
504
|
+
},
|
|
505
|
+
]);
|
|
506
|
+
setInputBuffer('');
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
if (message === '/human') {
|
|
510
|
+
setMessages((prev) => [
|
|
511
|
+
...prev,
|
|
512
|
+
{
|
|
513
|
+
timestamp: new Date().toISOString(),
|
|
514
|
+
sender: 'system',
|
|
515
|
+
content: 'Requesting human engineer... (2 credits/min)',
|
|
516
|
+
},
|
|
517
|
+
]);
|
|
518
|
+
setInputBuffer('');
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
if (message === '/help' || message === '/?') {
|
|
522
|
+
setMessages((prev) => [
|
|
523
|
+
...prev,
|
|
524
|
+
{
|
|
525
|
+
timestamp: new Date().toISOString(),
|
|
526
|
+
sender: 'system',
|
|
527
|
+
content: 'Available commands:',
|
|
528
|
+
},
|
|
529
|
+
...COMMANDS.map((c) => ({
|
|
530
|
+
timestamp: new Date().toISOString(),
|
|
531
|
+
sender: 'system',
|
|
532
|
+
content: `${c.cmd} — ${c.description}`,
|
|
533
|
+
})),
|
|
534
|
+
]);
|
|
535
|
+
setInputBuffer('');
|
|
536
|
+
return;
|
|
401
537
|
}
|
|
402
|
-
|
|
403
|
-
|
|
538
|
+
if (message === '/shell') {
|
|
539
|
+
setMode('shell');
|
|
540
|
+
setMessages((prev) => [
|
|
541
|
+
...prev,
|
|
542
|
+
{
|
|
543
|
+
timestamp: new Date().toISOString(),
|
|
544
|
+
sender: 'system',
|
|
545
|
+
content: 'Switched to local shell mode (commands are NOT shared with engineer). Type /resume to return.',
|
|
546
|
+
},
|
|
547
|
+
]);
|
|
548
|
+
setInputBuffer('');
|
|
549
|
+
return;
|
|
550
|
+
}
|
|
551
|
+
if (message.startsWith('/tunnel')) {
|
|
552
|
+
const parts = message.split(' ').filter(Boolean);
|
|
553
|
+
const port = Number(parts[1]);
|
|
554
|
+
if (!port || Number.isNaN(port)) {
|
|
555
|
+
setMessages((prev) => [
|
|
556
|
+
...prev,
|
|
557
|
+
{
|
|
558
|
+
timestamp: new Date().toISOString(),
|
|
559
|
+
sender: 'system',
|
|
560
|
+
content: 'Usage: /tunnel <port>',
|
|
561
|
+
},
|
|
562
|
+
]);
|
|
563
|
+
setInputBuffer('');
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
void shareTunnel(port);
|
|
567
|
+
setInputBuffer('');
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
if (message === '/close-tunnel') {
|
|
571
|
+
void closeActiveTunnel();
|
|
572
|
+
setInputBuffer('');
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
wsClient.send({
|
|
576
|
+
type: 'customer_message',
|
|
577
|
+
timestamp: new Date().toISOString(),
|
|
578
|
+
payload: { message },
|
|
579
|
+
});
|
|
580
|
+
setMessages((prev) => [
|
|
581
|
+
...prev,
|
|
582
|
+
{
|
|
583
|
+
timestamp: new Date().toISOString(),
|
|
584
|
+
sender: 'customer',
|
|
585
|
+
content: message,
|
|
586
|
+
},
|
|
587
|
+
]);
|
|
588
|
+
setInputBuffer('');
|
|
589
|
+
};
|
|
590
|
+
const endSession = async (reason = 'Customer ended session') => {
|
|
591
|
+
if (endingRef.current)
|
|
592
|
+
return;
|
|
593
|
+
endingRef.current = true;
|
|
594
|
+
try {
|
|
595
|
+
wsClient.send({
|
|
596
|
+
type: 'end_session',
|
|
597
|
+
timestamp: new Date().toISOString(),
|
|
598
|
+
payload: { reason, endedBy: 'customer' },
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
catch (error) {
|
|
602
|
+
// ignore send errors on shutdown
|
|
603
|
+
}
|
|
604
|
+
try {
|
|
605
|
+
await apiClient.endSession(taskId);
|
|
606
|
+
}
|
|
607
|
+
catch (error) {
|
|
608
|
+
// best-effort; still proceed to exit
|
|
609
|
+
}
|
|
610
|
+
try {
|
|
611
|
+
await tunnelManager.closeTunnel();
|
|
612
|
+
setActiveTunnel(null);
|
|
613
|
+
}
|
|
614
|
+
catch (error) {
|
|
615
|
+
// ignore cleanup errors
|
|
616
|
+
}
|
|
617
|
+
try {
|
|
618
|
+
wsClient.disconnect();
|
|
619
|
+
}
|
|
620
|
+
catch (error) {
|
|
621
|
+
// ignore
|
|
622
|
+
}
|
|
623
|
+
setTimeout(() => exit(), 200);
|
|
624
|
+
};
|
|
625
|
+
useInput((input, key) => {
|
|
626
|
+
const now = Date.now();
|
|
627
|
+
// DEBUG: Log all inputs (remove after testing)
|
|
628
|
+
if (process.env.CODEVF_DEBUG) {
|
|
629
|
+
console.error(`[DEBUG] input="${input}" length=${input?.length} key.return=${key.return} key.sequence="${key.sequence}" isPasteMode=${isPasteMode}`);
|
|
630
|
+
}
|
|
631
|
+
// Handle bracketed paste mode sequences
|
|
632
|
+
if (key.sequence === '\x1b[200~') {
|
|
633
|
+
setIsPasteMode(true);
|
|
634
|
+
blockReturnsUntilRef.current = now + 500;
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
if (key.sequence === '\x1b[201~') {
|
|
638
|
+
setIsPasteMode(false);
|
|
639
|
+
// Allow returns again after 100ms
|
|
640
|
+
blockReturnsUntilRef.current = now + 100;
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
// If we receive ANY text input (not a control key), block returns for 100ms
|
|
644
|
+
// This catches paste operations even without bracketed paste mode
|
|
645
|
+
if (input && input.length > 0 && !key.return && !key.backspace && !key.delete) {
|
|
646
|
+
lastInputTimeRef.current = now;
|
|
647
|
+
blockReturnsUntilRef.current = now + 100;
|
|
648
|
+
// If multi-character input, definitely a paste
|
|
649
|
+
if (input.length > 1) {
|
|
650
|
+
setIsPasteMode(true);
|
|
651
|
+
blockReturnsUntilRef.current = now + 300;
|
|
652
|
+
}
|
|
653
|
+
// Strip newlines from input and add to buffer
|
|
654
|
+
const sanitized = input.replace(/[\r\n]+/g, ' ');
|
|
655
|
+
if (sanitized) {
|
|
656
|
+
setInputBuffer((prev) => prev + sanitized);
|
|
657
|
+
}
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
// Handle return key
|
|
661
|
+
if (key.return) {
|
|
662
|
+
// If returns are blocked (recent text input), ignore this return
|
|
663
|
+
if (now < blockReturnsUntilRef.current) {
|
|
664
|
+
if (process.env.CODEVF_DEBUG) {
|
|
665
|
+
console.error(`[DEBUG] Return blocked for ${blockReturnsUntilRef.current - now}ms`);
|
|
666
|
+
}
|
|
667
|
+
setIsPasteMode(true);
|
|
668
|
+
// Extend block period
|
|
669
|
+
blockReturnsUntilRef.current = now + 100;
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
// Returns are not blocked - this is a real Enter key press
|
|
673
|
+
setIsPasteMode(false);
|
|
674
|
+
processReturnKey();
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
// Handle backspace/delete
|
|
678
|
+
if (key.backspace || key.delete) {
|
|
679
|
+
setInputBuffer((prev) => prev.slice(0, -1));
|
|
680
|
+
return;
|
|
404
681
|
}
|
|
405
682
|
}, { isActive: !isProcessingRequest });
|
|
406
683
|
const formatTime = (timestamp) => {
|
|
@@ -408,19 +685,19 @@ export const LiveSession = ({ taskId, wsClient, apiClient, permissionManager, })
|
|
|
408
685
|
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
409
686
|
};
|
|
410
687
|
return (React.createElement(Box, { flexDirection: "column", height: "100%" },
|
|
411
|
-
React.createElement(Box, { borderStyle: "round", borderColor: "
|
|
688
|
+
React.createElement(Box, { borderStyle: "round", borderColor: "magenta", paddingX: 1, marginBottom: 1 },
|
|
412
689
|
React.createElement(Box, { flexDirection: "column" },
|
|
413
|
-
React.createElement(Text, { bold: true, color: "
|
|
414
|
-
engineerName && (React.createElement(Text, { color: "
|
|
690
|
+
React.createElement(Text, { bold: true, color: "magenta", backgroundColor: "black" }, "CodeVF Engineer Session"),
|
|
691
|
+
engineerName && (React.createElement(Text, { color: "cyan" },
|
|
415
692
|
"Engineer: ",
|
|
416
693
|
engineerName,
|
|
417
|
-
|
|
418
|
-
engineerTitle && React.createElement(Text, {
|
|
694
|
+
' ',
|
|
695
|
+
engineerTitle && React.createElement(Text, { color: "gray" },
|
|
419
696
|
"(",
|
|
420
697
|
engineerTitle,
|
|
421
698
|
")"))),
|
|
422
|
-
React.createElement(Text, {
|
|
423
|
-
"
|
|
699
|
+
React.createElement(Text, { color: "yellow" },
|
|
700
|
+
"\uD83D\uDCB0 Credits: ",
|
|
424
701
|
creditsUsed,
|
|
425
702
|
" credit",
|
|
426
703
|
creditsUsed !== 1 ? 's' : '',
|
|
@@ -431,21 +708,25 @@ export const LiveSession = ({ taskId, wsClient, apiClient, permissionManager, })
|
|
|
431
708
|
formatTime(msg.timestamp),
|
|
432
709
|
"]"),
|
|
433
710
|
React.createElement(Text, null, " "),
|
|
434
|
-
msg.sender === 'engineer' && React.createElement(
|
|
435
|
-
|
|
436
|
-
"
|
|
437
|
-
msg.sender === 'customer' && React.createElement(
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
React.createElement(Text, { color: "
|
|
711
|
+
msg.sender === 'engineer' && (React.createElement(React.Fragment, null,
|
|
712
|
+
React.createElement(Text, { color: "magenta", bold: true, backgroundColor: "black" }, "Engineer:"),
|
|
713
|
+
React.createElement(Text, null, " "))),
|
|
714
|
+
msg.sender === 'customer' && (React.createElement(React.Fragment, null,
|
|
715
|
+
React.createElement(Text, { color: "green", bold: true, backgroundColor: "black" }, "\uD83D\uDC64 You:"),
|
|
716
|
+
React.createElement(Text, null, " "))),
|
|
717
|
+
msg.sender === 'system' && (React.createElement(React.Fragment, null,
|
|
718
|
+
React.createElement(Text, { color: "yellow", bold: true, backgroundColor: "blue" }, "\u2139\uFE0F System:"),
|
|
719
|
+
React.createElement(Text, null, " "))),
|
|
720
|
+
msg.sender === 'engineer' ? (React.createElement(Text, { color: "cyan", backgroundColor: "black" }, msg.content)) : (React.createElement(Text, null, msg.content)))))),
|
|
721
|
+
React.createElement(Box, { borderStyle: "round", borderColor: mode === 'shell' ? 'yellow' : 'magenta', paddingX: 1 },
|
|
722
|
+
React.createElement(Text, { color: mode === 'shell' ? 'yellow' : 'magenta', bold: true }, mode === 'shell' ? '🐚 Local> ' : '💬 Chat: '),
|
|
444
723
|
React.createElement(Text, null, inputBuffer),
|
|
445
724
|
React.createElement(Text, { color: "gray" }, "\u258C")),
|
|
446
725
|
React.createElement(Box, { marginTop: 1 },
|
|
447
|
-
React.createElement(Text, {
|
|
448
|
-
? '
|
|
449
|
-
:
|
|
726
|
+
React.createElement(Text, { color: isPasteMode ? 'yellow' : 'cyan' }, isPasteMode
|
|
727
|
+
? '📋 Paste mode active - Press Enter when done pasting'
|
|
728
|
+
: mode === 'shell'
|
|
729
|
+
? '🔒 Shell mode (not shared). /resume to return, /? for commands.'
|
|
730
|
+
: '💡 Quick: /auto /ai /human /shell /tunnel /? /end'))));
|
|
450
731
|
};
|
|
451
732
|
//# sourceMappingURL=LiveSession.js.map
|