@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
@@ -19,6 +19,16 @@ import path from "node:path";
19
19
  import { nowIso } from "../util/time.js";
20
20
  import { shortId as makeShortId } from "../util/ids.js";
21
21
 
22
+ // Workflow sub-status for an *open* task. Orthogonal to `state`
23
+ // (open/done/dropped): `state` is the storage lifecycle, `status` is how an
24
+ // open task is progressing. `blocked` means it's waiting on a human/agent.
25
+ export const TASK_STATUSES = Object.freeze(["pending", "running", "in_review", "blocked"]);
26
+ export const DEFAULT_TASK_STATUS = "pending";
27
+
28
+ function normalizeStatus(v) {
29
+ return TASK_STATUSES.includes(v) ? v : DEFAULT_TASK_STATUS;
30
+ }
31
+
22
32
  function tasksDir(storagePath) {
23
33
  return path.join(storagePath, "tasks");
24
34
  }
@@ -72,12 +82,15 @@ function projectState(events) {
72
82
  created_at: ev.ts,
73
83
  updated_at: ev.ts,
74
84
  state: "open",
85
+ status: normalizeStatus(ev.status),
75
86
  title: ev.title || "",
76
87
  body: ev.body || null,
77
88
  tags: Array.isArray(ev.tags) ? [...ev.tags] : [],
78
89
  due: ev.due || null,
79
90
  agent: ev.agent || null,
80
91
  source: ev.source || null,
92
+ created_by: ev.created_by || null,
93
+ thread: ev.thread || null,
81
94
  meta: ev.meta && typeof ev.meta === "object" ? { ...ev.meta } : {},
82
95
  });
83
96
  break;
@@ -87,7 +100,7 @@ function projectState(events) {
87
100
  const patch = ev.patch && typeof ev.patch === "object" ? ev.patch : {};
88
101
  for (const k of Object.keys(patch)) {
89
102
  if (k === "id" || k === "state" || k === "created_at") continue;
90
- existing[k] = patch[k];
103
+ existing[k] = k === "status" ? normalizeStatus(patch[k]) : patch[k];
91
104
  }
92
105
  existing.updated_at = ev.ts;
93
106
  break;
@@ -141,10 +154,13 @@ export function createTask(storagePath, fields) {
141
154
  op: "create",
142
155
  title: fields.title.trim(),
143
156
  body: fields.body || null,
157
+ status: normalizeStatus(fields.status),
144
158
  tags: Array.isArray(fields.tags) ? fields.tags.filter((t) => typeof t === "string") : [],
145
159
  due: fields.due || null,
146
160
  agent: fields.agent || null,
147
161
  source: fields.source || null,
162
+ created_by: fields.created_by || null,
163
+ thread: fields.thread || null,
148
164
  meta: fields.meta && typeof fields.meta === "object" ? fields.meta : {},
149
165
  };
150
166
  appendEvent(storagePath, ev);
@@ -207,6 +223,19 @@ export function patchTask(storagePath, idOrPrefix, patch) {
207
223
  return getTask(storagePath, existing.id);
208
224
  }
209
225
 
226
+ /** Set the workflow status (pending/running/in_review/blocked) of an open task. */
227
+ export function setTaskStatus(storagePath, idOrPrefix, status) {
228
+ const existing = getTask(storagePath, idOrPrefix);
229
+ if (!existing) return null;
230
+ appendEvent(storagePath, {
231
+ id: existing.id,
232
+ ts: nowIso(),
233
+ op: "update",
234
+ patch: { status: normalizeStatus(status) },
235
+ });
236
+ return getTask(storagePath, existing.id);
237
+ }
238
+
210
239
  /** Mark done. */
211
240
  export function doneTask(storagePath, idOrPrefix, by = null) {
212
241
  const existing = getTask(storagePath, idOrPrefix);
@@ -249,11 +278,15 @@ export function reopenTask(storagePath, idOrPrefix) {
249
278
  export function countTasks(storagePath) {
250
279
  const tasks = [...projectState(readAllEvents(storagePath)).values()];
251
280
  const today = new Date().toISOString().slice(0, 10);
281
+ const open = tasks.filter((t) => t.state === "open");
282
+ const byStatus = {};
283
+ for (const s of TASK_STATUSES) byStatus[s] = open.filter((t) => t.status === s).length;
252
284
  return {
253
- open: tasks.filter((t) => t.state === "open").length,
285
+ open: open.length,
254
286
  done: tasks.filter((t) => t.state === "done").length,
255
287
  dropped: tasks.filter((t) => t.state === "dropped").length,
256
- overdue: tasks.filter((t) => t.state === "open" && t.due && t.due < today).length,
288
+ overdue: open.filter((t) => t.due && t.due < today).length,
257
289
  total: tasks.length,
290
+ status: byStatus,
258
291
  };
259
292
  }
@@ -24,6 +24,17 @@ import {
24
24
  } from "#core/agent/memory.js";
25
25
  import { agentToResponse } from "./shared.js";
26
26
  import { normalizeVaultPatch } from "#core/apc/agents-vault.js";
27
+ import { PERMISSION_MODES } from "#core/constants/permissions.js";
28
+
29
+ // Autonomy mirrors the super-agent permission modes (total/automatico/permiso).
30
+ // An invalid value is dropped rather than persisted so a typo can't silently
31
+ // widen an agent's autonomy.
32
+ const AUTONOMY_VALUES = new Set(Object.values(PERMISSION_MODES));
33
+ function normalizeAutonomy(v) {
34
+ if (v === undefined) return undefined;
35
+ if (v === null || v === "") return null;
36
+ return AUTONOMY_VALUES.has(v) ? v : undefined;
37
+ }
27
38
 
28
39
  export function register(app, { projects, project }) {
29
40
  // Vault = global agent templates. Two-layer: bundled defaults shipped with
@@ -128,14 +139,17 @@ export function register(app, { projects, project }) {
128
139
  app.post("/projects/:pid/agents", (req, res) => {
129
140
  const p = project(req, res);
130
141
  if (!p) return;
131
- const { slug, role, model, skills, language, description, tools, is_master, parent } =
132
- req.body || {};
142
+ const {
143
+ slug, role, model, skills, language, description, tools, is_master,
144
+ parent, type, area, emoji, autonomy,
145
+ } = req.body || {};
133
146
  if (!slug) return res.status(400).json({ error: "slug required" });
134
147
  if (!/^[a-z][a-z0-9_-]*$/.test(slug))
135
148
  return res.status(400).json({ error: "invalid slug" });
136
149
  const existing = readAgents(p.path).find((a) => a.slug === slug);
137
150
  if (existing)
138
151
  return res.status(400).json({ error: `agent ${slug} already exists` });
152
+ const autonomyVal = normalizeAutonomy(autonomy);
139
153
  try {
140
154
  writeAgentFile(p.path, slug, {
141
155
  Role: role || null,
@@ -146,6 +160,10 @@ export function register(app, { projects, project }) {
146
160
  Tools: tools || [],
147
161
  Master: is_master ? true : null,
148
162
  Parent: parent || null,
163
+ Type: type || null,
164
+ Area: area || null,
165
+ Emoji: emoji || null,
166
+ Autonomy: autonomyVal || null,
149
167
  });
150
168
  ensureAgentDir(p.path, slug);
151
169
  ensureAgentRuntimeDir(p, slug);
@@ -179,6 +197,8 @@ export function register(app, { projects, project }) {
179
197
  setStr("Parent", b.parent);
180
198
  setStr("Type", b.type);
181
199
  setStr("Area", b.area);
200
+ setStr("Emoji", b.emoji);
201
+ setStr("Autonomy", normalizeAutonomy(b.autonomy));
182
202
  if (b.skills !== undefined) fields.Skills = Array.isArray(b.skills) ? b.skills : [];
183
203
  if (b.tools !== undefined) fields.Tools = Array.isArray(b.tools) ? b.tools : [];
184
204
  if (b.is_master !== undefined) {
@@ -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,