@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.
Files changed (58) hide show
  1. package/package.json +1 -1
  2. package/src/core/apc/paths.js +7 -0
  3. package/src/core/stores/conversations.js +10 -0
  4. package/src/core/stores/messages.js +37 -6
  5. package/src/core/stores/organization.js +152 -0
  6. package/src/core/stores/project-files.js +199 -0
  7. package/src/core/stores/tasks.js +36 -3
  8. package/src/host/daemon/api/agents.js +22 -2
  9. package/src/host/daemon/api/conversations.js +20 -2
  10. package/src/host/daemon/api/files-project.js +99 -0
  11. package/src/host/daemon/api/organization.js +88 -0
  12. package/src/host/daemon/api/shared.js +7 -0
  13. package/src/host/daemon/api/tasks.js +14 -0
  14. package/src/host/daemon/api.js +4 -0
  15. package/src/interfaces/cli/commands/org.js +77 -0
  16. package/src/interfaces/cli/index.js +48 -0
  17. package/src/interfaces/web/dist/assets/index-Cl0WXtxF.css +1 -0
  18. package/src/interfaces/web/dist/assets/index-DPAuXATr.js +705 -0
  19. package/src/interfaces/web/dist/assets/index-DPAuXATr.js.map +1 -0
  20. package/src/interfaces/web/dist/index.html +2 -2
  21. package/src/interfaces/web/package-lock.json +6 -6
  22. package/src/interfaces/web/src/App.tsx +1 -1
  23. package/src/interfaces/web/src/components/agents/AgentFormFields.tsx +123 -0
  24. package/src/interfaces/web/src/components/chat/ChatList.tsx +86 -65
  25. package/src/interfaces/web/src/components/common/ConfirmDialog.tsx +51 -0
  26. package/src/interfaces/web/src/components/common/TabNav.tsx +1 -1
  27. package/src/interfaces/web/src/components/files/FileBrowser.tsx +138 -0
  28. package/src/interfaces/web/src/components/files/FileTree.tsx +133 -0
  29. package/src/interfaces/web/src/components/files/FileViewer.tsx +167 -0
  30. package/src/interfaces/web/src/components/files/MarkdownEditor.tsx +48 -0
  31. package/src/interfaces/web/src/components/files/MarkdownPreview.tsx +146 -0
  32. package/src/interfaces/web/src/components/files/NewFileDialog.tsx +66 -0
  33. package/src/interfaces/web/src/components/structure/StructureDialogs.tsx +172 -0
  34. package/src/interfaces/web/src/components/tasks/TaskDetailPanel.tsx +142 -0
  35. package/src/interfaces/web/src/components/tasks/taskStatus.tsx +57 -0
  36. package/src/interfaces/web/src/hooks/useChat.ts +39 -7
  37. package/src/interfaces/web/src/i18n/en.ts +113 -3
  38. package/src/interfaces/web/src/i18n/es.ts +113 -3
  39. package/src/interfaces/web/src/lib/api/conversations.ts +6 -0
  40. package/src/interfaces/web/src/lib/api/organization.ts +18 -0
  41. package/src/interfaces/web/src/lib/api/projectFiles.ts +19 -0
  42. package/src/interfaces/web/src/lib/api/tasks.ts +16 -1
  43. package/src/interfaces/web/src/lib/api.ts +2 -0
  44. package/src/interfaces/web/src/lib/slug.ts +11 -0
  45. package/src/interfaces/web/src/screens/ProjectScreen.tsx +22 -3
  46. package/src/interfaces/web/src/screens/SettingsScreen.tsx +1 -1
  47. package/src/interfaces/web/src/screens/project/AgentDetailScreen.tsx +31 -11
  48. package/src/interfaces/web/src/screens/project/AgentsTab.tsx +24 -7
  49. package/src/interfaces/web/src/screens/project/ChatTab.tsx +136 -36
  50. package/src/interfaces/web/src/screens/project/DocsTab.tsx +13 -0
  51. package/src/interfaces/web/src/screens/project/FilesTab.tsx +12 -0
  52. package/src/interfaces/web/src/screens/project/Overview.tsx +122 -10
  53. package/src/interfaces/web/src/screens/project/StructureTab.tsx +147 -0
  54. package/src/interfaces/web/src/screens/project/TasksTab.tsx +101 -62
  55. package/src/interfaces/web/src/types/daemon.ts +68 -0
  56. package/src/interfaces/web/dist/assets/index-CAUezTBY.css +0 -1
  57. package/src/interfaces/web/dist/assets/index-DaE_memX.js +0 -651
  58. package/src/interfaces/web/dist/assets/index-DaE_memX.js.map +0 -1
@@ -1,14 +1,19 @@
1
1
  import { useState } from "react";
2
- import { Check, Plus, RotateCcw, Trash2 } from "lucide-react";
2
+ import { useSearchParams } from "react-router-dom";
3
+ import { Plus, ChevronDown, ChevronUp, Check, Trash2, RotateCcw } from "lucide-react";
3
4
  import { Tasks } from "../../lib/api";
4
5
  import { Section } from "../../components/Section";
5
6
  import { PagedList, usePagedQuery } from "../../components/Pager";
6
- import { Badge, Button, Empty, Field, Input, Loading } from "../../components/ui";
7
+ import { Badge, Button, Empty, Field, Input, Loading, Textarea } from "../../components/ui";
7
8
  import { useToast } from "../../components/Toast";
9
+ import { StatusIcon, StatusBadge, effectiveStatus } from "../../components/tasks/taskStatus";
10
+ import { TaskDetailPanel } from "../../components/tasks/TaskDetailPanel";
8
11
  import { t } from "../../i18n";
9
12
 
10
13
  export function TasksTab({ pid }: { pid: string }) {
11
14
  const [state, setState] = useState<"open" | "done" | "dropped">("open");
15
+ const [params, setParams] = useSearchParams();
16
+ const selected = params.get("task");
12
17
  const toast = useToast();
13
18
  // dedupingInterval:0 so switching the state filter always revalidates the
14
19
  // target page instead of showing the stale cached one from a prior switch.
@@ -19,14 +24,22 @@ export function TasksTab({ pid }: { pid: string }) {
19
24
  swr: { dedupingInterval: 0, revalidateOnFocus: true },
20
25
  });
21
26
  const [draft, setDraft] = useState("");
27
+ const [body, setBody] = useState("");
28
+ const [showBody, setShowBody] = useState(false);
22
29
  const [busy, setBusy] = useState(false);
23
30
 
31
+ const select = (id: string | null) => {
32
+ const next = new URLSearchParams(params);
33
+ if (id) next.set("task", id); else next.delete("task");
34
+ setParams(next, { replace: true });
35
+ };
36
+
24
37
  const add = async () => {
25
38
  if (!draft.trim()) return;
26
39
  setBusy(true);
27
40
  try {
28
- await Tasks.add(pid, { title: draft.trim() });
29
- setDraft("");
41
+ await Tasks.add(pid, { title: draft.trim(), body: body.trim() || null, source: "web" });
42
+ setDraft(""); setBody(""); setShowBody(false);
30
43
  toast.success(t("project.tasks.created"));
31
44
  paged.mutate();
32
45
  } catch (e: any) {
@@ -49,72 +62,98 @@ export function TasksTab({ pid }: { pid: string }) {
49
62
  <div className="flex gap-1">
50
63
  {(["open", "done", "dropped"] as const).map((s) => (
51
64
  <Button key={s} size="sm" data-testid={`task-filter-${s}`} variant={state === s ? "primary" : "ghost"} onClick={() => setState(s)}>
52
- {s}
65
+ {t(`tasks.state_${s}` as never)}
53
66
  </Button>
54
67
  ))}
55
68
  </div>
56
69
  }
57
70
  >
58
- <div className="mb-4 flex shrink-0 items-end gap-2">
59
- <Field label={t("project.tasks.add_label")}>
60
- <Input
61
- data-testid="task-input"
62
- placeholder={t("project.tasks.add_placeholder")}
63
- value={draft}
64
- onChange={(e) => setDraft(e.target.value)}
65
- onKeyDown={(e) => { if (e.key === "Enter") add(); }}
66
- />
67
- </Field>
68
- <Button variant="primary" data-testid="task-add" onClick={add} loading={busy}>
69
- <Plus size={14} /> {t("project.tasks.add")}
70
- </Button>
71
+ <div className="mb-4 shrink-0 space-y-2">
72
+ <div className="flex items-end gap-2">
73
+ <Field label={t("project.tasks.add_label")}>
74
+ <Input
75
+ data-testid="task-input"
76
+ placeholder={t("project.tasks.add_placeholder")}
77
+ value={draft}
78
+ onChange={(e) => setDraft(e.target.value)}
79
+ onKeyDown={(e) => { if (e.key === "Enter" && !showBody) add(); }}
80
+ />
81
+ </Field>
82
+ <Button variant="ghost" size="sm" onClick={() => setShowBody((v) => !v)} aria-label={t("tasks.toggle_prompt")}>
83
+ {showBody ? <ChevronUp size={14} /> : <ChevronDown size={14} />} {t("tasks.field_prompt")}
84
+ </Button>
85
+ <Button variant="primary" data-testid="task-add" onClick={add} loading={busy}>
86
+ <Plus size={14} /> {t("project.tasks.add")}
87
+ </Button>
88
+ </div>
89
+ {showBody && (
90
+ <Textarea rows={3} value={body} onChange={(e) => setBody(e.target.value)} placeholder={t("tasks.prompt_ph")} className="text-xs" />
91
+ )}
71
92
  </div>
72
93
 
73
- {paged.isLoading && <Loading />}
74
- {!paged.isLoading && paged.total === 0 && (
75
- <Empty>
76
- {state === "open"
77
- ? t("project.tasks.empty_open")
78
- : t("project.tasks.empty", { state })}
79
- {" "}<code>apx task add "…"</code>
80
- </Empty>
81
- )}
94
+ <div className="flex min-h-0 flex-1 gap-4">
95
+ <div className="flex min-w-0 flex-1 flex-col">
96
+ {paged.isLoading && <Loading />}
97
+ {!paged.isLoading && paged.total === 0 && (
98
+ <Empty>
99
+ {state === "open" ? t("project.tasks.empty_open") : t("project.tasks.empty", { state })}
100
+ {" "}<code>apx task add "…"</code>
101
+ </Empty>
102
+ )}
82
103
 
83
- <PagedList paged={paged} fullHeight>
84
- <ul className="space-y-2 text-sm" data-testid="task-list">
85
- {paged.items.map((task) => (
86
- <li key={task.id} data-testid={`task-${task.id}`} className="flex items-start gap-3 rounded-md border border-border bg-muted/30 px-3 py-2">
87
- <span className="mt-0.5 font-mono text-[10px] text-muted-fg">{task.id}</span>
88
- <div className="flex-1">
89
- <div className="font-medium">{task.title}</div>
90
- <div className="mt-0.5 flex flex-wrap items-center gap-2 text-xs text-muted-fg">
91
- {task.tags?.map((tag) => <Badge key={tag}>#{tag}</Badge>)}
92
- {task.agent && <Badge tone="info">@{task.agent}</Badge>}
93
- {task.source && <span>{t("project.tasks.via")} {task.source}</span>}
94
- {task.due && <span>{t("project.tasks.due")} {task.due}</span>}
95
- </div>
96
- </div>
97
- <div className="flex gap-1">
98
- {state === "open" && (
99
- <>
100
- <Button size="sm" variant="secondary" aria-label={t("project.tasks.aria_done")} data-testid={`task-done-${task.id}`} onClick={() => mark(() => Tasks.done(pid, task.id), t("project.tasks.done"))}>
101
- <Check size={13} />
102
- </Button>
103
- <Button size="sm" variant="destructive" aria-label={t("project.tasks.aria_drop")} data-testid={`task-drop-${task.id}`} onClick={() => mark(() => Tasks.drop(pid, task.id), t("project.tasks.drop"))}>
104
- <Trash2 size={13} />
105
- </Button>
106
- </>
107
- )}
108
- {state !== "open" && (
109
- <Button size="sm" variant="ghost" aria-label={t("project.tasks.aria_reopen")} data-testid={`task-reopen-${task.id}`} onClick={() => mark(() => Tasks.reopen(pid, task.id), t("project.tasks.reopen"))}>
110
- <RotateCcw size={13} />
111
- </Button>
112
- )}
113
- </div>
114
- </li>
115
- ))}
116
- </ul>
117
- </PagedList>
104
+ <PagedList paged={paged} fullHeight>
105
+ <ul className="space-y-2 text-sm" data-testid="task-list">
106
+ {paged.items.map((task) => {
107
+ const eff = effectiveStatus(task);
108
+ return (
109
+ <li
110
+ key={task.id}
111
+ data-testid={`task-${task.id}`}
112
+ onClick={() => select(task.id)}
113
+ className={`flex cursor-pointer items-start gap-3 rounded-md border px-3 py-2 hover:border-muted-fg/50 ${selected === task.id ? "border-primary/50 bg-primary/5" : "border-border bg-muted/30"}`}
114
+ >
115
+ <StatusIcon status={eff} className="mt-0.5 shrink-0" />
116
+ <div className="min-w-0 flex-1">
117
+ <div className="truncate font-medium">{task.title}</div>
118
+ <div className="mt-0.5 flex flex-wrap items-center gap-2 text-xs text-muted-fg">
119
+ {task.state === "open" && <StatusBadge status={eff} />}
120
+ {task.tags?.map((tag) => <Badge key={tag}>#{tag}</Badge>)}
121
+ {task.agent && <Badge tone="info">@{task.agent}</Badge>}
122
+ {task.due && <span>{t("project.tasks.due")} {task.due}</span>}
123
+ </div>
124
+ </div>
125
+ <div className="flex shrink-0 gap-1" onClick={(e) => e.stopPropagation()}>
126
+ {task.state === "open" ? (
127
+ <>
128
+ <Button size="sm" variant="secondary" aria-label={t("project.tasks.aria_done")} data-testid={`task-done-${task.id}`} onClick={() => mark(() => Tasks.done(pid, task.id), t("project.tasks.done"))}>
129
+ <Check size={13} />
130
+ </Button>
131
+ <Button size="sm" variant="destructive" aria-label={t("project.tasks.aria_drop")} data-testid={`task-drop-${task.id}`} onClick={() => mark(() => Tasks.drop(pid, task.id), t("project.tasks.drop"))}>
132
+ <Trash2 size={13} />
133
+ </Button>
134
+ </>
135
+ ) : (
136
+ <Button size="sm" variant="ghost" aria-label={t("project.tasks.aria_reopen")} data-testid={`task-reopen-${task.id}`} onClick={() => mark(() => Tasks.reopen(pid, task.id), t("project.tasks.reopen"))}>
137
+ <RotateCcw size={13} />
138
+ </Button>
139
+ )}
140
+ </div>
141
+ </li>
142
+ );
143
+ })}
144
+ </ul>
145
+ </PagedList>
146
+ </div>
147
+
148
+ {selected && (
149
+ <TaskDetailPanel
150
+ pid={pid}
151
+ taskId={selected}
152
+ onClose={() => select(null)}
153
+ onChanged={() => paged.mutate()}
154
+ />
155
+ )}
156
+ </div>
118
157
  </Section>
119
158
  );
120
159
  }
@@ -18,6 +18,9 @@ export interface ProjectEntry {
18
18
  storagePath?: string;
19
19
  }
20
20
 
21
+ // Autonomy mirrors the super-agent permission modes.
22
+ export type AgentAutonomy = "total" | "automatico" | "permiso";
23
+
21
24
  export interface AgentEntry {
22
25
  slug: string;
23
26
  role: string | null;
@@ -28,6 +31,8 @@ export interface AgentEntry {
28
31
  parent?: string | null;
29
32
  type?: string | null;
30
33
  area?: string | null;
34
+ emoji?: string | null;
35
+ autonomy?: AgentAutonomy | null;
31
36
  skills: string[];
32
37
  tools: string[];
33
38
  }
@@ -52,19 +57,77 @@ export interface RoutineEntry {
52
57
  post_commands?: string[];
53
58
  }
54
59
 
60
+ // Workflow sub-status for an open task (orthogonal to `state`).
61
+ export type TaskStatus = "pending" | "running" | "in_review" | "blocked";
62
+
55
63
  export interface TaskEntry {
56
64
  id: string;
57
65
  state: "open" | "done" | "dropped";
66
+ status?: TaskStatus;
58
67
  title: string;
59
68
  body: string | null;
60
69
  tags: string[];
61
70
  due: string | null;
62
71
  agent: string | null;
63
72
  source: string | null;
73
+ created_by?: string | null;
74
+ thread?: string | null;
75
+ done_at?: string | null;
76
+ done_by?: string | null;
77
+ dropped_at?: string | null;
64
78
  created_at: string;
65
79
  updated_at: string;
66
80
  }
67
81
 
82
+ export interface OrgArea {
83
+ slug: string;
84
+ name: string;
85
+ goal: string | null;
86
+ }
87
+
88
+ export interface OrgRole {
89
+ slug: string;
90
+ name: string;
91
+ area: string | null;
92
+ description: string | null;
93
+ }
94
+
95
+ export interface Organization {
96
+ areas: OrgArea[];
97
+ roles: OrgRole[];
98
+ }
99
+
100
+ // Project file browser (core/stores/project-files.js).
101
+ export type FileKind = "markdown" | "text" | "image" | "binary";
102
+
103
+ export interface FileNode {
104
+ name: string;
105
+ path: string;
106
+ type: "dir" | "file";
107
+ kind?: FileKind;
108
+ children?: FileNode[];
109
+ }
110
+
111
+ export interface FileTreeResponse {
112
+ scope: "project" | "docs";
113
+ root: string;
114
+ subdir: string;
115
+ tree: FileNode[];
116
+ truncated: boolean;
117
+ }
118
+
119
+ export interface FileContent {
120
+ path: string;
121
+ name: string;
122
+ kind: FileKind;
123
+ size: number;
124
+ modified: string;
125
+ encoding: "utf8" | "base64" | "binary";
126
+ mime?: string;
127
+ content: string | null;
128
+ too_large?: boolean;
129
+ }
130
+
68
131
  export interface McpEntry {
69
132
  name: string;
70
133
  source: "apc" | "runtime" | "global" | string;
@@ -143,6 +206,11 @@ export interface ConversationMessage {
143
206
  content: string;
144
207
  ts?: string;
145
208
  name?: string;
209
+ /** Present on role:"tool" rows from the global ledger — the tool name and its
210
+ * structured args/result, so the viewer can render a ToolCall part. */
211
+ tool?: string;
212
+ args?: Record<string, unknown>;
213
+ result?: unknown;
146
214
  }
147
215
 
148
216
  export interface ConversationDetail {