@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
@@ -0,0 +1,99 @@
1
+ // Project file browser + docs editor API.
2
+ //
3
+ // A sandboxed view of one project's own files. `scope` selects the sandbox:
4
+ // project — the whole repo (the /files browser)
5
+ // docs — the docs subfolder (config docs.root, default "docs"); powers
6
+ // the /docs spec editor
7
+ //
8
+ // GET /projects/:pid/fs/tree?scope=project|docs
9
+ // GET /projects/:pid/fs/file?scope=…&path=<rel>
10
+ // PUT /projects/:pid/fs/file body { scope?, path, content }
11
+ // POST /projects/:pid/fs/dir body { scope?, path }
12
+ // DELETE /projects/:pid/fs/entry?scope=…&path=<rel>
13
+ //
14
+ // Thin adapter over core/stores/project-files.
15
+ import {
16
+ listTree,
17
+ readFile,
18
+ writeFile,
19
+ makeDir,
20
+ removeEntry,
21
+ docsSubdir,
22
+ } from "#core/stores/project-files.js";
23
+
24
+ // Resolve the sandbox subdir for a scope. Unknown scope → project root.
25
+ function scopeOpts(p, scope) {
26
+ if (scope === "docs") return { subdir: docsSubdir(p.config), scope: "docs" };
27
+ return { subdir: "", scope: "project" };
28
+ }
29
+
30
+ export function register(app, { project }) {
31
+ app.get("/projects/:pid/fs/tree", (req, res) => {
32
+ const p = project(req, res);
33
+ if (!p) return;
34
+ const { subdir, scope } = scopeOpts(p, req.query?.scope);
35
+ try {
36
+ res.json({ scope, ...listTree(p.path, { subdir }) });
37
+ } catch (e) {
38
+ res.status(400).json({ error: e.message });
39
+ }
40
+ });
41
+
42
+ app.get("/projects/:pid/fs/file", (req, res) => {
43
+ const p = project(req, res);
44
+ if (!p) return;
45
+ const rel = req.query?.path;
46
+ if (!rel) return res.status(400).json({ error: "path required" });
47
+ const { subdir } = scopeOpts(p, req.query?.scope);
48
+ try {
49
+ const file = readFile(p.path, String(rel), { subdir });
50
+ if (!file) return res.status(404).json({ error: "file not found" });
51
+ res.json(file);
52
+ } catch (e) {
53
+ res.status(400).json({ error: e.message });
54
+ }
55
+ });
56
+
57
+ app.put("/projects/:pid/fs/file", (req, res) => {
58
+ const p = project(req, res);
59
+ if (!p) return;
60
+ const { path: rel, content, scope } = req.body || {};
61
+ if (!rel) return res.status(400).json({ error: "path required" });
62
+ if (typeof content !== "string")
63
+ return res.status(400).json({ error: "content must be a string" });
64
+ const { subdir } = scopeOpts(p, scope);
65
+ try {
66
+ res.json(writeFile(p.path, String(rel), content, { subdir }));
67
+ } catch (e) {
68
+ res.status(400).json({ error: e.message });
69
+ }
70
+ });
71
+
72
+ app.post("/projects/:pid/fs/dir", (req, res) => {
73
+ const p = project(req, res);
74
+ if (!p) return;
75
+ const { path: rel, scope } = req.body || {};
76
+ if (!rel) return res.status(400).json({ error: "path required" });
77
+ const { subdir } = scopeOpts(p, scope);
78
+ try {
79
+ res.status(201).json(makeDir(p.path, String(rel), { subdir }));
80
+ } catch (e) {
81
+ res.status(400).json({ error: e.message });
82
+ }
83
+ });
84
+
85
+ app.delete("/projects/:pid/fs/entry", (req, res) => {
86
+ const p = project(req, res);
87
+ if (!p) return;
88
+ const rel = req.query?.path;
89
+ if (!rel) return res.status(400).json({ error: "path required" });
90
+ const { subdir } = scopeOpts(p, req.query?.scope);
91
+ try {
92
+ if (!removeEntry(p.path, String(rel), { subdir }))
93
+ return res.status(404).json({ error: "entry not found" });
94
+ res.json({ ok: true });
95
+ } catch (e) {
96
+ res.status(400).json({ error: e.message });
97
+ }
98
+ });
99
+ }
@@ -0,0 +1,88 @@
1
+ // Organization structure (areas + roles) per project.
2
+ //
3
+ // GET /projects/:pid/organization -> { areas, roles }
4
+ // POST /projects/:pid/organization/areas body { name, slug?, goal? }
5
+ // PATCH /projects/:pid/organization/areas/:slug body { name?, goal? }
6
+ // DELETE /projects/:pid/organization/areas/:slug -> { ok }
7
+ // POST /projects/:pid/organization/roles body { name, slug?, area?, description? }
8
+ // PATCH /projects/:pid/organization/roles/:slug body { name?, area?, description? }
9
+ // DELETE /projects/:pid/organization/roles/:slug -> { ok }
10
+ //
11
+ // Thin adapter: parse body, call core/stores/organization, shape the response.
12
+ import {
13
+ readOrganization,
14
+ createArea,
15
+ updateArea,
16
+ removeArea,
17
+ createRole,
18
+ updateRole,
19
+ removeRole,
20
+ } from "#core/stores/organization.js";
21
+
22
+ export function register(app, { project }) {
23
+ app.get("/projects/:pid/organization", (req, res) => {
24
+ const p = project(req, res);
25
+ if (!p) return;
26
+ res.json(readOrganization(p.path));
27
+ });
28
+
29
+ app.post("/projects/:pid/organization/areas", (req, res) => {
30
+ const p = project(req, res);
31
+ if (!p) return;
32
+ try {
33
+ res.status(201).json(createArea(p.path, req.body || {}));
34
+ } catch (e) {
35
+ res.status(400).json({ error: e.message });
36
+ }
37
+ });
38
+
39
+ app.patch("/projects/:pid/organization/areas/:slug", (req, res) => {
40
+ const p = project(req, res);
41
+ if (!p) return;
42
+ try {
43
+ const area = updateArea(p.path, req.params.slug, req.body || {});
44
+ if (!area) return res.status(404).json({ error: "area not found" });
45
+ res.json(area);
46
+ } catch (e) {
47
+ res.status(400).json({ error: e.message });
48
+ }
49
+ });
50
+
51
+ app.delete("/projects/:pid/organization/areas/:slug", (req, res) => {
52
+ const p = project(req, res);
53
+ if (!p) return;
54
+ if (!removeArea(p.path, req.params.slug))
55
+ return res.status(404).json({ error: "area not found" });
56
+ res.json({ ok: true });
57
+ });
58
+
59
+ app.post("/projects/:pid/organization/roles", (req, res) => {
60
+ const p = project(req, res);
61
+ if (!p) return;
62
+ try {
63
+ res.status(201).json(createRole(p.path, req.body || {}));
64
+ } catch (e) {
65
+ res.status(400).json({ error: e.message });
66
+ }
67
+ });
68
+
69
+ app.patch("/projects/:pid/organization/roles/:slug", (req, res) => {
70
+ const p = project(req, res);
71
+ if (!p) return;
72
+ try {
73
+ const role = updateRole(p.path, req.params.slug, req.body || {});
74
+ if (!role) return res.status(404).json({ error: "role not found" });
75
+ res.json(role);
76
+ } catch (e) {
77
+ res.status(400).json({ error: e.message });
78
+ }
79
+ });
80
+
81
+ app.delete("/projects/:pid/organization/roles/:slug", (req, res) => {
82
+ const p = project(req, res);
83
+ if (!p) return;
84
+ if (!removeRole(p.path, req.params.slug))
85
+ return res.status(404).json({ error: "role not found" });
86
+ res.json({ ok: true });
87
+ });
88
+ }
@@ -210,6 +210,8 @@ export function agentToResponse(a) {
210
210
  "Parent",
211
211
  "Type",
212
212
  "Area",
213
+ "Emoji",
214
+ "Autonomy",
213
215
  ]);
214
216
  const extra = {};
215
217
  for (const [k, v] of Object.entries(f)) {
@@ -229,6 +231,11 @@ export function agentToResponse(a) {
229
231
  // definitional, kept in APC frontmatter.
230
232
  type: f.Type || null,
231
233
  area: f.Area || null,
234
+ // Display emoji (avatar) + autonomy (permission mode: total/automatico/
235
+ // permiso). Definitional, kept in APC frontmatter so they travel with the
236
+ // project and stay diffable.
237
+ emoji: f.Emoji || null,
238
+ autonomy: f.Autonomy || null,
232
239
  skills: Array.isArray(f.Skills) ? f.Skills : [],
233
240
  tools: Array.isArray(f.Tools) ? f.Tools : [],
234
241
  extra,
@@ -14,7 +14,9 @@ import {
14
14
  doneTask,
15
15
  dropTask,
16
16
  reopenTask,
17
+ setTaskStatus,
17
18
  countTasks,
19
+ TASK_STATUSES,
18
20
  } from "#core/stores/tasks.js";
19
21
  import { pageEnvelope } from "./shared.js";
20
22
 
@@ -113,6 +115,18 @@ export function register(app, { project, projects }) {
113
115
  res.json(updated);
114
116
  });
115
117
 
118
+ // Move an open task through its workflow (pending → running → in_review …).
119
+ app.post("/projects/:pid/tasks/:id/status", (req, res) => {
120
+ const p = project(req, res);
121
+ if (!p) return;
122
+ const { status } = req.body || {};
123
+ if (!TASK_STATUSES.includes(status))
124
+ return res.status(400).json({ error: `status must be one of ${TASK_STATUSES.join(", ")}` });
125
+ const updated = setTaskStatus(p.storagePath, req.params.id, status);
126
+ if (!updated) return res.status(404).json({ error: "task not found" });
127
+ res.json(updated);
128
+ });
129
+
116
130
  // Lightweight summary endpoint for status displays.
117
131
  app.get("/projects/:pid/tasks-summary", (req, res) => {
118
132
  const p = project(req, res);
@@ -33,6 +33,8 @@ import { register as registerRuntimes } from "./api/runtimes.js";
33
33
  import { register as registerRoutines } from "./api/routines.js";
34
34
  import { register as registerArtifacts } from "./api/artifacts.js";
35
35
  import { register as registerTasks } from "./api/tasks.js";
36
+ import { register as registerOrganization } from "./api/organization.js";
37
+ import { register as registerProjectFiles } from "./api/files-project.js";
36
38
  import { register as registerConfig } from "./api/config.js";
37
39
  import { register as registerRun } from "./api/run.js";
38
40
  import { register as registerTopLevel } from "./api/top-level.js";
@@ -121,6 +123,8 @@ export function buildApi({
121
123
  registerRoutines(app, ctx);
122
124
  registerArtifacts(app, ctx);
123
125
  registerTasks(app, ctx);
126
+ registerOrganization(app, ctx);
127
+ registerProjectFiles(app, ctx);
124
128
  registerConfig(app, ctx);
125
129
 
126
130
  // ---- Top-level shortcuts (MCP server clients) --------------------
@@ -0,0 +1,77 @@
1
+ // apx org — organization structure (areas + roles) for a project.
2
+ // Backed by /projects/:pid/organization (core/stores/organization.js).
3
+ //
4
+ // apx org show [--project X]
5
+ // apx org area add "<name>" [--slug s] [--goal g] [--project X]
6
+ // apx org area rm <slug> [--project X]
7
+ // apx org role add "<name>" [--slug s] [--area a] [--desc d] [--project X]
8
+ // apx org role rm <slug> [--project X]
9
+ //
10
+ // Thin surface over the daemon API — the web panel calls the same routes.
11
+ import { http } from "../http.js";
12
+ import { resolveProjectId } from "./project.js";
13
+
14
+ export const ORG_USAGE = {
15
+ show: "apx org show [--project X]",
16
+ areaAdd: 'apx org area add "<name>" [--slug s] [--goal g] [--project X]',
17
+ areaRm: "apx org area rm <slug> [--project X]",
18
+ roleAdd: 'apx org role add "<name>" [--slug s] [--area a] [--desc d] [--project X]',
19
+ roleRm: "apx org role rm <slug> [--project X]",
20
+ };
21
+
22
+ function fail(key, msg) {
23
+ console.error(`apx org: ${msg}`);
24
+ console.error(`Usage: ${ORG_USAGE[key]}`);
25
+ process.exit(1);
26
+ }
27
+
28
+ export async function cmdOrgShow(args) {
29
+ const pid = await resolveProjectId(args?.flags?.project);
30
+ const org = await http.get(`/projects/${pid}/organization`);
31
+ if (!org.areas.length && !org.roles.length) {
32
+ console.log("(no organization structure yet)");
33
+ return;
34
+ }
35
+ console.log("Areas:");
36
+ for (const a of org.areas) console.log(` • ${a.name} (${a.slug})${a.goal ? ` — ${a.goal}` : ""}`);
37
+ console.log("Roles:");
38
+ for (const r of org.roles) {
39
+ console.log(` • ${r.name} (${r.slug})${r.area ? ` [${r.area}]` : ""}${r.description ? ` — ${r.description}` : ""}`);
40
+ }
41
+ }
42
+
43
+ export async function cmdOrgAreaAdd(args) {
44
+ const name = (args._ || []).slice(1).join(" ").trim();
45
+ if (!name) return fail("areaAdd", "name required");
46
+ const pid = await resolveProjectId(args?.flags?.project);
47
+ const area = await http.post(`/projects/${pid}/organization/areas`, {
48
+ name, slug: args.flags?.slug, goal: args.flags?.goal,
49
+ });
50
+ console.log(`added area ${area.name} (${area.slug})`);
51
+ }
52
+
53
+ export async function cmdOrgAreaRm(args) {
54
+ const slug = (args._ || [])[1];
55
+ if (!slug) return fail("areaRm", "slug required");
56
+ const pid = await resolveProjectId(args?.flags?.project);
57
+ await http.delete(`/projects/${pid}/organization/areas/${encodeURIComponent(slug)}`);
58
+ console.log(`removed area ${slug}`);
59
+ }
60
+
61
+ export async function cmdOrgRoleAdd(args) {
62
+ const name = (args._ || []).slice(1).join(" ").trim();
63
+ if (!name) return fail("roleAdd", "name required");
64
+ const pid = await resolveProjectId(args?.flags?.project);
65
+ const role = await http.post(`/projects/${pid}/organization/roles`, {
66
+ name, slug: args.flags?.slug, area: args.flags?.area, description: args.flags?.desc,
67
+ });
68
+ console.log(`added role ${role.name} (${role.slug})${role.area ? ` in ${role.area}` : ""}`);
69
+ }
70
+
71
+ export async function cmdOrgRoleRm(args) {
72
+ const slug = (args._ || [])[1];
73
+ if (!slug) return fail("roleRm", "slug required");
74
+ const pid = await resolveProjectId(args?.flags?.project);
75
+ await http.delete(`/projects/${pid}/organization/roles/${encodeURIComponent(slug)}`);
76
+ console.log(`removed role ${slug}`);
77
+ }
@@ -140,6 +140,13 @@ import {
140
140
  cmdTaskReopen,
141
141
  cmdTaskPatch,
142
142
  } from "./commands/task.js";
143
+ import {
144
+ cmdOrgShow,
145
+ cmdOrgAreaAdd,
146
+ cmdOrgAreaRm,
147
+ cmdOrgRoleAdd,
148
+ cmdOrgRoleRm,
149
+ } from "./commands/org.js";
143
150
 
144
151
  const __filename = fileURLToPath(import.meta.url);
145
152
  const __dirname = path.dirname(__filename);
@@ -1531,6 +1538,24 @@ const HELP_TOPICS = new Map(Object.entries({
1531
1538
  usage: ["apx tasks <subcommand> [args] [--flags]"],
1532
1539
  examples: ["apx tasks list"],
1533
1540
  }),
1541
+ org: topic({
1542
+ title: "apx org",
1543
+ summary: "Organization structure (areas + roles) for a project — the org chart companies/enterprises use to group agents.",
1544
+ usage: ["apx org <show|area|role> [args] [--flags]"],
1545
+ commands: [
1546
+ ["show | list", "Print the project's areas and roles."],
1547
+ ["area add \"<name>\"", "Create an area. --slug, --goal optional."],
1548
+ ["area rm <slug>", "Remove an area (its roles are detached, not deleted)."],
1549
+ ["role add \"<name>\"", "Create a role. --slug, --area, --desc optional."],
1550
+ ["role rm <slug>", "Remove a role."],
1551
+ ],
1552
+ options: [["--project <name|id|path>", "Pin command to a specific project."]],
1553
+ examples: [
1554
+ "apx org area add \"Engineering\" --goal \"Build the product\"",
1555
+ "apx org role add \"Tech Lead\" --area engineering",
1556
+ "apx org show",
1557
+ ],
1558
+ }),
1534
1559
  "task add": topic({
1535
1560
  title: "apx task add",
1536
1561
  summary: "Create a task on a project's TODO list.",
@@ -2604,6 +2629,29 @@ async function dispatch(cmd, rest) {
2604
2629
  break;
2605
2630
  }
2606
2631
 
2632
+ case "org":
2633
+ case "organization": {
2634
+ const sub = rest[0];
2635
+ const a = parseArgs(rest.slice(1));
2636
+ // `apx org area add ...` / `apx org role rm ...` — the resource verb is
2637
+ // the first positional, the action the second.
2638
+ if (!sub || sub === "show" || sub === "list") await cmdOrgShow(a);
2639
+ else if (sub === "area") {
2640
+ const action = rest[1];
2641
+ const aa = parseArgs(rest.slice(1)); // keep `area` as _[0] for name parsing
2642
+ if (action === "add" || action === "new") await cmdOrgAreaAdd(aa);
2643
+ else if (action === "rm" || action === "remove" || action === "delete") await cmdOrgAreaRm(aa);
2644
+ else die("usage: apx org area <add|rm> ...");
2645
+ } else if (sub === "role") {
2646
+ const action = rest[1];
2647
+ const ra = parseArgs(rest.slice(1));
2648
+ if (action === "add" || action === "new") await cmdOrgRoleAdd(ra);
2649
+ else if (action === "rm" || action === "remove" || action === "delete") await cmdOrgRoleRm(ra);
2650
+ else die("usage: apx org role <add|rm> ...");
2651
+ } else die(`unknown org subcommand: ${sub}\nUsage: apx org <show|area|role> ...`);
2652
+ break;
2653
+ }
2654
+
2607
2655
  case "skills": {
2608
2656
  const sub = rest[0];
2609
2657
  const a = parseArgs(rest.slice(1));