@agentprojectcontext/apx 1.56.2 → 1.58.0
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/package.json +1 -1
- package/src/core/apc/paths.js +7 -0
- package/src/core/stores/conversations.js +10 -0
- package/src/core/stores/messages.js +37 -6
- package/src/core/stores/organization.js +152 -0
- package/src/core/stores/project-files.js +199 -0
- package/src/core/stores/tasks.js +36 -3
- package/src/host/daemon/api/agents.js +22 -2
- package/src/host/daemon/api/conversations.js +20 -2
- package/src/host/daemon/api/files-project.js +99 -0
- package/src/host/daemon/api/organization.js +88 -0
- package/src/host/daemon/api/shared.js +7 -0
- package/src/host/daemon/api/tasks.js +14 -0
- package/src/host/daemon/api.js +4 -0
- package/src/interfaces/cli/commands/org.js +77 -0
- package/src/interfaces/cli/index.js +48 -0
- package/src/interfaces/web/dist/assets/index-Cl0WXtxF.css +1 -0
- package/src/interfaces/web/dist/assets/index-DPAuXATr.js +705 -0
- package/src/interfaces/web/dist/assets/index-DPAuXATr.js.map +1 -0
- package/src/interfaces/web/dist/index.html +2 -2
- package/src/interfaces/web/package-lock.json +6 -6
- package/src/interfaces/web/src/App.tsx +1 -1
- package/src/interfaces/web/src/components/agents/AgentFormFields.tsx +123 -0
- package/src/interfaces/web/src/components/chat/ChatList.tsx +86 -65
- package/src/interfaces/web/src/components/common/ConfirmDialog.tsx +51 -0
- package/src/interfaces/web/src/components/common/TabNav.tsx +1 -1
- package/src/interfaces/web/src/components/files/FileBrowser.tsx +138 -0
- package/src/interfaces/web/src/components/files/FileTree.tsx +133 -0
- package/src/interfaces/web/src/components/files/FileViewer.tsx +167 -0
- package/src/interfaces/web/src/components/files/MarkdownEditor.tsx +48 -0
- package/src/interfaces/web/src/components/files/MarkdownPreview.tsx +146 -0
- package/src/interfaces/web/src/components/files/NewFileDialog.tsx +66 -0
- package/src/interfaces/web/src/components/structure/StructureDialogs.tsx +172 -0
- package/src/interfaces/web/src/components/tasks/TaskDetailPanel.tsx +142 -0
- package/src/interfaces/web/src/components/tasks/taskStatus.tsx +57 -0
- package/src/interfaces/web/src/hooks/useChat.ts +39 -7
- package/src/interfaces/web/src/i18n/en.ts +113 -3
- package/src/interfaces/web/src/i18n/es.ts +113 -3
- package/src/interfaces/web/src/lib/api/conversations.ts +6 -0
- package/src/interfaces/web/src/lib/api/organization.ts +18 -0
- package/src/interfaces/web/src/lib/api/projectFiles.ts +19 -0
- package/src/interfaces/web/src/lib/api/tasks.ts +16 -1
- package/src/interfaces/web/src/lib/api.ts +2 -0
- package/src/interfaces/web/src/lib/slug.ts +11 -0
- package/src/interfaces/web/src/screens/ProjectScreen.tsx +22 -3
- package/src/interfaces/web/src/screens/SettingsScreen.tsx +1 -1
- package/src/interfaces/web/src/screens/project/AgentDetailScreen.tsx +31 -11
- package/src/interfaces/web/src/screens/project/AgentsTab.tsx +24 -7
- package/src/interfaces/web/src/screens/project/ChatTab.tsx +136 -36
- package/src/interfaces/web/src/screens/project/DocsTab.tsx +13 -0
- package/src/interfaces/web/src/screens/project/FilesTab.tsx +12 -0
- package/src/interfaces/web/src/screens/project/Overview.tsx +122 -10
- package/src/interfaces/web/src/screens/project/StructureTab.tsx +147 -0
- package/src/interfaces/web/src/screens/project/TasksTab.tsx +101 -62
- package/src/interfaces/web/src/types/daemon.ts +68 -0
- package/src/interfaces/web/dist/assets/index-CAUezTBY.css +0 -1
- package/src/interfaces/web/dist/assets/index-DaE_memX.js +0 -651
- package/src/interfaces/web/dist/assets/index-DaE_memX.js.map +0 -1
|
@@ -33,6 +33,10 @@ export const es = {
|
|
|
33
33
|
hide: "Ocultar",
|
|
34
34
|
copy: "Copiar",
|
|
35
35
|
run: "Ejecutar",
|
|
36
|
+
refresh: "Refrescar",
|
|
37
|
+
view_all: "Ver todo",
|
|
38
|
+
saved: "Guardado.",
|
|
39
|
+
deleted: "Eliminado.",
|
|
36
40
|
pager_prev: "Anterior",
|
|
37
41
|
pager_next: "Siguiente",
|
|
38
42
|
pager_page: "Página {page} de {total}",
|
|
@@ -293,10 +297,14 @@ export const es = {
|
|
|
293
297
|
vars: "Variables",
|
|
294
298
|
logs: "Logs",
|
|
295
299
|
memories: "Memorias",
|
|
300
|
+
structure: "Estructura",
|
|
301
|
+
docs: "Docs",
|
|
302
|
+
files: "Archivos",
|
|
296
303
|
},
|
|
297
304
|
|
|
298
305
|
sections: {
|
|
299
306
|
workspace: "Workspace",
|
|
307
|
+
content: "Contenido",
|
|
300
308
|
automation: "Automatización",
|
|
301
309
|
knowledge: "Conversaciones",
|
|
302
310
|
config: "Config",
|
|
@@ -305,11 +313,18 @@ export const es = {
|
|
|
305
313
|
overview: {
|
|
306
314
|
tasks_open: "Tasks abiertas",
|
|
307
315
|
routines: "Rutinas",
|
|
316
|
+
routines_active: "Rutinas activas",
|
|
308
317
|
agents: "Agents",
|
|
309
318
|
mcps: "MCPs",
|
|
310
319
|
artifacts: "Artifacts",
|
|
311
320
|
chat: "Chat (super-agent)",
|
|
312
321
|
chat_value: "abrir",
|
|
322
|
+
roster: "Equipo",
|
|
323
|
+
no_agents: "Todavía no hay agentes.",
|
|
324
|
+
orchestrators: "Orquestadores",
|
|
325
|
+
specialists: "Especialistas",
|
|
326
|
+
recent_tasks: "Tasks recientes",
|
|
327
|
+
no_activity: "No hay tasks abiertas.",
|
|
313
328
|
},
|
|
314
329
|
|
|
315
330
|
artifacts: {
|
|
@@ -320,6 +335,7 @@ export const es = {
|
|
|
320
335
|
chat: {
|
|
321
336
|
title: "Chat con agente",
|
|
322
337
|
subtitle: "Chat directo con el agente del proyecto.",
|
|
338
|
+
live_title: "Chat con {agent}",
|
|
323
339
|
superagent_title: "Chat con {persona}",
|
|
324
340
|
superagent_subtitle: "Chat con {persona} — el super-agente APX. Puede usar tools (proyectos, tasks, mcps, agentes).",
|
|
325
341
|
loaded_subtitle: "Conversación cargada con {slug}. Lo que mandes se agrega a este chat.",
|
|
@@ -328,7 +344,13 @@ export const es = {
|
|
|
328
344
|
placeholder: "Escribí algo y enter para enviar (shift+enter = nueva línea)",
|
|
329
345
|
send: "Enviar",
|
|
330
346
|
stop: "Stop",
|
|
331
|
-
|
|
347
|
+
new_session: "Nueva sesión",
|
|
348
|
+
delete: "Borrar",
|
|
349
|
+
delete_confirm_title: "Borrar chat",
|
|
350
|
+
delete_confirm_desc: "Esta acción no se puede deshacer. Se elimina para siempre el historial de este chat.",
|
|
351
|
+
deleted: "Chat borrado.",
|
|
352
|
+
meta_created: "Creado {date} · {channel}",
|
|
353
|
+
meta_new: "Chat nuevo · {channel}",
|
|
332
354
|
copy: "copiar",
|
|
333
355
|
copied: "Copiado.",
|
|
334
356
|
stopped_marker: " [detenido]",
|
|
@@ -346,8 +368,7 @@ export const es = {
|
|
|
346
368
|
all_agents: "Todos los agentes",
|
|
347
369
|
empty: "No hay conversaciones todavía. Arrancá una desde la derecha.",
|
|
348
370
|
count: "{n} en total",
|
|
349
|
-
|
|
350
|
-
live_subtitle: "Sesión en memoria",
|
|
371
|
+
pick_agent: "Elegí un agente",
|
|
351
372
|
},
|
|
352
373
|
},
|
|
353
374
|
|
|
@@ -1293,6 +1314,95 @@ export const es = {
|
|
|
1293
1314
|
role_assigned: "{name} → {role}",
|
|
1294
1315
|
},
|
|
1295
1316
|
|
|
1317
|
+
agents_form: {
|
|
1318
|
+
emoji: "Emoji",
|
|
1319
|
+
area: "Área",
|
|
1320
|
+
role: "Rol",
|
|
1321
|
+
no_role: "— sin rol —",
|
|
1322
|
+
autonomy: "Autonomía",
|
|
1323
|
+
autonomy_hint: "Cuánto puede hacer el agente sin pedir confirmación.",
|
|
1324
|
+
auto_total: "Total",
|
|
1325
|
+
auto_automatico: "Auto",
|
|
1326
|
+
auto_permiso: "Permiso",
|
|
1327
|
+
},
|
|
1328
|
+
|
|
1329
|
+
structure: {
|
|
1330
|
+
title: "Estructura",
|
|
1331
|
+
subtitle: "Áreas y roles de la empresa. Las áreas agrupan agentes; los roles definen su función.",
|
|
1332
|
+
info: "Las áreas son agrupaciones opcionales. Los roles definen la función de un agente y pueden pertenecer a un área.",
|
|
1333
|
+
empty: "Todavía no hay áreas ni roles. Creá el primero arriba.",
|
|
1334
|
+
new_area: "Nueva área",
|
|
1335
|
+
new_role: "Nuevo rol",
|
|
1336
|
+
edit_area: "Editar área",
|
|
1337
|
+
edit_role: "Editar rol",
|
|
1338
|
+
create_area: "Crear área",
|
|
1339
|
+
create_role: "Crear rol",
|
|
1340
|
+
name: "Nombre",
|
|
1341
|
+
slug: "Slug",
|
|
1342
|
+
goal: "Objetivo",
|
|
1343
|
+
goal_hint: "Para qué existe esta área (opcional).",
|
|
1344
|
+
area: "Área",
|
|
1345
|
+
description: "Descripción",
|
|
1346
|
+
no_area: "— sin área —",
|
|
1347
|
+
roles: "Roles",
|
|
1348
|
+
add_role: "rol",
|
|
1349
|
+
no_roles: "sin roles",
|
|
1350
|
+
general_roles: "Roles generales",
|
|
1351
|
+
delete_area: "Borrar área",
|
|
1352
|
+
delete_role: "Borrar rol",
|
|
1353
|
+
delete_area_desc: "¿Borrar el área \"{name}\"? Sus roles quedan sin área, no se eliminan.",
|
|
1354
|
+
delete_role_desc: "¿Borrar el rol \"{name}\"?",
|
|
1355
|
+
},
|
|
1356
|
+
|
|
1357
|
+
files: {
|
|
1358
|
+
docs_label: "Docs",
|
|
1359
|
+
files_label: "Archivos",
|
|
1360
|
+
new_doc: "Nuevo documento",
|
|
1361
|
+
new_doc_hint: "Podés usar carpetas: cases/onboarding/spec.md",
|
|
1362
|
+
empty: "No hay archivos.",
|
|
1363
|
+
docs_empty: "Todavía no hay documentación. Creá el primer documento.",
|
|
1364
|
+
truncated: "Listado recortado (demasiados archivos).",
|
|
1365
|
+
select_prompt: "Elegí un archivo para verlo.",
|
|
1366
|
+
save: "Guardar",
|
|
1367
|
+
saved: "Guardado.",
|
|
1368
|
+
deleted: "Eliminado.",
|
|
1369
|
+
created: "Documento creado.",
|
|
1370
|
+
edit: "Editar",
|
|
1371
|
+
preview: "Preview",
|
|
1372
|
+
discard: "Descartar",
|
|
1373
|
+
no_preview: "No hay preview para este archivo.",
|
|
1374
|
+
too_large: "Archivo demasiado grande para mostrar.",
|
|
1375
|
+
path_label: "Ruta del archivo",
|
|
1376
|
+
path_example: "ej. cases/onboarding/spec.md",
|
|
1377
|
+
create: "Crear",
|
|
1378
|
+
},
|
|
1379
|
+
|
|
1380
|
+
tasks: {
|
|
1381
|
+
state_open: "abiertas",
|
|
1382
|
+
state_done: "hechas",
|
|
1383
|
+
state_dropped: "descartadas",
|
|
1384
|
+
status_pending: "pendiente",
|
|
1385
|
+
status_running: "corriendo",
|
|
1386
|
+
status_in_review: "en revisión",
|
|
1387
|
+
status_blocked: "bloqueada",
|
|
1388
|
+
done_label: "hecha",
|
|
1389
|
+
dropped_label: "descartada",
|
|
1390
|
+
detail_title: "Detalle de task",
|
|
1391
|
+
field_title: "Título",
|
|
1392
|
+
field_prompt: "Prompt",
|
|
1393
|
+
field_status: "Estado",
|
|
1394
|
+
field_agent: "Agente",
|
|
1395
|
+
field_creator: "Creada por",
|
|
1396
|
+
field_source: "Origen",
|
|
1397
|
+
field_created: "Creada",
|
|
1398
|
+
field_updated: "Actualizada",
|
|
1399
|
+
field_done: "Completada",
|
|
1400
|
+
prompt_ph: "Descripción / prompt de la task…",
|
|
1401
|
+
toggle_prompt: "Prompt",
|
|
1402
|
+
view_thread: "Ver conversación",
|
|
1403
|
+
mark_done: "Completar",
|
|
1404
|
+
},
|
|
1405
|
+
|
|
1296
1406
|
agents_ui: {
|
|
1297
1407
|
model_router_default: "modelo: default del router",
|
|
1298
1408
|
slug_kebab_hint: "kebab-case, ej. reviewer, my-agent, content-writer",
|
|
@@ -12,6 +12,12 @@ export const Conversations = {
|
|
|
12
12
|
http.get<ThreadListEntry[]>(`/projects/${pid}/super-agent/threads`),
|
|
13
13
|
thread: (pid: string, channel: string, id: string) =>
|
|
14
14
|
http.get<ThreadDetail>(`/projects/${pid}/super-agent/threads/${channel}/${id}`),
|
|
15
|
+
// Delete a persisted agent conversation (its `.md` file).
|
|
16
|
+
remove: (pid: string, slug: string, id: string) =>
|
|
17
|
+
http.del<{ ok: boolean }>(`/projects/${pid}/agents/${slug}/conversations/${id}`),
|
|
18
|
+
// Delete a super-agent channel thread (its channel+day ledger file).
|
|
19
|
+
removeThread: (pid: string, channel: string, id: string) =>
|
|
20
|
+
http.del<{ ok: boolean }>(`/projects/${pid}/super-agent/threads/${channel}/${id}`),
|
|
15
21
|
compact: (pid: string, slug: string, id?: string) =>
|
|
16
22
|
http.post<{ ok?: boolean }>(
|
|
17
23
|
id
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { http } from "../http";
|
|
2
|
+
import type { Organization, OrgArea, OrgRole } from "../../types/daemon";
|
|
3
|
+
|
|
4
|
+
export const Org = {
|
|
5
|
+
get: (pid: string) => http.get<Organization>(`/projects/${pid}/organization`),
|
|
6
|
+
createArea: (pid: string, body: { name: string; slug?: string; goal?: string | null }) =>
|
|
7
|
+
http.post<OrgArea>(`/projects/${pid}/organization/areas`, body),
|
|
8
|
+
updateArea: (pid: string, slug: string, patch: { name?: string; goal?: string | null }) =>
|
|
9
|
+
http.patch<OrgArea>(`/projects/${pid}/organization/areas/${encodeURIComponent(slug)}`, patch),
|
|
10
|
+
removeArea: (pid: string, slug: string) =>
|
|
11
|
+
http.del<{ ok: boolean }>(`/projects/${pid}/organization/areas/${encodeURIComponent(slug)}`),
|
|
12
|
+
createRole: (pid: string, body: { name: string; slug?: string; area?: string | null; description?: string | null }) =>
|
|
13
|
+
http.post<OrgRole>(`/projects/${pid}/organization/roles`, body),
|
|
14
|
+
updateRole: (pid: string, slug: string, patch: { name?: string; area?: string | null; description?: string | null }) =>
|
|
15
|
+
http.patch<OrgRole>(`/projects/${pid}/organization/roles/${encodeURIComponent(slug)}`, patch),
|
|
16
|
+
removeRole: (pid: string, slug: string) =>
|
|
17
|
+
http.del<{ ok: boolean }>(`/projects/${pid}/organization/roles/${encodeURIComponent(slug)}`),
|
|
18
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { http } from "../http";
|
|
2
|
+
import type { FileTreeResponse, FileContent } from "../../types/daemon";
|
|
3
|
+
|
|
4
|
+
export type FileScope = "project" | "docs";
|
|
5
|
+
|
|
6
|
+
// Project file browser + docs editor. `scope` picks the sandbox: the whole
|
|
7
|
+
// repo ("project") or the docs subfolder ("docs"). Same store, different root.
|
|
8
|
+
export const ProjectFiles = {
|
|
9
|
+
tree: (pid: string, scope: FileScope = "project") =>
|
|
10
|
+
http.get<FileTreeResponse>(`/projects/${pid}/fs/tree?scope=${scope}`),
|
|
11
|
+
read: (pid: string, path: string, scope: FileScope = "project") =>
|
|
12
|
+
http.get<FileContent>(`/projects/${pid}/fs/file?scope=${scope}&path=${encodeURIComponent(path)}`),
|
|
13
|
+
write: (pid: string, path: string, content: string, scope: FileScope = "project") =>
|
|
14
|
+
http.put<{ ok: boolean; path: string; bytes: number }>(`/projects/${pid}/fs/file`, { scope, path, content }),
|
|
15
|
+
mkdir: (pid: string, path: string, scope: FileScope = "project") =>
|
|
16
|
+
http.post<{ ok: boolean; path: string }>(`/projects/${pid}/fs/dir`, { scope, path }),
|
|
17
|
+
remove: (pid: string, path: string, scope: FileScope = "project") =>
|
|
18
|
+
http.del<{ ok: boolean }>(`/projects/${pid}/fs/entry?scope=${scope}&path=${encodeURIComponent(path)}`),
|
|
19
|
+
};
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import { http, unwrapPage } from "../http";
|
|
2
|
-
import type { TaskEntry } from "../../types/daemon";
|
|
2
|
+
import type { TaskEntry, TaskStatus } from "../../types/daemon";
|
|
3
3
|
|
|
4
4
|
export interface GlobalTaskEntry extends TaskEntry {
|
|
5
5
|
project_id: number;
|
|
6
6
|
project_name: string;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
export interface TaskSummary {
|
|
10
|
+
open: number;
|
|
11
|
+
done: number;
|
|
12
|
+
dropped: number;
|
|
13
|
+
overdue: number;
|
|
14
|
+
total: number;
|
|
15
|
+
status: Record<TaskStatus, number>;
|
|
16
|
+
}
|
|
17
|
+
|
|
9
18
|
export const Tasks = {
|
|
10
19
|
// Full sets (no pagination) — unwrapped to plain arrays for non-paged callers.
|
|
11
20
|
list: (pid: string, state: TaskEntry["state"] | "all" = "open") =>
|
|
@@ -18,9 +27,15 @@ export const Tasks = {
|
|
|
18
27
|
http.get<unknown>(`/projects/${pid}/tasks?state=${state}&limit=${limit}&offset=${offset}`).then((b) => unwrapPage<TaskEntry>(b)),
|
|
19
28
|
globalPage: ({ state, limit, offset }: { state: TaskEntry["state"] | "all"; limit: number; offset: number }) =>
|
|
20
29
|
http.get<unknown>(`/tasks?state=${state}&limit=${limit}&offset=${offset}`).then((b) => unwrapPage<GlobalTaskEntry>(b)),
|
|
30
|
+
get: (pid: string, id: string) => http.get<TaskEntry>(`/projects/${pid}/tasks/${id}`),
|
|
21
31
|
add: (pid: string, body: Partial<TaskEntry>) =>
|
|
22
32
|
http.post<TaskEntry>(`/projects/${pid}/tasks`, body),
|
|
33
|
+
patch: (pid: string, id: string, patch: Partial<TaskEntry>) =>
|
|
34
|
+
http.patch<TaskEntry>(`/projects/${pid}/tasks/${id}`, { patch }),
|
|
35
|
+
status: (pid: string, id: string, status: TaskStatus) =>
|
|
36
|
+
http.post<TaskEntry>(`/projects/${pid}/tasks/${id}/status`, { status }),
|
|
23
37
|
done: (pid: string, id: string) => http.post<TaskEntry>(`/projects/${pid}/tasks/${id}/done`),
|
|
24
38
|
drop: (pid: string, id: string) => http.post<TaskEntry>(`/projects/${pid}/tasks/${id}/drop`),
|
|
25
39
|
reopen: (pid: string, id: string) => http.post<TaskEntry>(`/projects/${pid}/tasks/${id}/reopen`),
|
|
40
|
+
summary: (pid: string) => http.get<TaskSummary>(`/projects/${pid}/tasks-summary`),
|
|
26
41
|
};
|
|
@@ -24,6 +24,8 @@ export * from "./api/deck";
|
|
|
24
24
|
export * from "./api/code";
|
|
25
25
|
export * from "./api/artifacts";
|
|
26
26
|
export * from "./api/skills";
|
|
27
|
+
export * from "./api/organization";
|
|
28
|
+
export * from "./api/projectFiles";
|
|
27
29
|
|
|
28
30
|
// Re-export the daemon types so older imports of "../lib/api" still work.
|
|
29
31
|
export type {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Kebab-case a free-text name into a slug. Mirrors the backend's slugifyName
|
|
2
|
+
// (core/stores/organization.js) so a name typed in the web produces the same
|
|
3
|
+
// slug the daemon would derive.
|
|
4
|
+
export function slugify(name: string): string {
|
|
5
|
+
return String(name || "")
|
|
6
|
+
.trim()
|
|
7
|
+
.toLowerCase()
|
|
8
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
9
|
+
.replace(/^-+|-+$/g, "")
|
|
10
|
+
.replace(/-{2,}/g, "-");
|
|
11
|
+
}
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
Bot, Heart, Zap, Puzzle, FolderKanban, Settings,
|
|
5
5
|
MessagesSquare, Send, KeyRound,
|
|
6
6
|
LayoutDashboard, Boxes, Cpu, ScrollText, History, Brain, FileCode2,
|
|
7
|
+
Building2, FileText, FolderTree,
|
|
7
8
|
} from "lucide-react";
|
|
8
9
|
import { useNavCollapse, type TabSection } from "../components/common/TabNav";
|
|
9
10
|
import { TabLayout } from "../components/common/TabLayout";
|
|
@@ -30,10 +31,14 @@ import { TelegramTab } from "./project/TelegramTab";
|
|
|
30
31
|
import { MemoriesTab } from "./project/MemoriesTab";
|
|
31
32
|
import { ArtifactsTab } from "./project/ArtifactsTab";
|
|
32
33
|
import { AgentDetailScreen } from "./project/AgentDetailScreen";
|
|
34
|
+
import { StructureTab } from "./project/StructureTab";
|
|
35
|
+
import { DocsTab } from "./project/DocsTab";
|
|
36
|
+
import { FilesTab } from "./project/FilesTab";
|
|
33
37
|
|
|
34
38
|
type NavKey =
|
|
35
39
|
| "" | "chat" | "config" | "telegram"
|
|
36
|
-
| "agents" | "routines" | "tasks" | "mcps" | "vars" | "logs" | "memories" | "artifacts"
|
|
40
|
+
| "agents" | "routines" | "tasks" | "mcps" | "vars" | "logs" | "memories" | "artifacts"
|
|
41
|
+
| "structure" | "docs" | "files";
|
|
37
42
|
|
|
38
43
|
export function ProjectScreen() {
|
|
39
44
|
const navigate = useNavigate();
|
|
@@ -79,6 +84,9 @@ export function ProjectScreen() {
|
|
|
79
84
|
},
|
|
80
85
|
];
|
|
81
86
|
}
|
|
87
|
+
// Structure (org roles/areas) is only meaningful for company/enterprise
|
|
88
|
+
// projects — gate it on the project kind.
|
|
89
|
+
const isCompany = project?.kind === "company";
|
|
82
90
|
return [
|
|
83
91
|
{
|
|
84
92
|
title: t("project.sections.workspace"),
|
|
@@ -87,9 +95,17 @@ export function ProjectScreen() {
|
|
|
87
95
|
{ key: "telegram", label: t("project.nav.telegram"), icon: Send },
|
|
88
96
|
{ key: "chat", label: t("project.nav.chat"), icon: MessagesSquare },
|
|
89
97
|
{ key: "agents", label: t("project.nav.agents"), icon: Bot },
|
|
98
|
+
...(isCompany ? [{ key: "structure", label: t("project.nav.structure"), icon: Building2 }] : []),
|
|
90
99
|
{ key: "memories", label: t("project.nav.memories"), icon: Brain },
|
|
91
100
|
],
|
|
92
101
|
},
|
|
102
|
+
{
|
|
103
|
+
title: t("project.sections.content"),
|
|
104
|
+
items: [
|
|
105
|
+
{ key: "docs", label: t("project.nav.docs"), icon: FileText },
|
|
106
|
+
{ key: "files", label: t("project.nav.files"), icon: FolderTree },
|
|
107
|
+
],
|
|
108
|
+
},
|
|
93
109
|
{
|
|
94
110
|
title: t("project.sections.automation"),
|
|
95
111
|
items: [
|
|
@@ -108,7 +124,7 @@ export function ProjectScreen() {
|
|
|
108
124
|
],
|
|
109
125
|
},
|
|
110
126
|
];
|
|
111
|
-
}, [isBase]);
|
|
127
|
+
}, [isBase, project?.kind]);
|
|
112
128
|
|
|
113
129
|
// First path segment after /p/:pid — so deep routes like agents/:slug still
|
|
114
130
|
// highlight the "agents" nav item.
|
|
@@ -141,7 +157,7 @@ export function ProjectScreen() {
|
|
|
141
157
|
onChange={onTabChange}
|
|
142
158
|
collapsed={collapsed}
|
|
143
159
|
onToggleCollapse={toggle}
|
|
144
|
-
contentClassName="w-full space-y-6
|
|
160
|
+
contentClassName="w-full space-y-6 py-6 pt-3 pr-6 pl-1"
|
|
145
161
|
testId={`project-tab-${active || "overview"}`}
|
|
146
162
|
>
|
|
147
163
|
<Routes>
|
|
@@ -155,6 +171,9 @@ export function ProjectScreen() {
|
|
|
155
171
|
<Route path="telegram" element={<TelegramTab pid={pid} />} />
|
|
156
172
|
<Route path="agents" element={<AgentsTab pid={pid} />} />
|
|
157
173
|
<Route path="agents/:slug" element={<AgentDetailScreen pid={pid} />} />
|
|
174
|
+
<Route path="structure" element={<StructureTab pid={pid} />} />
|
|
175
|
+
<Route path="docs" element={<DocsTab pid={pid} />} />
|
|
176
|
+
<Route path="files" element={<FilesTab pid={pid} />} />
|
|
158
177
|
<Route path="memories" element={<MemoriesTab pid={pid} />} />
|
|
159
178
|
<Route path="routines" element={<RoutinesTab pid={pid} />} />
|
|
160
179
|
<Route path="tasks" element={isBase ? <GlobalTasksTab /> : <TasksTab pid={pid} />} />
|
|
@@ -101,7 +101,7 @@ export function SettingsScreen() {
|
|
|
101
101
|
onChange={(k) => navigate(k === "identity" ? "/settings" : `/settings/${pathFromTab(k as TabKey)}`)}
|
|
102
102
|
collapsed={collapsed}
|
|
103
103
|
onToggleCollapse={toggle}
|
|
104
|
-
contentClassName={`w-full ${WIDE_TABS.has(active) ? "" : "mx-auto max-w-3xl"} space-y-6
|
|
104
|
+
contentClassName={`w-full ${WIDE_TABS.has(active) ? "" : "mx-auto max-w-3xl"} space-y-6 py-6 pt-3 pr-6 pl-4`}
|
|
105
105
|
testId={`settings-tab-${active}`}
|
|
106
106
|
>
|
|
107
107
|
<Panel />
|
|
@@ -12,8 +12,11 @@ import { Badge, Button, Field, Input, Loading, Switch, Textarea } from "../../co
|
|
|
12
12
|
import { Tip } from "../../components/ui/tip";
|
|
13
13
|
import { UiSelect } from "../../components/UiSelect";
|
|
14
14
|
import { useToast } from "../../components/Toast";
|
|
15
|
+
import { ConfirmDialog } from "../../components/common/ConfirmDialog";
|
|
16
|
+
import { EmojiInput, AutonomyPicker, AreaRoleFields } from "../../components/agents/AgentFormFields";
|
|
15
17
|
import { cn } from "../../lib/cn";
|
|
16
18
|
import { t } from "../../i18n";
|
|
19
|
+
import type { AgentAutonomy } from "../../types/daemon";
|
|
17
20
|
import { AgentBrainGraph, type BrainNode } from "./AgentBrainGraph";
|
|
18
21
|
|
|
19
22
|
type TabKey = "overview" | "memories" | "records" | "sleep" | "brain" | "config";
|
|
@@ -28,7 +31,7 @@ function buildTabs(): { key: TabKey; label: string; icon: typeof Bot }[] {
|
|
|
28
31
|
];
|
|
29
32
|
}
|
|
30
33
|
|
|
31
|
-
function typeOptions() {
|
|
34
|
+
export function typeOptions() {
|
|
32
35
|
return [
|
|
33
36
|
{ value: "", label: t("agents_ui.type_none") },
|
|
34
37
|
{ value: "orchestrator", label: t("agents_ui.type_orchestrator"), description: t("agents_ui.type_orchestrator_desc") },
|
|
@@ -205,9 +208,11 @@ function AgentConfigForm({
|
|
|
205
208
|
onDeleted: () => void;
|
|
206
209
|
}) {
|
|
207
210
|
const toast = useToast();
|
|
211
|
+
const [emoji, setEmoji] = useState(agent.emoji || "");
|
|
208
212
|
const [type, setType] = useState(agent.type || "");
|
|
209
213
|
const [area, setArea] = useState(agent.area || "");
|
|
210
214
|
const [role, setRole] = useState(agent.role || "");
|
|
215
|
+
const [autonomy, setAutonomy] = useState<AgentAutonomy | "">(agent.autonomy || "");
|
|
211
216
|
const [model, setModel] = useState(agent.model || "");
|
|
212
217
|
const [parent, setParent] = useState(agent.parent || "");
|
|
213
218
|
const [isMaster, setIsMaster] = useState(!!agent.is_master);
|
|
@@ -216,14 +221,17 @@ function AgentConfigForm({
|
|
|
216
221
|
const [description, setDescription] = useState(agent.description || "");
|
|
217
222
|
const [system, setSystem] = useState(agent.system || "");
|
|
218
223
|
const [busy, setBusy] = useState(false);
|
|
224
|
+
const [confirmDelete, setConfirmDelete] = useState(false);
|
|
219
225
|
|
|
220
226
|
const save = async () => {
|
|
221
227
|
setBusy(true);
|
|
222
228
|
try {
|
|
223
229
|
await Agents.update(pid, agent.slug, {
|
|
230
|
+
emoji: emoji || null,
|
|
224
231
|
type: type || null,
|
|
225
232
|
area: area || null,
|
|
226
233
|
role: role || null,
|
|
234
|
+
autonomy: autonomy || null,
|
|
227
235
|
model: model || null,
|
|
228
236
|
parent: parent || null,
|
|
229
237
|
is_master: isMaster || type === "orchestrator",
|
|
@@ -239,20 +247,23 @@ function AgentConfigForm({
|
|
|
239
247
|
};
|
|
240
248
|
|
|
241
249
|
const del = async () => {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
250
|
+
await Agents.remove(pid, agent.slug);
|
|
251
|
+
toast.success(t("project.agent_detail.delete_success"));
|
|
252
|
+
onDeleted();
|
|
245
253
|
};
|
|
246
254
|
|
|
247
255
|
return (
|
|
248
256
|
<Section title={t("project.agent_detail.config_title")} description={`.apc/agents/${agent.slug}.md — ${t("agents_ui.config_def_desc")}`}>
|
|
249
257
|
<div className="space-y-3">
|
|
250
|
-
<div className="grid grid-cols-
|
|
258
|
+
<div className="grid grid-cols-[80px_1fr] gap-3">
|
|
259
|
+
<Field label={t("agents_form.emoji")}><EmojiInput value={emoji} onChange={setEmoji} /></Field>
|
|
251
260
|
<Field label={t("project.agent_detail.type_label")}><UiSelect value={type} onChange={setType} options={typeOptions()} /></Field>
|
|
252
|
-
<Field label={t("project.agent_detail.area_label")} hint={t("project.agent_detail.area_hint")}><Input value={area} onChange={(e) => setArea(e.target.value)} placeholder={t("project.agent_detail.area_ph")} /></Field>
|
|
253
261
|
</div>
|
|
262
|
+
<AreaRoleFields pid={pid} area={area} role={role} onArea={setArea} onRole={setRole} />
|
|
263
|
+
<Field label={t("agents_form.autonomy")} hint={t("agents_form.autonomy_hint")}>
|
|
264
|
+
<AutonomyPicker value={autonomy} onChange={setAutonomy} />
|
|
265
|
+
</Field>
|
|
254
266
|
<div className="grid grid-cols-2 gap-3">
|
|
255
|
-
<Field label={t("project.agent_detail.role_label")}><Input value={role} onChange={(e) => setRole(e.target.value)} placeholder="Operations Lead" /></Field>
|
|
256
267
|
<Field label={t("project.agent_detail.parent_label")}>
|
|
257
268
|
<UiSelect
|
|
258
269
|
value={parent}
|
|
@@ -261,10 +272,10 @@ function AgentConfigForm({
|
|
|
261
272
|
options={[{ value: "", label: t("project.agent_detail.none_parent") }, ...agents.filter((x) => x.slug !== agent.slug).map((x) => ({ value: x.slug, label: x.slug }))]}
|
|
262
273
|
/>
|
|
263
274
|
</Field>
|
|
275
|
+
<Field label={t("project.agent_detail.model_label")} hint={t("project.agent_detail.model_hint")}>
|
|
276
|
+
<Input value={model} onChange={(e) => setModel(e.target.value)} placeholder={t("project.agent_detail.model_ph")} />
|
|
277
|
+
</Field>
|
|
264
278
|
</div>
|
|
265
|
-
<Field label={t("project.agent_detail.model_label")} hint={t("project.agent_detail.model_hint")}>
|
|
266
|
-
<Input value={model} onChange={(e) => setModel(e.target.value)} placeholder={t("project.agent_detail.model_ph")} />
|
|
267
|
-
</Field>
|
|
268
279
|
<Field label={t("project.agent_detail.skills_label")}><Input value={skills} onChange={(e) => setSkills(e.target.value)} placeholder="skill-a, skill-b" /></Field>
|
|
269
280
|
<ToolsPicker value={tools} onChange={setTools} />
|
|
270
281
|
<Field label={t("project.agent_detail.bio_label")}><Textarea rows={2} value={description} onChange={(e) => setDescription(e.target.value)} /></Field>
|
|
@@ -274,10 +285,19 @@ function AgentConfigForm({
|
|
|
274
285
|
<Switch checked={isMaster} onChange={setIsMaster} label={t("project.agent_detail.master_label")} />
|
|
275
286
|
|
|
276
287
|
<div className="flex items-center justify-between border-t border-border pt-3">
|
|
277
|
-
<Button variant="destructive" onClick={
|
|
288
|
+
<Button variant="destructive" onClick={() => setConfirmDelete(true)}><Trash2 size={13} /> {t("project.agent_detail.delete_btn")}</Button>
|
|
278
289
|
<Button variant="primary" loading={busy} onClick={save}><Save size={13} /> {t("project.agent_detail.save_btn")}</Button>
|
|
279
290
|
</div>
|
|
280
291
|
</div>
|
|
292
|
+
|
|
293
|
+
<ConfirmDialog
|
|
294
|
+
open={confirmDelete}
|
|
295
|
+
onClose={() => setConfirmDelete(false)}
|
|
296
|
+
onConfirm={del}
|
|
297
|
+
title={t("project.agent_detail.delete_btn")}
|
|
298
|
+
description={t("project.agent_detail.delete_confirm", { slug: agent.slug })}
|
|
299
|
+
confirmLabel={t("project.agent_detail.delete_btn")}
|
|
300
|
+
/>
|
|
281
301
|
</Section>
|
|
282
302
|
);
|
|
283
303
|
}
|
|
@@ -8,8 +8,11 @@ import { Section } from "../../components/Section";
|
|
|
8
8
|
import { Badge, Button, Dialog, Empty, Field, Input, Loading, Switch, Textarea } from "../../components/ui";
|
|
9
9
|
import { UiSelect } from "../../components/UiSelect";
|
|
10
10
|
import { useToast } from "../../components/Toast";
|
|
11
|
+
import { EmojiInput, AutonomyPicker, AreaRoleFields } from "../../components/agents/AgentFormFields";
|
|
11
12
|
import { cn } from "../../lib/cn";
|
|
12
13
|
import { t } from "../../i18n";
|
|
14
|
+
import type { AgentAutonomy } from "../../types/daemon";
|
|
15
|
+
import { typeOptions } from "./AgentDetailScreen";
|
|
13
16
|
|
|
14
17
|
const LANGS = ["", "es", "en", "pt", "fr", "it", "de"];
|
|
15
18
|
const csv = (s: string) => s.split(",").map((x) => x.trim()).filter(Boolean);
|
|
@@ -224,7 +227,7 @@ function AgentCard({
|
|
|
224
227
|
>
|
|
225
228
|
<div className="flex items-center gap-2">
|
|
226
229
|
<div className={cn("flex size-8 shrink-0 items-center justify-center rounded-lg bg-gradient-to-br", gradient)}>
|
|
227
|
-
<Icon className="size-4 text-white" />
|
|
230
|
+
{agent.emoji ? <span className="text-base leading-none">{agent.emoji}</span> : <Icon className="size-4 text-white" />}
|
|
228
231
|
</div>
|
|
229
232
|
<span className="truncate text-sm font-semibold">{agent.slug}</span>
|
|
230
233
|
</div>
|
|
@@ -250,7 +253,7 @@ function ListView({ agents, onOpen, onChat }: { agents: AgentEntry[]; onOpen: (s
|
|
|
250
253
|
return (
|
|
251
254
|
<div key={a.slug} data-testid={`agent-card-${a.slug}`} className="flex cursor-pointer items-center gap-4 rounded-xl border border-border bg-muted/30 p-3 hover:border-muted-fg/50" onClick={() => onOpen(a.slug)}>
|
|
252
255
|
<div className={cn("flex size-9 shrink-0 items-center justify-center rounded-xl bg-gradient-to-br", gradient)}>
|
|
253
|
-
<Icon className="size-4 text-white" />
|
|
256
|
+
{a.emoji ? <span className="text-lg leading-none">{a.emoji}</span> : <Icon className="size-4 text-white" />}
|
|
254
257
|
</div>
|
|
255
258
|
<div className="min-w-0 flex-1">
|
|
256
259
|
<div className="flex flex-wrap items-center gap-2">
|
|
@@ -282,7 +285,11 @@ function CreateAgentDialog({
|
|
|
282
285
|
}: { open: boolean; onClose: () => void; onCreated: () => void; pid: string; agents: AgentEntry[] }) {
|
|
283
286
|
const toast = useToast();
|
|
284
287
|
const [slug, setSlug] = useState("");
|
|
288
|
+
const [emoji, setEmoji] = useState("");
|
|
289
|
+
const [type, setType] = useState("");
|
|
285
290
|
const [role, setRole] = useState("");
|
|
291
|
+
const [area, setArea] = useState("");
|
|
292
|
+
const [autonomy, setAutonomy] = useState<AgentAutonomy | "">("");
|
|
286
293
|
const [model, setModel] = useState("");
|
|
287
294
|
const [language, setLanguage] = useState("");
|
|
288
295
|
const [description, setDescription] = useState("");
|
|
@@ -293,8 +300,9 @@ function CreateAgentDialog({
|
|
|
293
300
|
const [busy, setBusy] = useState(false);
|
|
294
301
|
|
|
295
302
|
const reset = () => {
|
|
296
|
-
setSlug(""); setRole("");
|
|
297
|
-
|
|
303
|
+
setSlug(""); setEmoji(""); setType(""); setRole(""); setArea(""); setAutonomy("");
|
|
304
|
+
setModel(""); setLanguage(""); setDescription(""); setSkills(""); setTools("");
|
|
305
|
+
setIsMaster(false); setParent("");
|
|
298
306
|
};
|
|
299
307
|
|
|
300
308
|
const submit = async () => {
|
|
@@ -303,13 +311,17 @@ function CreateAgentDialog({
|
|
|
303
311
|
try {
|
|
304
312
|
await Agents.create(pid, {
|
|
305
313
|
slug,
|
|
314
|
+
emoji: emoji || undefined,
|
|
315
|
+
type: type || undefined,
|
|
306
316
|
role: role || undefined,
|
|
317
|
+
area: area || undefined,
|
|
318
|
+
autonomy: autonomy || undefined,
|
|
307
319
|
model: model || undefined,
|
|
308
320
|
language: language || undefined,
|
|
309
321
|
description: description || undefined,
|
|
310
322
|
skills: csv(skills),
|
|
311
323
|
tools: csv(tools),
|
|
312
|
-
is_master: isMaster,
|
|
324
|
+
is_master: isMaster || type === "orchestrator",
|
|
313
325
|
parent: parent || undefined,
|
|
314
326
|
});
|
|
315
327
|
toast.success(t("project.agents.create_success", { slug }));
|
|
@@ -334,10 +346,15 @@ function CreateAgentDialog({
|
|
|
334
346
|
}
|
|
335
347
|
>
|
|
336
348
|
<div className="space-y-3">
|
|
337
|
-
<div className="grid grid-cols-
|
|
349
|
+
<div className="grid grid-cols-[80px_1fr_1fr] gap-3">
|
|
350
|
+
<Field label={t("agents_form.emoji")}><EmojiInput value={emoji} onChange={setEmoji} /></Field>
|
|
338
351
|
<Field label={t("project.agents.slug_label")}><Input autoFocus data-testid="agent-slug" value={slug} onChange={(e) => setSlug(e.target.value)} placeholder={t("project.agents.slug_ph")} /></Field>
|
|
339
|
-
<Field label={t("project.
|
|
352
|
+
<Field label={t("project.agent_detail.type_label")}><UiSelect value={type} onChange={setType} options={typeOptions()} /></Field>
|
|
340
353
|
</div>
|
|
354
|
+
<AreaRoleFields pid={pid} area={area} role={role} onArea={setArea} onRole={setRole} />
|
|
355
|
+
<Field label={t("agents_form.autonomy")} hint={t("agents_form.autonomy_hint")}>
|
|
356
|
+
<AutonomyPicker value={autonomy} onChange={setAutonomy} />
|
|
357
|
+
</Field>
|
|
341
358
|
<div className="grid grid-cols-2 gap-3">
|
|
342
359
|
<Field label={t("project.agents.model_label")} hint={t("project.agents.model_hint")}><Input value={model} onChange={(e) => setModel(e.target.value)} /></Field>
|
|
343
360
|
<Field label={t("project.agents.lang_label")}><UiSelect value={language} onChange={setLanguage} options={LANGS.map((l) => ({ value: l, label: l || "—" }))} /></Field>
|