zugzbot 1.0.12 → 1.0.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/.utils/zugzweb/client/dist/assets/index-CcbbJtSz.css +1 -0
- package/.utils/zugzweb/client/dist/assets/index-DXxAlmSX.js +266 -0
- package/.utils/zugzweb/client/dist/index.html +2 -2
- package/.utils/zugzweb/client/src/App.tsx +180 -21
- package/.utils/zugzweb/daemon.js +49 -0
- package/bin/init.js +1 -1
- package/package.json +1 -1
- package/.utils/zugzweb/client/dist/assets/index-C6YqJdUm.js +0 -266
- package/.utils/zugzweb/client/dist/assets/index-g8FHpS8d.css +0 -1
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>client</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-DXxAlmSX.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CcbbJtSz.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="root"></div>
|
|
@@ -567,6 +567,147 @@ export default function App() {
|
|
|
567
567
|
setExpandedProjects(prev => ({ ...prev, [projectId]: !prev[projectId] }))
|
|
568
568
|
}
|
|
569
569
|
|
|
570
|
+
// Activa un proyecto levantando opencode serve en su directorio en segundo plano
|
|
571
|
+
const handleActivateProject = async (projectId: string) => {
|
|
572
|
+
const project = projects.find(p => p.id === projectId)
|
|
573
|
+
if (!project) return
|
|
574
|
+
|
|
575
|
+
setIsProcessing(true)
|
|
576
|
+
setErrorMsg('Activando entorno de proyecto en segundo plano...')
|
|
577
|
+
try {
|
|
578
|
+
const res = await fetch('/api-custom/activate-project', {
|
|
579
|
+
method: 'POST',
|
|
580
|
+
headers: { 'Content-Type': 'application/json' },
|
|
581
|
+
body: JSON.stringify({ path: project.path })
|
|
582
|
+
})
|
|
583
|
+
if (res.ok) {
|
|
584
|
+
setActiveProjectId(projectId)
|
|
585
|
+
|
|
586
|
+
// Limpiar estados de sesión de la instancia anterior
|
|
587
|
+
setSessions([])
|
|
588
|
+
setCurrentSessionId('')
|
|
589
|
+
setCurrentSession(null)
|
|
590
|
+
setMessages([])
|
|
591
|
+
|
|
592
|
+
// Forzar recarga de sesiones del nuevo entorno
|
|
593
|
+
await fetchSessions()
|
|
594
|
+
setErrorMsg('')
|
|
595
|
+
} else {
|
|
596
|
+
const errData = await res.json().catch(() => ({}))
|
|
597
|
+
setErrorMsg(errData.error || 'Error al activar el proyecto')
|
|
598
|
+
}
|
|
599
|
+
} catch {
|
|
600
|
+
setErrorMsg('No se pudo conectar con el Daemon para activar el proyecto')
|
|
601
|
+
} finally {
|
|
602
|
+
setIsProcessing(false)
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Crea e inicia de forma inmediata una sesión dentro de un proyecto específico de un solo clic
|
|
607
|
+
const handleCreateSessionInProject = async (projectId: string) => {
|
|
608
|
+
const project = projects.find(p => p.id === projectId)
|
|
609
|
+
if (!project) return
|
|
610
|
+
|
|
611
|
+
// 1. Si el proyecto no es el activo, activarlo primero en segundo plano de forma transparente
|
|
612
|
+
if (activeProjectId !== projectId) {
|
|
613
|
+
setIsProcessing(true)
|
|
614
|
+
setErrorMsg(`Activando entorno de "${project.name}"...`)
|
|
615
|
+
try {
|
|
616
|
+
const res = await fetch('/api-custom/activate-project', {
|
|
617
|
+
method: 'POST',
|
|
618
|
+
headers: { 'Content-Type': 'application/json' },
|
|
619
|
+
body: JSON.stringify({ path: project.path })
|
|
620
|
+
})
|
|
621
|
+
if (res.ok) {
|
|
622
|
+
setActiveProjectId(projectId)
|
|
623
|
+
setSessions([])
|
|
624
|
+
setCurrentSessionId('')
|
|
625
|
+
setCurrentSession(null)
|
|
626
|
+
setMessages([])
|
|
627
|
+
setErrorMsg('')
|
|
628
|
+
} else {
|
|
629
|
+
alert(`No se pudo activar el proyecto: ${project.name}`)
|
|
630
|
+
setIsProcessing(false)
|
|
631
|
+
return
|
|
632
|
+
}
|
|
633
|
+
} catch {
|
|
634
|
+
alert(`Error al conectar al Daemon para activar el proyecto: ${project.name}`)
|
|
635
|
+
setIsProcessing(false)
|
|
636
|
+
return
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// 2. Solicitar el título de la sesión de forma sencilla
|
|
641
|
+
const title = prompt(`Introduce el título de la nueva sesión para "${project.name}":`, `Sesión ${new Date().toLocaleDateString()}`)
|
|
642
|
+
if (!title || !title.trim()) return
|
|
643
|
+
|
|
644
|
+
setIsProcessing(true)
|
|
645
|
+
setErrorMsg('Iniciando sesión...')
|
|
646
|
+
try {
|
|
647
|
+
const res = await fetch('/api/session', {
|
|
648
|
+
method: 'POST',
|
|
649
|
+
headers: { 'Content-Type': 'application/json' },
|
|
650
|
+
body: JSON.stringify({ title: title.trim() })
|
|
651
|
+
})
|
|
652
|
+
if (res.ok) {
|
|
653
|
+
const newSess = await res.json()
|
|
654
|
+
setSessions(prev => [newSess, ...prev])
|
|
655
|
+
setCurrentSessionId(newSess.id)
|
|
656
|
+
setCurrentSession(newSess)
|
|
657
|
+
setMessages([])
|
|
658
|
+
|
|
659
|
+
// Guardar mapeo sesión -> proyecto
|
|
660
|
+
setSessionMappings(prev => ({
|
|
661
|
+
...prev,
|
|
662
|
+
[newSess.id]: projectId
|
|
663
|
+
}))
|
|
664
|
+
|
|
665
|
+
// Redirigir a la vista de chat
|
|
666
|
+
navigateTo('/')
|
|
667
|
+
} else {
|
|
668
|
+
alert('Error al crear la sesión en Opencode')
|
|
669
|
+
}
|
|
670
|
+
} catch {
|
|
671
|
+
alert('Error de conexión al crear sesión')
|
|
672
|
+
} finally {
|
|
673
|
+
setIsProcessing(false)
|
|
674
|
+
setErrorMsg('')
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Elimina de forma permanente una sesión y sus mensajes en la API de Opencode
|
|
679
|
+
const handleDeleteSession = async (sessionId: string, e: React.MouseEvent) => {
|
|
680
|
+
e.stopPropagation()
|
|
681
|
+
|
|
682
|
+
if (!confirm('¿Estás seguro de que quieres eliminar esta sesión y todos sus mensajes permanentemente?')) {
|
|
683
|
+
return
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
try {
|
|
687
|
+
const res = await fetch(`/api/session/${sessionId}`, {
|
|
688
|
+
method: 'DELETE'
|
|
689
|
+
})
|
|
690
|
+
if (res.ok) {
|
|
691
|
+
setSessions(prev => prev.filter(s => s.id !== sessionId))
|
|
692
|
+
setSessionMappings(prev => {
|
|
693
|
+
const copy = { ...prev }
|
|
694
|
+
delete copy[sessionId]
|
|
695
|
+
return copy
|
|
696
|
+
})
|
|
697
|
+
|
|
698
|
+
if (currentSessionId === sessionId) {
|
|
699
|
+
setCurrentSessionId('')
|
|
700
|
+
setCurrentSession(null)
|
|
701
|
+
setMessages([])
|
|
702
|
+
}
|
|
703
|
+
} else {
|
|
704
|
+
alert('No se pudo eliminar la sesión de Opencode')
|
|
705
|
+
}
|
|
706
|
+
} catch {
|
|
707
|
+
alert('Error de red al eliminar la sesión')
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
570
711
|
// Handler para el resize de la barra lateral
|
|
571
712
|
const handleMouseDown = (e: React.MouseEvent) => {
|
|
572
713
|
e.preventDefault()
|
|
@@ -980,9 +1121,16 @@ export default function App() {
|
|
|
980
1121
|
</button>
|
|
981
1122
|
|
|
982
1123
|
<div className="flex items-center gap-1 shrink-0 ml-1">
|
|
1124
|
+
<button
|
|
1125
|
+
onClick={() => handleCreateSessionInProject(project.id)}
|
|
1126
|
+
className="p-1 hover:bg-[#27272a] text-emerald-500 hover:text-emerald-400 rounded transition cursor-pointer"
|
|
1127
|
+
title="Crear Sesión en este proyecto"
|
|
1128
|
+
>
|
|
1129
|
+
<Plus size={10} strokeWidth={3} />
|
|
1130
|
+
</button>
|
|
983
1131
|
{!isProjectActive && (
|
|
984
1132
|
<button
|
|
985
|
-
onClick={() =>
|
|
1133
|
+
onClick={() => handleActivateProject(project.id)}
|
|
986
1134
|
className="p-1 hover:bg-[#27272a] text-[#71717a] hover:text-white rounded transition cursor-pointer"
|
|
987
1135
|
title="Activar Proyecto"
|
|
988
1136
|
>
|
|
@@ -1017,26 +1165,37 @@ export default function App() {
|
|
|
1017
1165
|
return (
|
|
1018
1166
|
<div key={root.id} className="space-y-1">
|
|
1019
1167
|
{/* Sesión Principal */}
|
|
1020
|
-
<
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
<
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1168
|
+
<div className={`w-full group/sess flex items-center justify-between rounded-lg p-1.5 transition duration-200 ${
|
|
1169
|
+
isRootActive
|
|
1170
|
+
? 'bg-[#1c1c1f] border border-[#2e2e33] text-white shadow-sm'
|
|
1171
|
+
: 'hover:bg-[#121215] text-[#a1a1aa] hover:text-white'
|
|
1172
|
+
}`}>
|
|
1173
|
+
<button
|
|
1174
|
+
onClick={() => {
|
|
1175
|
+
setCurrentSessionId(root.id)
|
|
1176
|
+
setErrorMsg('')
|
|
1177
|
+
navigateTo('/')
|
|
1178
|
+
}}
|
|
1179
|
+
className="flex-1 text-left cursor-pointer outline-none min-w-0"
|
|
1180
|
+
>
|
|
1181
|
+
<span className="font-semibold truncate text-[11px] block">
|
|
1182
|
+
💬 {root.title || `Sesión: ${root.id.substring(0, 8)}`}
|
|
1183
|
+
</span>
|
|
1184
|
+
<div className="flex items-center justify-between text-[9px] text-[#52525b] mt-0.5">
|
|
1185
|
+
<span>{root.agent || 'sdd-orchestrator'}</span>
|
|
1186
|
+
<span>{formatModelName(root.model)}</span>
|
|
1187
|
+
</div>
|
|
1188
|
+
</button>
|
|
1189
|
+
|
|
1190
|
+
{/* Botón de eliminar sesión */}
|
|
1191
|
+
<button
|
|
1192
|
+
onClick={(e) => handleDeleteSession(root.id, e)}
|
|
1193
|
+
className="opacity-0 group-hover/sess:opacity-100 p-1 hover:bg-red-950/40 text-[#71717a] hover:text-red-400 rounded transition cursor-pointer shrink-0 ml-1"
|
|
1194
|
+
title="Eliminar Sesión"
|
|
1195
|
+
>
|
|
1196
|
+
<Square size={10} />
|
|
1197
|
+
</button>
|
|
1198
|
+
</div>
|
|
1040
1199
|
|
|
1041
1200
|
{/* Subagentes hijas */}
|
|
1042
1201
|
{hasSubagents && (
|
package/.utils/zugzweb/daemon.js
CHANGED
|
@@ -192,6 +192,55 @@ const startServer = () => {
|
|
|
192
192
|
return
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
+
// 3c. Endpoint: Activar un proyecto levantando opencode serve en su directorio
|
|
196
|
+
if (req.url === '/api-custom/activate-project' && req.method === 'POST') {
|
|
197
|
+
let body = ''
|
|
198
|
+
req.on('data', chunk => body += chunk)
|
|
199
|
+
req.on('end', () => {
|
|
200
|
+
try {
|
|
201
|
+
const parsed = JSON.parse(body)
|
|
202
|
+
const targetPath = parsed.path
|
|
203
|
+
if (targetPath && path.isAbsolute(targetPath)) {
|
|
204
|
+
// 1. Matar cualquier proceso escuchando en el puerto 4096 (el puerto por defecto)
|
|
205
|
+
exec('lsof -t -i :4096', (err, stdout) => {
|
|
206
|
+
if (stdout) {
|
|
207
|
+
const pids = stdout.split('\n').filter(Boolean)
|
|
208
|
+
for (const pid of pids) {
|
|
209
|
+
try {
|
|
210
|
+
process.kill(parseInt(pid, 10), 'SIGKILL')
|
|
211
|
+
} catch (e) {
|
|
212
|
+
// ignore
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// 2. Levantar opencode serve con cwd set al directorio del proyecto
|
|
218
|
+
console.log(`\x1b[35m%s\x1b[0m`, `🚀 Levantando opencode serve en: ${targetPath}`)
|
|
219
|
+
const opencodeProcess = spawn('opencode', ['serve', '--port', '4096'], {
|
|
220
|
+
cwd: targetPath,
|
|
221
|
+
detached: true,
|
|
222
|
+
stdio: 'ignore'
|
|
223
|
+
})
|
|
224
|
+
opencodeProcess.unref()
|
|
225
|
+
|
|
226
|
+
// Esperar a que el servidor esté listo
|
|
227
|
+
setTimeout(() => {
|
|
228
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
229
|
+
res.end(JSON.stringify({ success: true, message: `Proyecto activo en ${targetPath}` }))
|
|
230
|
+
}, 2000)
|
|
231
|
+
})
|
|
232
|
+
} else {
|
|
233
|
+
res.writeHead(400, { 'Content-Type': 'application/json' })
|
|
234
|
+
res.end(JSON.stringify({ error: 'Ruta absoluta inválida o ausente' }))
|
|
235
|
+
}
|
|
236
|
+
} catch (e) {
|
|
237
|
+
res.writeHead(500, { 'Content-Type': 'application/json' })
|
|
238
|
+
res.end(JSON.stringify({ error: 'Error al activar el proyecto', detail: e.message }))
|
|
239
|
+
}
|
|
240
|
+
})
|
|
241
|
+
return
|
|
242
|
+
}
|
|
243
|
+
|
|
195
244
|
// 4. Proxy inverso para las APIs de Opencode (prefijo /api)
|
|
196
245
|
if (req.url.startsWith('/api/')) {
|
|
197
246
|
const targetPath = req.url.substring(4) // Quita el '/api'
|
package/bin/init.js
CHANGED
|
@@ -25,7 +25,7 @@ ${bold}${cyan}███████╗██╗ ██╗ ██████
|
|
|
25
25
|
███╔╝ ██║ ██║██║ ██║ ███╔╝
|
|
26
26
|
███████╗╚██████╔╝╚██████╔╝███████╗
|
|
27
27
|
╚══════╝ ╚═════╝ ╚═════╝ ╚══════╝${reset}
|
|
28
|
-
${bold}${yellow} Harness Installer v1.0.
|
|
28
|
+
${bold}${yellow} Harness Installer v1.0.13${reset}\n`;
|
|
29
29
|
|
|
30
30
|
console.log(banner);
|
|
31
31
|
console.log(`${bold}${cyan}🔍 Detectando entorno de trabajo...${reset}`);
|