@agentprojectcontext/apx 1.57.0 → 1.59.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.
Files changed (60) hide show
  1. package/package.json +1 -1
  2. package/src/core/agent/skills/index.js +9 -0
  3. package/src/core/agent/skills/inspector.js +7 -2
  4. package/src/core/agent/skills/policy.js +128 -0
  5. package/src/core/agent/super-agent.js +8 -1
  6. package/src/core/agent/tools/handlers/list-skills.js +6 -3
  7. package/src/core/agent/tools/handlers/load-skill.js +9 -2
  8. package/src/core/apc/paths.js +7 -0
  9. package/src/core/stores/organization.js +152 -0
  10. package/src/core/stores/project-files.js +199 -0
  11. package/src/core/stores/tasks.js +36 -3
  12. package/src/host/daemon/api/agents.js +22 -2
  13. package/src/host/daemon/api/files-project.js +99 -0
  14. package/src/host/daemon/api/organization.js +88 -0
  15. package/src/host/daemon/api/shared.js +7 -0
  16. package/src/host/daemon/api/skills.js +301 -17
  17. package/src/host/daemon/api/tasks.js +14 -0
  18. package/src/host/daemon/api.js +4 -0
  19. package/src/interfaces/cli/commands/org.js +77 -0
  20. package/src/interfaces/cli/commands/skills.js +3 -2
  21. package/src/interfaces/cli/index.js +48 -0
  22. package/src/interfaces/web/dist/assets/index-CnQb4N6C.js +731 -0
  23. package/src/interfaces/web/dist/assets/index-CnQb4N6C.js.map +1 -0
  24. package/src/interfaces/web/dist/assets/index-Dv3X-zpx.css +1 -0
  25. package/src/interfaces/web/dist/index.html +2 -2
  26. package/src/interfaces/web/src/components/agents/AgentFormFields.tsx +123 -0
  27. package/src/interfaces/web/src/components/common/ConfirmDialog.tsx +51 -0
  28. package/src/interfaces/web/src/components/files/FileBrowser.tsx +138 -0
  29. package/src/interfaces/web/src/components/files/FileTree.tsx +133 -0
  30. package/src/interfaces/web/src/components/files/FileViewer.tsx +167 -0
  31. package/src/interfaces/web/src/components/files/MarkdownEditor.tsx +48 -0
  32. package/src/interfaces/web/src/components/files/MarkdownPreview.tsx +146 -0
  33. package/src/interfaces/web/src/components/files/NewFileDialog.tsx +66 -0
  34. package/src/interfaces/web/src/components/settings/SkillsManager.tsx +465 -0
  35. package/src/interfaces/web/src/components/settings/SkillsSettings.tsx +49 -0
  36. package/src/interfaces/web/src/components/structure/StructureDialogs.tsx +172 -0
  37. package/src/interfaces/web/src/components/tasks/TaskDetailPanel.tsx +142 -0
  38. package/src/interfaces/web/src/components/tasks/taskStatus.tsx +57 -0
  39. package/src/interfaces/web/src/i18n/en.ts +171 -0
  40. package/src/interfaces/web/src/i18n/es.ts +171 -0
  41. package/src/interfaces/web/src/lib/api/organization.ts +18 -0
  42. package/src/interfaces/web/src/lib/api/projectFiles.ts +19 -0
  43. package/src/interfaces/web/src/lib/api/skills.ts +79 -8
  44. package/src/interfaces/web/src/lib/api/tasks.ts +16 -1
  45. package/src/interfaces/web/src/lib/api.ts +2 -0
  46. package/src/interfaces/web/src/lib/slug.ts +11 -0
  47. package/src/interfaces/web/src/screens/ProjectScreen.tsx +25 -2
  48. package/src/interfaces/web/src/screens/SettingsScreen.tsx +3 -3
  49. package/src/interfaces/web/src/screens/project/AgentDetailScreen.tsx +31 -11
  50. package/src/interfaces/web/src/screens/project/AgentsTab.tsx +24 -7
  51. package/src/interfaces/web/src/screens/project/DocsTab.tsx +13 -0
  52. package/src/interfaces/web/src/screens/project/FilesTab.tsx +12 -0
  53. package/src/interfaces/web/src/screens/project/Overview.tsx +122 -10
  54. package/src/interfaces/web/src/screens/project/SkillsTab.tsx +13 -0
  55. package/src/interfaces/web/src/screens/project/StructureTab.tsx +147 -0
  56. package/src/interfaces/web/src/screens/project/TasksTab.tsx +101 -62
  57. package/src/interfaces/web/src/types/daemon.ts +63 -0
  58. package/src/interfaces/web/dist/assets/index-CEI8DfVg.css +0 -1
  59. package/src/interfaces/web/dist/assets/index-DJ-ocXOR.js +0 -651
  60. package/src/interfaces/web/dist/assets/index-DJ-ocXOR.js.map +0 -1
@@ -0,0 +1,172 @@
1
+ import { useEffect, useState } from "react";
2
+ import { Dialog, Button, Field, Input, Textarea } from "../ui";
3
+ import { UiSelect } from "../UiSelect";
4
+ import { useToast } from "../Toast";
5
+ import { Org } from "../../lib/api/organization";
6
+ import { slugify } from "../../lib/slug";
7
+ import { t } from "../../i18n";
8
+ import type { OrgArea, OrgRole } from "../../types/daemon";
9
+
10
+ // Create or edit an area. `editing` present → edit mode (slug is immutable).
11
+ export function AreaDialog({
12
+ open, onClose, pid, editing, onSaved,
13
+ }: {
14
+ open: boolean;
15
+ onClose: () => void;
16
+ pid: string;
17
+ editing?: OrgArea | null;
18
+ onSaved: () => void;
19
+ }) {
20
+ const toast = useToast();
21
+ const [name, setName] = useState("");
22
+ const [slug, setSlug] = useState("");
23
+ const [goal, setGoal] = useState("");
24
+ const [busy, setBusy] = useState(false);
25
+
26
+ useEffect(() => {
27
+ if (!open) return;
28
+ setName(editing?.name ?? "");
29
+ setSlug(editing?.slug ?? "");
30
+ setGoal(editing?.goal ?? "");
31
+ }, [open, editing]);
32
+
33
+ const save = async () => {
34
+ if (!name.trim()) return;
35
+ setBusy(true);
36
+ try {
37
+ if (editing) await Org.updateArea(pid, editing.slug, { name, goal });
38
+ else await Org.createArea(pid, { name, slug: slug || slugify(name), goal });
39
+ toast.success(t("common.saved"));
40
+ onSaved();
41
+ onClose();
42
+ } catch (e) {
43
+ toast.error(e instanceof Error ? e.message : String(e));
44
+ } finally {
45
+ setBusy(false);
46
+ }
47
+ };
48
+
49
+ return (
50
+ <Dialog
51
+ open={open}
52
+ onClose={onClose}
53
+ title={editing ? t("structure.edit_area") : t("structure.new_area")}
54
+ footer={
55
+ <>
56
+ <Button variant="ghost" onClick={onClose}>{t("common.cancel")}</Button>
57
+ <Button variant="primary" data-testid="area-create" onClick={() => void save()} loading={busy} disabled={!name.trim()}>
58
+ {editing ? t("common.save") : t("structure.create_area")}
59
+ </Button>
60
+ </>
61
+ }
62
+ >
63
+ <div className="space-y-3">
64
+ <Field label={t("structure.name")}>
65
+ <Input
66
+ autoFocus
67
+ data-testid="area-name"
68
+ value={name}
69
+ onChange={(e) => { setName(e.target.value); if (!editing) setSlug(slugify(e.target.value)); }}
70
+ placeholder="Engineering"
71
+ />
72
+ </Field>
73
+ {!editing && (
74
+ <Field label={t("structure.slug")}>
75
+ <Input value={slug} onChange={(e) => setSlug(slugify(e.target.value))} className="font-mono" placeholder="engineering" />
76
+ </Field>
77
+ )}
78
+ <Field label={t("structure.goal")} hint={t("structure.goal_hint")}>
79
+ <Textarea value={goal} onChange={(e) => setGoal(e.target.value)} rows={2} />
80
+ </Field>
81
+ </div>
82
+ </Dialog>
83
+ );
84
+ }
85
+
86
+ // Create or edit a role. `presetArea` pre-selects an area (quick-create from an
87
+ // area card).
88
+ export function RoleDialog({
89
+ open, onClose, pid, areas, editing, presetArea, onSaved,
90
+ }: {
91
+ open: boolean;
92
+ onClose: () => void;
93
+ pid: string;
94
+ areas: OrgArea[];
95
+ editing?: OrgRole | null;
96
+ presetArea?: string | null;
97
+ onSaved: () => void;
98
+ }) {
99
+ const toast = useToast();
100
+ const [name, setName] = useState("");
101
+ const [slug, setSlug] = useState("");
102
+ const [area, setArea] = useState("");
103
+ const [description, setDescription] = useState("");
104
+ const [busy, setBusy] = useState(false);
105
+
106
+ useEffect(() => {
107
+ if (!open) return;
108
+ setName(editing?.name ?? "");
109
+ setSlug(editing?.slug ?? "");
110
+ setArea(editing?.area ?? presetArea ?? "");
111
+ setDescription(editing?.description ?? "");
112
+ }, [open, editing, presetArea]);
113
+
114
+ const save = async () => {
115
+ if (!name.trim()) return;
116
+ setBusy(true);
117
+ try {
118
+ if (editing) await Org.updateRole(pid, editing.slug, { name, area: area || null, description });
119
+ else await Org.createRole(pid, { name, slug: slug || slugify(name), area: area || null, description });
120
+ toast.success(t("common.saved"));
121
+ onSaved();
122
+ onClose();
123
+ } catch (e) {
124
+ toast.error(e instanceof Error ? e.message : String(e));
125
+ } finally {
126
+ setBusy(false);
127
+ }
128
+ };
129
+
130
+ const areaOptions = [
131
+ { value: "", label: t("structure.no_area") },
132
+ ...areas.map((a) => ({ value: a.slug, label: a.name })),
133
+ ];
134
+
135
+ return (
136
+ <Dialog
137
+ open={open}
138
+ onClose={onClose}
139
+ title={editing ? t("structure.edit_role") : t("structure.new_role")}
140
+ footer={
141
+ <>
142
+ <Button variant="ghost" onClick={onClose}>{t("common.cancel")}</Button>
143
+ <Button variant="primary" onClick={() => void save()} loading={busy} disabled={!name.trim()}>
144
+ {editing ? t("common.save") : t("structure.create_role")}
145
+ </Button>
146
+ </>
147
+ }
148
+ >
149
+ <div className="space-y-3">
150
+ <Field label={t("structure.name")}>
151
+ <Input
152
+ autoFocus
153
+ value={name}
154
+ onChange={(e) => { setName(e.target.value); if (!editing) setSlug(slugify(e.target.value)); }}
155
+ placeholder="Tech Lead"
156
+ />
157
+ </Field>
158
+ {!editing && (
159
+ <Field label={t("structure.slug")}>
160
+ <Input value={slug} onChange={(e) => setSlug(slugify(e.target.value))} className="font-mono" placeholder="tech-lead" />
161
+ </Field>
162
+ )}
163
+ <Field label={t("structure.area")}>
164
+ <UiSelect value={area} onChange={setArea} options={areaOptions} placeholder={t("structure.no_area")} />
165
+ </Field>
166
+ <Field label={t("structure.description")}>
167
+ <Textarea value={description} onChange={(e) => setDescription(e.target.value)} rows={2} />
168
+ </Field>
169
+ </div>
170
+ </Dialog>
171
+ );
172
+ }
@@ -0,0 +1,142 @@
1
+ import { useState, useEffect } from "react";
2
+ import useSWR from "swr";
3
+ import { X, Check, Trash2, RotateCcw, ExternalLink, Save } from "lucide-react";
4
+ import { useNavigate } from "react-router-dom";
5
+ import { Tasks } from "../../lib/api";
6
+ import { Button, Spinner, Textarea } from "../ui";
7
+ import { UiSelect } from "../UiSelect";
8
+ import { useToast } from "../Toast";
9
+ import { StatusBadge, effectiveStatus, TASK_STATUS_ORDER, statusLabel } from "./taskStatus";
10
+ import { t } from "../../i18n";
11
+ import type { TaskStatus } from "../../types/daemon";
12
+
13
+ function Row({ label, children }: { label: string; children: React.ReactNode }) {
14
+ return (
15
+ <div className="flex items-baseline justify-between gap-3 text-xs">
16
+ <span className="text-muted-foreground">{label}</span>
17
+ <span className="text-right font-mono text-foreground/90">{children}</span>
18
+ </div>
19
+ );
20
+ }
21
+
22
+ // Right-hand task inspector: prompt/body, workflow status, who created it, the
23
+ // linked thread, timestamps, and lifecycle actions. Mirrors Panda's detail
24
+ // panel but wired to APX's task store.
25
+ export function TaskDetailPanel({
26
+ pid, taskId, onClose, onChanged,
27
+ }: {
28
+ pid: string;
29
+ taskId: string;
30
+ onClose: () => void;
31
+ onChanged: () => void;
32
+ }) {
33
+ const toast = useToast();
34
+ const navigate = useNavigate();
35
+ const { data: task, isLoading, mutate } = useSWR(`/projects/${pid}/tasks/${taskId}`, () => Tasks.get(pid, taskId));
36
+ const [body, setBody] = useState("");
37
+ const [busy, setBusy] = useState(false);
38
+
39
+ useEffect(() => { setBody(task?.body ?? ""); }, [task?.id, task?.body]);
40
+
41
+ const refresh = () => { void mutate(); onChanged(); };
42
+ const act = async (fn: () => Promise<unknown>) => {
43
+ setBusy(true);
44
+ try { await fn(); refresh(); }
45
+ catch (e) { toast.error(e instanceof Error ? e.message : String(e)); }
46
+ finally { setBusy(false); }
47
+ };
48
+
49
+ if (isLoading) return <div className="flex w-80 items-center justify-center border-l border-border"><Spinner /></div>;
50
+ if (!task) return null;
51
+
52
+ const eff = effectiveStatus(task);
53
+ const isOpen = task.state === "open";
54
+ const bodyDirty = body !== (task.body ?? "");
55
+
56
+ return (
57
+ <div className="flex w-80 shrink-0 flex-col border-l border-border bg-card/40" data-testid="task-detail">
58
+ <div className="flex shrink-0 items-center justify-between border-b border-border px-4 py-2.5">
59
+ <span className="text-[10px] font-semibold uppercase tracking-wide text-muted-foreground">{t("tasks.detail_title")}</span>
60
+ <button type="button" onClick={onClose} aria-label={t("common.close")} className="text-muted-foreground hover:text-foreground">
61
+ <X className="size-4" />
62
+ </button>
63
+ </div>
64
+
65
+ <div className="min-h-0 flex-1 space-y-4 overflow-y-auto px-4 py-4">
66
+ <div>
67
+ <div className="mb-1 text-[10px] uppercase tracking-wide text-muted-foreground">{t("tasks.field_title")}</div>
68
+ <div className="text-sm font-semibold">{task.title}</div>
69
+ </div>
70
+
71
+ <div className="flex items-center gap-2">
72
+ <StatusBadge status={eff} />
73
+ <span className="font-mono text-[10px] text-muted-foreground">{task.id}</span>
74
+ </div>
75
+
76
+ {/* Prompt / body — editable */}
77
+ <div>
78
+ <div className="mb-1 flex items-center justify-between">
79
+ <span className="text-[10px] uppercase tracking-wide text-muted-foreground">{t("tasks.field_prompt")}</span>
80
+ {bodyDirty && (
81
+ <button type="button" onClick={() => act(async () => { await Tasks.patch(pid, task.id, { body }); toast.success(t("common.saved")); })} className="flex items-center gap-1 text-[10px] text-emerald-500 hover:text-emerald-400">
82
+ <Save className="size-3" />{t("files.save")}
83
+ </button>
84
+ )}
85
+ </div>
86
+ <Textarea rows={4} value={body} onChange={(e) => setBody(e.target.value)} placeholder={t("tasks.prompt_ph")} className="text-xs" />
87
+ </div>
88
+
89
+ {/* Workflow status control (open tasks) */}
90
+ {isOpen && (
91
+ <div>
92
+ <div className="mb-1 text-[10px] uppercase tracking-wide text-muted-foreground">{t("tasks.field_status")}</div>
93
+ <UiSelect
94
+ value={task.status ?? "pending"}
95
+ onChange={(v) => act(() => Tasks.status(pid, task.id, v as TaskStatus))}
96
+ options={TASK_STATUS_ORDER.map((s) => ({ value: s, label: statusLabel(s) }))}
97
+ />
98
+ </div>
99
+ )}
100
+
101
+ <div className="space-y-1.5 rounded-lg border border-border bg-background/40 p-2.5">
102
+ {task.agent && <Row label={t("tasks.field_agent")}>@{task.agent}</Row>}
103
+ {task.created_by && <Row label={t("tasks.field_creator")}>{task.created_by}</Row>}
104
+ {task.source && <Row label={t("tasks.field_source")}>{task.source}</Row>}
105
+ {task.due && <Row label={t("project.tasks.due")}>{task.due}</Row>}
106
+ <Row label={t("tasks.field_created")}>{new Date(task.created_at).toLocaleString()}</Row>
107
+ <Row label={t("tasks.field_updated")}>{new Date(task.updated_at).toLocaleString()}</Row>
108
+ {task.done_at && <Row label={t("tasks.field_done")}>{new Date(task.done_at).toLocaleString()}</Row>}
109
+ </div>
110
+
111
+ {/* Thread link */}
112
+ {task.thread && (
113
+ <button
114
+ type="button"
115
+ onClick={() => navigate(`/p/${pid}/chat?thread=${task.thread}`)}
116
+ className="flex w-full items-center justify-center gap-1.5 rounded-lg border border-sky-500/30 bg-sky-500/5 px-3 py-2 text-xs text-sky-500 hover:bg-sky-500/10"
117
+ >
118
+ <ExternalLink className="size-3.5" />{t("tasks.view_thread")}
119
+ </button>
120
+ )}
121
+ </div>
122
+
123
+ {/* Actions */}
124
+ <div className="flex shrink-0 gap-2 border-t border-border px-4 py-3">
125
+ {isOpen ? (
126
+ <>
127
+ <Button size="sm" variant="primary" className="flex-1" loading={busy} onClick={() => act(() => Tasks.done(pid, task.id))}>
128
+ <Check size={13} />{t("tasks.mark_done")}
129
+ </Button>
130
+ <Button size="sm" variant="destructive" loading={busy} onClick={() => act(() => Tasks.drop(pid, task.id))} aria-label={t("project.tasks.aria_drop")}>
131
+ <Trash2 size={13} />
132
+ </Button>
133
+ </>
134
+ ) : (
135
+ <Button size="sm" variant="secondary" className="flex-1" loading={busy} onClick={() => act(() => Tasks.reopen(pid, task.id))}>
136
+ <RotateCcw size={13} />{t("project.tasks.reopen")}
137
+ </Button>
138
+ )}
139
+ </div>
140
+ </div>
141
+ );
142
+ }
@@ -0,0 +1,57 @@
1
+ import { Loader2, CheckCircle2, XCircle, Clock, CircleDot, HelpCircle } from "lucide-react";
2
+ import { cn } from "../../lib/cn";
3
+ import { t } from "../../i18n";
4
+ import type { TaskEntry, TaskStatus } from "../../types/daemon";
5
+
6
+ type Meta = { labelKey: string; color: string; dot: string; Icon: typeof Clock; spin?: boolean };
7
+
8
+ // Single source of truth for how each workflow status looks. Reused by the
9
+ // task list, detail panel and the floor/overview.
10
+ const STATUS_META: Record<TaskStatus, Meta> = {
11
+ pending: { labelKey: "tasks.status_pending", color: "text-amber-500", dot: "bg-amber-400", Icon: CircleDot },
12
+ running: { labelKey: "tasks.status_running", color: "text-sky-500", dot: "bg-sky-400", Icon: Loader2, spin: true },
13
+ in_review: { labelKey: "tasks.status_in_review", color: "text-violet-500", dot: "bg-violet-400", Icon: Clock },
14
+ blocked: { labelKey: "tasks.status_blocked", color: "text-slate-400", dot: "bg-slate-400", Icon: HelpCircle },
15
+ };
16
+
17
+ export const TASK_STATUS_ORDER: TaskStatus[] = ["pending", "running", "in_review", "blocked"];
18
+
19
+ // Effective status for display: closed tasks render as done/dropped regardless
20
+ // of their last open sub-status.
21
+ export function effectiveStatus(task: TaskEntry): TaskStatus | "done" | "dropped" {
22
+ if (task.state === "done") return "done";
23
+ if (task.state === "dropped") return "dropped";
24
+ return task.status ?? "pending";
25
+ }
26
+
27
+ export function statusLabel(status: TaskStatus): string {
28
+ return t(STATUS_META[status].labelKey as never);
29
+ }
30
+
31
+ export function StatusIcon({ status, className }: { status: TaskStatus | "done" | "dropped"; className?: string }) {
32
+ if (status === "done") return <CheckCircle2 className={cn("size-4 text-emerald-500", className)} />;
33
+ if (status === "dropped") return <XCircle className={cn("size-4 text-muted-foreground", className)} />;
34
+ const m = STATUS_META[status];
35
+ return <m.Icon className={cn("size-4", m.color, m.spin && "animate-spin", className)} />;
36
+ }
37
+
38
+ export function StatusBadge({ status }: { status: TaskStatus | "done" | "dropped" }) {
39
+ const label =
40
+ status === "done" ? t("tasks.done_label")
41
+ : status === "dropped" ? t("tasks.dropped_label")
42
+ : statusLabel(status);
43
+ const color =
44
+ status === "done" ? "text-emerald-500 border-emerald-500/30"
45
+ : status === "dropped" ? "text-muted-foreground border-border"
46
+ : `${STATUS_META[status].color} border-current/30`;
47
+ return (
48
+ <span className={cn("inline-flex items-center gap-1 rounded-md border px-1.5 py-0.5 text-[10px] font-medium capitalize", color)}>
49
+ <StatusIcon status={status} className="size-3" />
50
+ {label}
51
+ </span>
52
+ );
53
+ }
54
+
55
+ export function StatusDot({ status }: { status: TaskStatus }) {
56
+ return <span className={cn("size-2 rounded-full", STATUS_META[status].dot)} />;
57
+ }
@@ -32,6 +32,10 @@ export const en = {
32
32
  hide: "Hide",
33
33
  copy: "Copy",
34
34
  run: "Run",
35
+ refresh: "Refresh",
36
+ view_all: "View all",
37
+ saved: "Saved.",
38
+ deleted: "Deleted.",
35
39
  pager_prev: "Previous",
36
40
  pager_next: "Next",
37
41
  pager_page: "Page {page} of {total}",
@@ -292,10 +296,14 @@ export const en = {
292
296
  vars: "Variables",
293
297
  logs: "Logs",
294
298
  memories: "Memories",
299
+ structure: "Structure",
300
+ docs: "Docs",
301
+ files: "Files",
295
302
  },
296
303
 
297
304
  sections: {
298
305
  workspace: "Workspace",
306
+ content: "Content",
299
307
  automation: "Automation",
300
308
  knowledge: "Conversations",
301
309
  config: "Config",
@@ -304,11 +312,18 @@ export const en = {
304
312
  overview: {
305
313
  tasks_open: "Open tasks",
306
314
  routines: "Routines",
315
+ routines_active: "Active routines",
307
316
  agents: "Agents",
308
317
  mcps: "MCPs",
309
318
  artifacts: "Artifacts",
310
319
  chat: "Chat (super-agent)",
311
320
  chat_value: "open",
321
+ roster: "Team",
322
+ no_agents: "No agents yet.",
323
+ orchestrators: "Orchestrators",
324
+ specialists: "Specialists",
325
+ recent_tasks: "Recent tasks",
326
+ no_activity: "No open tasks.",
312
327
  },
313
328
 
314
329
  artifacts: {
@@ -1301,6 +1316,95 @@ export const en = {
1301
1316
  role_assigned: "{name} → {role}",
1302
1317
  },
1303
1318
 
1319
+ agents_form: {
1320
+ emoji: "Emoji",
1321
+ area: "Area",
1322
+ role: "Role",
1323
+ no_role: "— no role —",
1324
+ autonomy: "Autonomy",
1325
+ autonomy_hint: "How much the agent can do without asking for confirmation.",
1326
+ auto_total: "Total",
1327
+ auto_automatico: "Auto",
1328
+ auto_permiso: "Permission",
1329
+ },
1330
+
1331
+ structure: {
1332
+ title: "Structure",
1333
+ subtitle: "Company areas and roles. Areas group agents; roles define their function.",
1334
+ info: "Areas are optional groupings. Roles define an agent's function and may belong to an area.",
1335
+ empty: "No areas or roles yet. Create the first one above.",
1336
+ new_area: "New area",
1337
+ new_role: "New role",
1338
+ edit_area: "Edit area",
1339
+ edit_role: "Edit role",
1340
+ create_area: "Create area",
1341
+ create_role: "Create role",
1342
+ name: "Name",
1343
+ slug: "Slug",
1344
+ goal: "Goal",
1345
+ goal_hint: "What this area exists for (optional).",
1346
+ area: "Area",
1347
+ description: "Description",
1348
+ no_area: "— no area —",
1349
+ roles: "Roles",
1350
+ add_role: "role",
1351
+ no_roles: "no roles",
1352
+ general_roles: "General roles",
1353
+ delete_area: "Delete area",
1354
+ delete_role: "Delete role",
1355
+ delete_area_desc: "Delete area \"{name}\"? Its roles are detached, not deleted.",
1356
+ delete_role_desc: "Delete role \"{name}\"?",
1357
+ },
1358
+
1359
+ files: {
1360
+ docs_label: "Docs",
1361
+ files_label: "Files",
1362
+ new_doc: "New document",
1363
+ new_doc_hint: "Folders allowed: cases/onboarding/spec.md",
1364
+ empty: "No files.",
1365
+ docs_empty: "No documentation yet. Create the first document.",
1366
+ truncated: "Listing truncated (too many files).",
1367
+ select_prompt: "Pick a file to view it.",
1368
+ save: "Save",
1369
+ saved: "Saved.",
1370
+ deleted: "Deleted.",
1371
+ created: "Document created.",
1372
+ edit: "Edit",
1373
+ preview: "Preview",
1374
+ discard: "Discard",
1375
+ no_preview: "No preview for this file.",
1376
+ too_large: "File too large to display.",
1377
+ path_label: "File path",
1378
+ path_example: "e.g. cases/onboarding/spec.md",
1379
+ create: "Create",
1380
+ },
1381
+
1382
+ tasks: {
1383
+ state_open: "open",
1384
+ state_done: "done",
1385
+ state_dropped: "dropped",
1386
+ status_pending: "pending",
1387
+ status_running: "running",
1388
+ status_in_review: "in review",
1389
+ status_blocked: "blocked",
1390
+ done_label: "done",
1391
+ dropped_label: "dropped",
1392
+ detail_title: "Task detail",
1393
+ field_title: "Title",
1394
+ field_prompt: "Prompt",
1395
+ field_status: "Status",
1396
+ field_agent: "Agent",
1397
+ field_creator: "Created by",
1398
+ field_source: "Source",
1399
+ field_created: "Created",
1400
+ field_updated: "Updated",
1401
+ field_done: "Completed",
1402
+ prompt_ph: "Task description / prompt…",
1403
+ toggle_prompt: "Prompt",
1404
+ view_thread: "View thread",
1405
+ mark_done: "Complete",
1406
+ },
1407
+
1304
1408
  agents_ui: {
1305
1409
  model_router_default: "model: router default",
1306
1410
  slug_kebab_hint: "kebab-case, e.g. reviewer, my-agent, content-writer",
@@ -1581,6 +1685,73 @@ export const en = {
1581
1685
  cfg_apx_storage_id: "APX storage id",
1582
1686
  },
1583
1687
 
1688
+ skills_page: {
1689
+ title: "Skills",
1690
+ desc: "Turn skills on or off per agent. Pick a scope: the super-agent (global) or a specific project.",
1691
+ list_title: "Installed skills",
1692
+ list_desc: "APX's private skills are always active and can't be changed.",
1693
+ scope_label: "Scope",
1694
+ scope_super_agent: "Super-agent (global)",
1695
+ scope_hint: "The super-agent uses the global scope. Each project can override skills independently.",
1696
+ count_label: "{n} skills · {on} on",
1697
+ empty: "No skills yet. Create one below or install via the CLI.",
1698
+ source_builtin: "APX",
1699
+ source_global: "Global",
1700
+ source_project: "Project",
1701
+ private_badge: "Private",
1702
+ private_hint: "Built-in APX skill — always active, can't be disabled or deleted.",
1703
+ overridden_badge: "Override",
1704
+ inherited_hint: "Inherited from global",
1705
+ reset_to_global: "Reset to global",
1706
+ on: "on",
1707
+ off: "off",
1708
+ toggle_failed: "Could not change state: {msg}",
1709
+ add_title: "Add a skill",
1710
+ add_desc: "Creates a user skill at ~/.apx/skills/<slug>/SKILL.md. Available across all scopes.",
1711
+ add_slug_label: "Slug",
1712
+ add_slug_ph: "my-skill",
1713
+ add_desc_label: "Description",
1714
+ add_desc_ph: "One line describing when to use it",
1715
+ add_body_label: "Body (Markdown)",
1716
+ add_body_ph: "# My skill\n\nInstructions for the agent…",
1717
+ add_btn: "Create skill",
1718
+ created_ok: "Skill \"{slug}\" created.",
1719
+ create_failed: "Could not create: {msg}",
1720
+ delete_btn: "Delete",
1721
+ delete_confirm: "Delete skill \"{slug}\"? This can't be undone.",
1722
+ deleted_ok: "Skill \"{slug}\" deleted.",
1723
+ delete_failed: "Could not delete: {msg}",
1724
+ inspector_section_title: "Skill Inspector (per-turn RAG)",
1725
+ inspector_section_desc: "Advanced: local RAG that injects only the skills a message needs.",
1726
+ scope_ph: "— choose scope —",
1727
+ select_a_skill: "Pick a skill from the list to see its content.",
1728
+ added_by: "Added by",
1729
+ activator: "Activator",
1730
+ by_apx: "APX (built-in)",
1731
+ by_you: "You",
1732
+ activator_value: "Semantic match (RAG)",
1733
+ tab_preview: "Preview",
1734
+ tab_source: "Source",
1735
+ add_menu: "Add",
1736
+ add_online: "Create with editor",
1737
+ add_online_hint: "Write slug + description + content",
1738
+ add_zip: "Upload .zip",
1739
+ add_zip_hint: "Import a packaged skill",
1740
+ add_repo: "From git repo",
1741
+ add_repo_hint: "Clone from a URL",
1742
+ create_dialog_title: "Create skill",
1743
+ repo_dialog_title: "Import from git repo",
1744
+ repo_url_label: "Repo URL",
1745
+ repo_url_ph: "https://github.com/user/my-skill.git",
1746
+ repo_url_hint: "The repo (or its subfolder) must contain a SKILL.md.",
1747
+ import_btn: "Import",
1748
+ imported_ok: "Skill \"{slug}\" imported.",
1749
+ import_failed: "Could not import: {msg}",
1750
+ cancel: "Cancel",
1751
+ manager_tab: "Skills",
1752
+ rag_tab: "Config (RAG)",
1753
+ },
1754
+
1584
1755
  shared_ui: {
1585
1756
  skill_inspector_title: "Skill Inspector ({embedder}) chose these skills for this turn",
1586
1757
  tools_count: "{n} tools",