@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.
- package/package.json +1 -1
- package/src/core/agent/skills/index.js +9 -0
- package/src/core/agent/skills/inspector.js +7 -2
- package/src/core/agent/skills/policy.js +128 -0
- package/src/core/agent/super-agent.js +8 -1
- package/src/core/agent/tools/handlers/list-skills.js +6 -3
- package/src/core/agent/tools/handlers/load-skill.js +9 -2
- package/src/core/apc/paths.js +7 -0
- 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/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/skills.js +301 -17
- 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/commands/skills.js +3 -2
- package/src/interfaces/cli/index.js +48 -0
- package/src/interfaces/web/dist/assets/index-CnQb4N6C.js +731 -0
- package/src/interfaces/web/dist/assets/index-CnQb4N6C.js.map +1 -0
- package/src/interfaces/web/dist/assets/index-Dv3X-zpx.css +1 -0
- package/src/interfaces/web/dist/index.html +2 -2
- package/src/interfaces/web/src/components/agents/AgentFormFields.tsx +123 -0
- package/src/interfaces/web/src/components/common/ConfirmDialog.tsx +51 -0
- 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/settings/SkillsManager.tsx +465 -0
- package/src/interfaces/web/src/components/settings/SkillsSettings.tsx +49 -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/i18n/en.ts +171 -0
- package/src/interfaces/web/src/i18n/es.ts +171 -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/skills.ts +79 -8
- 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 +25 -2
- package/src/interfaces/web/src/screens/SettingsScreen.tsx +3 -3
- 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/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/SkillsTab.tsx +13 -0
- 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 +63 -0
- package/src/interfaces/web/dist/assets/index-CEI8DfVg.css +0 -1
- package/src/interfaces/web/dist/assets/index-DJ-ocXOR.js +0 -651
- package/src/interfaces/web/dist/assets/index-DJ-ocXOR.js.map +0 -1
package/src/core/stores/tasks.js
CHANGED
|
@@ -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:
|
|
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:
|
|
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 {
|
|
132
|
-
|
|
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,
|