@agentprojectcontext/apx 1.74.1 → 1.75.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/prompt-builder.js +26 -7
- package/src/core/agent/render-template.js +22 -0
- package/src/core/profiles/block.js +290 -0
- package/src/core/profiles/bundled/secretary/PROFILE.es.md +44 -0
- package/src/core/profiles/bundled/secretary/PROFILE.md +44 -0
- package/src/core/profiles/bundled/secretary/channels/routine.md +43 -0
- package/src/core/profiles/bundled/secretary/config.schema.json +49 -0
- package/src/core/profiles/bundled/secretary/profile.json +20 -0
- package/src/core/profiles/bundled/secretary/routines/day-close.json +10 -0
- package/src/core/profiles/bundled/secretary/routines/day-open.json +10 -0
- package/src/core/profiles/index.js +16 -0
- package/src/core/profiles/lifecycle.js +720 -0
- package/src/core/profiles/manifest.js +193 -0
- package/src/core/profiles/paths.js +51 -0
- package/src/core/profiles/store.js +184 -0
- package/src/core/runtime-skills/apx-profile/SKILL.md +126 -0
- package/src/core/stores/routines.js +61 -1
- package/src/host/daemon/api/profiles.js +179 -0
- package/src/host/daemon/api/web.js +1 -1
- package/src/host/daemon/api.js +2 -0
- package/src/interfaces/cli/commands/profile.js +252 -0
- package/src/interfaces/cli/index.js +62 -0
- package/src/interfaces/web/dist/assets/index-CQ5kyFej.css +1 -0
- package/src/interfaces/web/dist/assets/{index-DUXlrW8P.js → index-CXeqTvfy.js} +186 -181
- package/src/interfaces/web/dist/assets/index-CXeqTvfy.js.map +1 -0
- package/src/interfaces/web/dist/index.html +2 -2
- package/src/interfaces/web/package-lock.json +15 -15
- package/src/interfaces/web/src/components/settings/ProfilePanel.tsx +245 -0
- package/src/interfaces/web/src/hooks/useProfiles.ts +37 -0
- package/src/interfaces/web/src/i18n/en.ts +31 -0
- package/src/interfaces/web/src/i18n/es.ts +31 -0
- package/src/interfaces/web/src/lib/api/profiles.ts +86 -0
- package/src/interfaces/web/src/screens/SettingsScreen.tsx +6 -2
- package/src/interfaces/web/dist/assets/index-COrRuBp1.css +0 -1
- package/src/interfaces/web/dist/assets/index-DUXlrW8P.js.map +0 -1
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
<link rel="apple-touch-icon" href="/favicon/dark/apple-touch-icon.png" media="(prefers-color-scheme: dark)" />
|
|
19
19
|
<link rel="manifest" href="/favicon/white/site.webmanifest" media="(prefers-color-scheme: light)" />
|
|
20
20
|
<link rel="manifest" href="/favicon/dark/site.webmanifest" media="(prefers-color-scheme: dark)" />
|
|
21
|
-
<script type="module" crossorigin src="/assets/index-
|
|
22
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
21
|
+
<script type="module" crossorigin src="/assets/index-CXeqTvfy.js"></script>
|
|
22
|
+
<link rel="stylesheet" crossorigin href="/assets/index-CQ5kyFej.css">
|
|
23
23
|
</head>
|
|
24
24
|
<body class="bg-background text-foreground antialiased">
|
|
25
25
|
<div id="root"></div>
|
|
@@ -1280,9 +1280,9 @@
|
|
|
1280
1280
|
}
|
|
1281
1281
|
},
|
|
1282
1282
|
"node_modules/@reduxjs/toolkit/node_modules/immer": {
|
|
1283
|
-
"version": "11.1.
|
|
1284
|
-
"resolved": "https://registry.npmjs.org/immer/-/immer-11.1.
|
|
1285
|
-
"integrity": "sha512-
|
|
1283
|
+
"version": "11.1.17",
|
|
1284
|
+
"resolved": "https://registry.npmjs.org/immer/-/immer-11.1.17.tgz",
|
|
1285
|
+
"integrity": "sha512-8Vu44Y0MuMBlTQz/jQ8HEMYNq/bBqk87MnBwYR5mC8AthfhEXidZ5aT/oA/CUqboa8THKltnD9L3xyqhU/Sy1Q==",
|
|
1286
1286
|
"license": "MIT",
|
|
1287
1287
|
"funding": {
|
|
1288
1288
|
"type": "opencollective",
|
|
@@ -2183,9 +2183,9 @@
|
|
|
2183
2183
|
}
|
|
2184
2184
|
},
|
|
2185
2185
|
"node_modules/baseline-browser-mapping": {
|
|
2186
|
-
"version": "2.11.
|
|
2187
|
-
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.
|
|
2188
|
-
"integrity": "sha512-
|
|
2186
|
+
"version": "2.11.14",
|
|
2187
|
+
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.14.tgz",
|
|
2188
|
+
"integrity": "sha512-JyJ954WzuIR8/FFzX0o5krdSTrBAkcCSRfWSleRsIHSWV+cZe2FI1PKggVkFke1hBldRs+LRxUczzE9iPmgZww==",
|
|
2189
2189
|
"dev": true,
|
|
2190
2190
|
"license": "Apache-2.0",
|
|
2191
2191
|
"bin": {
|
|
@@ -2601,9 +2601,9 @@
|
|
|
2601
2601
|
"license": "MIT"
|
|
2602
2602
|
},
|
|
2603
2603
|
"node_modules/electron-to-chromium": {
|
|
2604
|
-
"version": "1.5.
|
|
2605
|
-
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.
|
|
2606
|
-
"integrity": "sha512-
|
|
2604
|
+
"version": "1.5.407",
|
|
2605
|
+
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.407.tgz",
|
|
2606
|
+
"integrity": "sha512-4R8XgQOdfxexCd/u63lRm6wCHjECwI45MV9wxAs2ggtfWe2hwlo1ql97jKsju2IcJ+jFSTwBssyYoiWhh7mauQ==",
|
|
2607
2607
|
"dev": true,
|
|
2608
2608
|
"license": "ISC"
|
|
2609
2609
|
},
|
|
@@ -3510,9 +3510,9 @@
|
|
|
3510
3510
|
}
|
|
3511
3511
|
},
|
|
3512
3512
|
"node_modules/react-resizable-panels": {
|
|
3513
|
-
"version": "4.12.
|
|
3514
|
-
"resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-4.12.
|
|
3515
|
-
"integrity": "sha512-
|
|
3513
|
+
"version": "4.12.3",
|
|
3514
|
+
"resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-4.12.3.tgz",
|
|
3515
|
+
"integrity": "sha512-GHMJWnDXui/3RX4bT+cgBP+N3N2nkwcoZATr/2xLFpqQQe7TlBrE0cGM0dUa9ceT7A90tUrSRsiqgRG+g0/2AA==",
|
|
3516
3516
|
"license": "MIT",
|
|
3517
3517
|
"peerDependencies": {
|
|
3518
3518
|
"react": "^18.0.0 || ^19.0.0",
|
|
@@ -3776,9 +3776,9 @@
|
|
|
3776
3776
|
}
|
|
3777
3777
|
},
|
|
3778
3778
|
"node_modules/swr": {
|
|
3779
|
-
"version": "2.5.
|
|
3780
|
-
"resolved": "https://registry.npmjs.org/swr/-/swr-2.5.
|
|
3781
|
-
"integrity": "sha512-
|
|
3779
|
+
"version": "2.5.1",
|
|
3780
|
+
"resolved": "https://registry.npmjs.org/swr/-/swr-2.5.1.tgz",
|
|
3781
|
+
"integrity": "sha512-BRw55e8r0B7SpDN20CAzoQAHl7y1yP7/Zt7oqUjMv0vSt2u2Xnkm88Ws+VypbV9BXHQVuSuyVq7zMjO16wSExw==",
|
|
3782
3782
|
"license": "MIT",
|
|
3783
3783
|
"dependencies": {
|
|
3784
3784
|
"dequal": "^2.0.3",
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { AlertTriangle, CheckCircle2, Info } from "lucide-react";
|
|
3
|
+
import { Section } from "../Section";
|
|
4
|
+
import { Badge, Button, Dialog, Empty, Field, Input, Loading } from "../ui";
|
|
5
|
+
import { UiSelect } from "../UiSelect";
|
|
6
|
+
import { useToast } from "../Toast";
|
|
7
|
+
import { useProfiles, useProfile, useProfileDoctor } from "../../hooks/useProfiles";
|
|
8
|
+
import { ProfilesApi, type ProfileSchemaProp } from "../../lib/api/profiles";
|
|
9
|
+
import { t } from "../../i18n";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Agent profiles — an installable line of work for the super-agent.
|
|
13
|
+
*
|
|
14
|
+
* Not to be confused with the agent's persona (its visible name, under
|
|
15
|
+
* Identity), nor with configuration profiles. With none active, APX behaves
|
|
16
|
+
* exactly as it always has, and the panel says so rather than looking broken.
|
|
17
|
+
*/
|
|
18
|
+
export function ProfilePanel() {
|
|
19
|
+
const toast = useToast();
|
|
20
|
+
const { active, profiles, isLoading, mutate } = useProfiles();
|
|
21
|
+
const [selectedId, setSelectedId] = useState<string | null>(null);
|
|
22
|
+
|
|
23
|
+
const currentId = selectedId ?? active ?? profiles[0]?.id ?? null;
|
|
24
|
+
const { profile, mutate: mutateProfile } = useProfile(currentId);
|
|
25
|
+
const { doctor, mutate: mutateDoctor } = useProfileDoctor(active);
|
|
26
|
+
|
|
27
|
+
const [draft, setDraft] = useState<Record<string, string>>({});
|
|
28
|
+
const [busy, setBusy] = useState(false);
|
|
29
|
+
const [confirmOff, setConfirmOff] = useState(false);
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (!profile) return;
|
|
33
|
+
const next: Record<string, string> = {};
|
|
34
|
+
for (const [k, v] of Object.entries(profile.config || {})) next[k] = String(v ?? "");
|
|
35
|
+
setDraft(next);
|
|
36
|
+
}, [profile?.id, profile?.config]);
|
|
37
|
+
|
|
38
|
+
const refreshAll = async () => {
|
|
39
|
+
await Promise.all([mutate(), mutateProfile(), mutateDoctor()]);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
if (isLoading) return <Loading />;
|
|
43
|
+
|
|
44
|
+
const activate = async (id: string, force: boolean) => {
|
|
45
|
+
setBusy(true);
|
|
46
|
+
try {
|
|
47
|
+
const r = await ProfilesApi.use(id, force);
|
|
48
|
+
for (const w of r.warnings || []) toast.error(w);
|
|
49
|
+
await refreshAll();
|
|
50
|
+
toast.success(t("settings.profile.activated"));
|
|
51
|
+
} catch (e) {
|
|
52
|
+
toast.error((e as Error).message);
|
|
53
|
+
} finally {
|
|
54
|
+
setBusy(false);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const deactivate = async () => {
|
|
59
|
+
setBusy(true);
|
|
60
|
+
try {
|
|
61
|
+
await ProfilesApi.off();
|
|
62
|
+
await refreshAll();
|
|
63
|
+
toast.success(t("settings.profile.deactivated"));
|
|
64
|
+
} catch (e) {
|
|
65
|
+
toast.error((e as Error).message);
|
|
66
|
+
} finally {
|
|
67
|
+
setBusy(false);
|
|
68
|
+
setConfirmOff(false);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const saveConfig = async () => {
|
|
73
|
+
if (!profile) return;
|
|
74
|
+
setBusy(true);
|
|
75
|
+
try {
|
|
76
|
+
const r = await ProfilesApi.setConfig(draft, profile.id);
|
|
77
|
+
await refreshAll();
|
|
78
|
+
const moved = r.routines?.installed?.length ?? 0;
|
|
79
|
+
toast.success(
|
|
80
|
+
moved > 0 ? t("settings.profile.saved_with_routines") : t("settings.profile.saved"),
|
|
81
|
+
);
|
|
82
|
+
} catch (e) {
|
|
83
|
+
toast.error((e as Error).message);
|
|
84
|
+
} finally {
|
|
85
|
+
setBusy(false);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const props: Record<string, ProfileSchemaProp> = profile?.schema?.properties || {};
|
|
90
|
+
const overBudget = !!profile?.budget && !!profile?.tokens && profile.tokens > profile.budget;
|
|
91
|
+
|
|
92
|
+
return (
|
|
93
|
+
<div className="flex flex-col gap-4" data-testid="profile-panel">
|
|
94
|
+
<Section
|
|
95
|
+
title={t("settings.profile.title")}
|
|
96
|
+
description={t("settings.profile.subtitle")}
|
|
97
|
+
>
|
|
98
|
+
{!active ? (
|
|
99
|
+
<div
|
|
100
|
+
data-testid="profile-vanilla-hint"
|
|
101
|
+
className="mb-3 flex items-start gap-2 rounded-md border border-border bg-muted/40 p-3 text-sm"
|
|
102
|
+
>
|
|
103
|
+
<Info size={16} className="mt-0.5 shrink-0 opacity-70" />
|
|
104
|
+
<span>{t("settings.profile.vanilla_hint")}</span>
|
|
105
|
+
</div>
|
|
106
|
+
) : null}
|
|
107
|
+
|
|
108
|
+
{!profiles.length ? (
|
|
109
|
+
<Empty>{t("settings.profile.none_available")}</Empty>
|
|
110
|
+
) : (
|
|
111
|
+
<div className="flex flex-col gap-2">
|
|
112
|
+
{profiles.map((p) => (
|
|
113
|
+
<button
|
|
114
|
+
key={p.id}
|
|
115
|
+
data-testid={`profile-row-${p.id}`}
|
|
116
|
+
type="button"
|
|
117
|
+
onClick={() => setSelectedId(p.id)}
|
|
118
|
+
className={`flex items-start justify-between gap-3 rounded-md border p-3 text-left transition ${
|
|
119
|
+
p.id === currentId ? "border-primary bg-muted/40" : "border-border hover:bg-muted/20"
|
|
120
|
+
}`}
|
|
121
|
+
>
|
|
122
|
+
<span className="min-w-0">
|
|
123
|
+
<span className="flex items-center gap-2">
|
|
124
|
+
<span className="font-medium">{p.name}</span>
|
|
125
|
+
<Badge>{p.source}</Badge>
|
|
126
|
+
{p.active ? <Badge tone="success">{t("settings.profile.active")}</Badge> : null}
|
|
127
|
+
</span>
|
|
128
|
+
{p.description ? (
|
|
129
|
+
<span className="mt-0.5 block text-sm opacity-70">{p.description}</span>
|
|
130
|
+
) : null}
|
|
131
|
+
</span>
|
|
132
|
+
<span className="shrink-0 text-xs opacity-60">{p.version ? `v${p.version}` : ""}</span>
|
|
133
|
+
</button>
|
|
134
|
+
))}
|
|
135
|
+
</div>
|
|
136
|
+
)}
|
|
137
|
+
|
|
138
|
+
{profile ? (
|
|
139
|
+
<div className="mt-4 flex flex-wrap items-center gap-2">
|
|
140
|
+
{profile.active ? (
|
|
141
|
+
<Button variant="destructive" loading={busy} onClick={() => setConfirmOff(true)}>
|
|
142
|
+
{t("settings.profile.deactivate")}
|
|
143
|
+
</Button>
|
|
144
|
+
) : (
|
|
145
|
+
<Button variant="primary" loading={busy} onClick={() => activate(profile.id, !!active)}>
|
|
146
|
+
{active ? t("settings.profile.replace_active") : t("settings.profile.activate")}
|
|
147
|
+
</Button>
|
|
148
|
+
)}
|
|
149
|
+
<span className="text-xs opacity-60">
|
|
150
|
+
{t("settings.profile.token_cost")}: ~{profile.tokens ?? 0}
|
|
151
|
+
{profile.budget ? ` / ${profile.budget}` : ""}
|
|
152
|
+
{overBudget ? ` — ${t("settings.profile.over_budget")}` : ""}
|
|
153
|
+
</span>
|
|
154
|
+
</div>
|
|
155
|
+
) : null}
|
|
156
|
+
</Section>
|
|
157
|
+
|
|
158
|
+
{profile && Object.keys(props).length ? (
|
|
159
|
+
<Section
|
|
160
|
+
title={t("settings.profile.settings_title")}
|
|
161
|
+
description={t("settings.profile.settings_subtitle")}
|
|
162
|
+
>
|
|
163
|
+
<div className="grid grid-cols-2 gap-3">
|
|
164
|
+
{Object.entries(props).map(([key, def]) => (
|
|
165
|
+
<Field key={key} label={def.title || key} hint={def.description}>
|
|
166
|
+
{def.enum ? (
|
|
167
|
+
<UiSelect
|
|
168
|
+
value={draft[key] ?? String(def.default ?? "")}
|
|
169
|
+
onChange={(v) => setDraft({ ...draft, [key]: v })}
|
|
170
|
+
options={def.enum.map((o) => ({ value: String(o), label: String(o) }))}
|
|
171
|
+
/>
|
|
172
|
+
) : (
|
|
173
|
+
<Input
|
|
174
|
+
value={draft[key] ?? ""}
|
|
175
|
+
onChange={(e) => setDraft({ ...draft, [key]: e.target.value })}
|
|
176
|
+
/>
|
|
177
|
+
)}
|
|
178
|
+
</Field>
|
|
179
|
+
))}
|
|
180
|
+
</div>
|
|
181
|
+
<div className="mt-4">
|
|
182
|
+
<Button variant="primary" loading={busy} onClick={saveConfig}>
|
|
183
|
+
{t("common.save")}
|
|
184
|
+
</Button>
|
|
185
|
+
</div>
|
|
186
|
+
</Section>
|
|
187
|
+
) : null}
|
|
188
|
+
|
|
189
|
+
{active && doctor ? (
|
|
190
|
+
<Section title={t("settings.profile.doctor_title")} description={doctor.summary}>
|
|
191
|
+
{!doctor.checks.length ? (
|
|
192
|
+
<div className="flex items-center gap-2 text-sm">
|
|
193
|
+
<CheckCircle2 size={16} className="text-emerald-500" />
|
|
194
|
+
{t("settings.profile.doctor_clean")}
|
|
195
|
+
</div>
|
|
196
|
+
) : (
|
|
197
|
+
<ul className="flex flex-col gap-2">
|
|
198
|
+
{doctor.checks.map((c, i) => (
|
|
199
|
+
<li key={i} className="flex items-start gap-2 text-sm">
|
|
200
|
+
<AlertTriangle
|
|
201
|
+
size={16}
|
|
202
|
+
className={`mt-0.5 shrink-0 ${c.level === "error" ? "text-red-500" : "text-amber-500"}`}
|
|
203
|
+
/>
|
|
204
|
+
<span className="min-w-0">
|
|
205
|
+
<span className="opacity-60">[{c.label}]</span> {c.detail}
|
|
206
|
+
{c.fix ? (
|
|
207
|
+
<code className="mt-1 block rounded bg-muted px-1.5 py-0.5 text-xs">{c.fix}</code>
|
|
208
|
+
) : null}
|
|
209
|
+
</span>
|
|
210
|
+
</li>
|
|
211
|
+
))}
|
|
212
|
+
</ul>
|
|
213
|
+
)}
|
|
214
|
+
</Section>
|
|
215
|
+
) : null}
|
|
216
|
+
|
|
217
|
+
{profile ? (
|
|
218
|
+
<Section
|
|
219
|
+
title={t("settings.profile.preview_title")}
|
|
220
|
+
description={t("settings.profile.preview_subtitle")}
|
|
221
|
+
>
|
|
222
|
+
<pre data-testid="profile-preview" className="max-h-96 overflow-auto whitespace-pre-wrap rounded-md border border-border bg-muted/30 p-3 text-xs leading-relaxed">
|
|
223
|
+
{profile.preview || t("settings.profile.preview_empty")}
|
|
224
|
+
</pre>
|
|
225
|
+
</Section>
|
|
226
|
+
) : null}
|
|
227
|
+
|
|
228
|
+
<Dialog
|
|
229
|
+
open={confirmOff}
|
|
230
|
+
onClose={() => setConfirmOff(false)}
|
|
231
|
+
title={t("settings.profile.deactivate_title")}
|
|
232
|
+
footer={
|
|
233
|
+
<>
|
|
234
|
+
<Button onClick={() => setConfirmOff(false)}>{t("common.cancel")}</Button>
|
|
235
|
+
<Button variant="destructive" loading={busy} onClick={deactivate}>
|
|
236
|
+
{t("settings.profile.deactivate")}
|
|
237
|
+
</Button>
|
|
238
|
+
</>
|
|
239
|
+
}
|
|
240
|
+
>
|
|
241
|
+
{t("settings.profile.deactivate_confirm")}
|
|
242
|
+
</Dialog>
|
|
243
|
+
</div>
|
|
244
|
+
);
|
|
245
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import useSWR from "swr";
|
|
2
|
+
import { ProfilesApi } from "../lib/api/profiles";
|
|
3
|
+
import type { ProfileDetail, ProfileDoctor, ProfileSummary } from "../lib/api/profiles";
|
|
4
|
+
|
|
5
|
+
/** The catalogue of agent profiles, plus which one is active. */
|
|
6
|
+
export function useProfiles() {
|
|
7
|
+
const { data, error, isLoading, mutate } = useSWR<{
|
|
8
|
+
active: string | null;
|
|
9
|
+
profiles: ProfileSummary[];
|
|
10
|
+
}>("/profiles", () => ProfilesApi.list());
|
|
11
|
+
|
|
12
|
+
return {
|
|
13
|
+
active: data?.active ?? null,
|
|
14
|
+
profiles: data?.profiles ?? [],
|
|
15
|
+
error,
|
|
16
|
+
isLoading,
|
|
17
|
+
mutate,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** One profile, including its schema, settings and rendered prompt preview. */
|
|
22
|
+
export function useProfile(id: string | null) {
|
|
23
|
+
const { data, error, isLoading, mutate } = useSWR<ProfileDetail>(
|
|
24
|
+
id ? `/profiles/${id}` : null,
|
|
25
|
+
() => ProfilesApi.get(id as string),
|
|
26
|
+
);
|
|
27
|
+
return { profile: data, error, isLoading, mutate };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Health of the active profile (or a named one). */
|
|
31
|
+
export function useProfileDoctor(id: string | null) {
|
|
32
|
+
const { data, error, isLoading, mutate } = useSWR<ProfileDoctor>(
|
|
33
|
+
id ? `/profiles/doctor?id=${id}` : "/profiles/doctor",
|
|
34
|
+
() => ProfilesApi.doctor(id || undefined),
|
|
35
|
+
);
|
|
36
|
+
return { doctor: data, error, isLoading, mutate };
|
|
37
|
+
}
|
|
@@ -192,12 +192,43 @@ export const en = {
|
|
|
192
192
|
tabs: {
|
|
193
193
|
identity: "Identity",
|
|
194
194
|
super_agent: "Super-agent",
|
|
195
|
+
profile: "Agent profile",
|
|
195
196
|
engines: "Engines & models",
|
|
196
197
|
telegram: "Telegram",
|
|
197
198
|
devices: "Devices",
|
|
198
199
|
advanced: "Advanced",
|
|
199
200
|
},
|
|
200
201
|
|
|
202
|
+
profile: {
|
|
203
|
+
title: "Agent profile",
|
|
204
|
+
subtitle:
|
|
205
|
+
"An installable line of work for the super-agent: what it does with its day and when it speaks to you. Distinct from the agent's name (that lives under Identity).",
|
|
206
|
+
vanilla_hint:
|
|
207
|
+
"No profile is active. APX behaves exactly as it always has — the super-agent prompt is identical to a clean install.",
|
|
208
|
+
none_available: "No profiles available yet.",
|
|
209
|
+
active: "active",
|
|
210
|
+
activate: "Activate",
|
|
211
|
+
replace_active: "Replace the active one",
|
|
212
|
+
deactivate: "Deactivate",
|
|
213
|
+
deactivate_title: "Deactivate the profile?",
|
|
214
|
+
deactivate_confirm:
|
|
215
|
+
"APX goes back to vanilla. The profile's routines are disabled but not deleted, and your settings, tasks and memory are untouched — activating it again restores everything.",
|
|
216
|
+
activated: "Profile active",
|
|
217
|
+
deactivated: "Profile off — APX is back to vanilla",
|
|
218
|
+
token_cost: "Prompt cost",
|
|
219
|
+
over_budget: "over its declared budget",
|
|
220
|
+
settings_title: "Profile settings",
|
|
221
|
+
settings_subtitle:
|
|
222
|
+
"Blank fields fall back to the package default. Changing a time really reschedules the routine.",
|
|
223
|
+
saved: "Settings saved",
|
|
224
|
+
saved_with_routines: "Settings saved and routines rescheduled",
|
|
225
|
+
doctor_title: "Doctor",
|
|
226
|
+
doctor_clean: "All good.",
|
|
227
|
+
preview_title: "Prompt block",
|
|
228
|
+
preview_subtitle: "Exactly what reaches the model, with your values substituted.",
|
|
229
|
+
preview_empty: "(empty)",
|
|
230
|
+
},
|
|
231
|
+
|
|
201
232
|
identity: {
|
|
202
233
|
title: "Identity",
|
|
203
234
|
subtitle: "User data. Agent configuration goes in Super-agent.",
|
|
@@ -193,12 +193,43 @@ export const es = {
|
|
|
193
193
|
tabs: {
|
|
194
194
|
identity: "Identidad",
|
|
195
195
|
super_agent: "Super-agente",
|
|
196
|
+
profile: "Perfil del agente",
|
|
196
197
|
engines: "Engines & modelos",
|
|
197
198
|
telegram: "Telegram",
|
|
198
199
|
devices: "Dispositivos",
|
|
199
200
|
advanced: "Avanzado",
|
|
200
201
|
},
|
|
201
202
|
|
|
203
|
+
profile: {
|
|
204
|
+
title: "Perfil del agente",
|
|
205
|
+
subtitle:
|
|
206
|
+
"Un oficio instalable para el super-agente: qué hace con su día y cuándo te habla. Distinto del nombre del agente (eso está en Identidad).",
|
|
207
|
+
vanilla_hint:
|
|
208
|
+
"No hay ningún perfil activo. APX se comporta exactamente como siempre — el prompt del super-agente es idéntico al de una instalación limpia.",
|
|
209
|
+
none_available: "No hay perfiles disponibles todavía.",
|
|
210
|
+
active: "activo",
|
|
211
|
+
activate: "Activar",
|
|
212
|
+
replace_active: "Reemplazar el activo",
|
|
213
|
+
deactivate: "Desactivar",
|
|
214
|
+
deactivate_title: "¿Desactivar el perfil?",
|
|
215
|
+
deactivate_confirm:
|
|
216
|
+
"APX vuelve a vanilla. Las rutinas del perfil se deshabilitan pero no se borran, y tu configuración, tareas y memoria quedan intactas: volver a activarlo restaura todo.",
|
|
217
|
+
activated: "Perfil activo",
|
|
218
|
+
deactivated: "Perfil desactivado — APX está en vanilla",
|
|
219
|
+
token_cost: "Costo de prompt",
|
|
220
|
+
over_budget: "excede su presupuesto declarado",
|
|
221
|
+
settings_title: "Configuración del perfil",
|
|
222
|
+
settings_subtitle:
|
|
223
|
+
"Los valores en blanco toman el default del paquete. Cambiar un horario reprograma la rutina de verdad.",
|
|
224
|
+
saved: "Configuración guardada",
|
|
225
|
+
saved_with_routines: "Configuración guardada y rutinas reprogramadas",
|
|
226
|
+
doctor_title: "Diagnóstico",
|
|
227
|
+
doctor_clean: "Todo en orden.",
|
|
228
|
+
preview_title: "Bloque de prompt",
|
|
229
|
+
preview_subtitle: "Exactamente lo que recibe el modelo, con tus valores ya sustituidos.",
|
|
230
|
+
preview_empty: "(vacío)",
|
|
231
|
+
},
|
|
232
|
+
|
|
202
233
|
identity: {
|
|
203
234
|
title: "Identidad",
|
|
204
235
|
subtitle: "Datos del usuario. Configuración del agente va en Super-agente.",
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { http } from "../http";
|
|
2
|
+
|
|
3
|
+
/** A property of a profile's config.schema.json (the supported subset). */
|
|
4
|
+
export type ProfileSchemaProp = {
|
|
5
|
+
type?: "string" | "integer" | "number" | "boolean";
|
|
6
|
+
enum?: (string | number | boolean)[];
|
|
7
|
+
default?: string | number | boolean;
|
|
8
|
+
title?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export type ProfileSchema = {
|
|
13
|
+
type?: string;
|
|
14
|
+
properties?: Record<string, ProfileSchemaProp>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type ProfileSummary = {
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
version: string | null;
|
|
21
|
+
description: string;
|
|
22
|
+
author: string | null;
|
|
23
|
+
languages: string[];
|
|
24
|
+
source: "bundled" | "user" | "user-override";
|
|
25
|
+
active: boolean;
|
|
26
|
+
dir: string;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type ProfileDetail = ProfileSummary & {
|
|
30
|
+
provides: Record<string, string[]>;
|
|
31
|
+
requires: Record<string, string[]>;
|
|
32
|
+
schema: ProfileSchema | null;
|
|
33
|
+
defaults: Record<string, unknown>;
|
|
34
|
+
config: Record<string, unknown>;
|
|
35
|
+
budget: number | null;
|
|
36
|
+
tokens: number | null;
|
|
37
|
+
/** The rendered prompt block, exactly as it reaches the model. */
|
|
38
|
+
preview: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type ProfileCheck = {
|
|
42
|
+
level: "error" | "warn";
|
|
43
|
+
label: string;
|
|
44
|
+
detail: string;
|
|
45
|
+
fix: string | null;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export type ProfileDoctor = {
|
|
49
|
+
id: string | null;
|
|
50
|
+
active: boolean;
|
|
51
|
+
ok: boolean;
|
|
52
|
+
tokens?: number;
|
|
53
|
+
budget?: number | null;
|
|
54
|
+
checks: ProfileCheck[];
|
|
55
|
+
summary: string;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export type ProfileRoutineSync = {
|
|
59
|
+
installed: string[];
|
|
60
|
+
skipped: { name: string; reason: string }[];
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const ProfilesApi = {
|
|
64
|
+
list: () => http.get<{ active: string | null; profiles: ProfileSummary[] }>("/profiles"),
|
|
65
|
+
get: (id: string) => http.get<ProfileDetail>(`/profiles/${encodeURIComponent(id)}`),
|
|
66
|
+
doctor: (id?: string) =>
|
|
67
|
+
http.get<ProfileDoctor>(`/profiles/doctor${id ? `?id=${encodeURIComponent(id)}` : ""}`),
|
|
68
|
+
install: (source: string, force = false) =>
|
|
69
|
+
http.post<{ ok: true; profile: ProfileDetail; warnings: string[]; tokens: number }>(
|
|
70
|
+
"/profiles/install",
|
|
71
|
+
{ source, force }
|
|
72
|
+
),
|
|
73
|
+
use: (id: string, force = false) =>
|
|
74
|
+
http.post<{ ok: true; profile: ProfileDetail; routines: ProfileRoutineSync; warnings: string[] }>(
|
|
75
|
+
"/profiles/use",
|
|
76
|
+
{ id, force }
|
|
77
|
+
),
|
|
78
|
+
off: () => http.post<{ ok: true; was: string | null; routines: string[] }>("/profiles/off", {}),
|
|
79
|
+
setConfig: (values: Record<string, unknown>, id?: string) =>
|
|
80
|
+
http.patch<{ ok: true; config: Record<string, unknown>; changed: string[]; routines: ProfileRoutineSync }>(
|
|
81
|
+
"/profiles/config",
|
|
82
|
+
{ values, id }
|
|
83
|
+
),
|
|
84
|
+
uninstall: (id: string) =>
|
|
85
|
+
http.del<{ ok: true; id: string; source: string }>(`/profiles/${encodeURIComponent(id)}`),
|
|
86
|
+
};
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { type ReactElement } from "react";
|
|
2
2
|
import { useLocation, useNavigate } from "react-router-dom";
|
|
3
3
|
import {
|
|
4
|
-
Bot, Cpu, Database, Globe, KeyRound, LayoutGrid, MessageCircle, Mic, Monitor, ScrollText, Send, Smartphone, Sparkles, User,
|
|
4
|
+
Bot, Cpu, Database, Globe, IdCard, KeyRound, LayoutGrid, MessageCircle, Mic, Monitor, ScrollText, Send, Smartphone, Sparkles, User,
|
|
5
5
|
} from "lucide-react";
|
|
6
6
|
import { useNavCollapse, type TabSection } from "../components/common/TabNav";
|
|
7
7
|
import { TabLayout } from "../components/common/TabLayout";
|
|
8
8
|
import { IdentityPanel } from "../components/settings/IdentityPanel";
|
|
9
9
|
import { SuperAgentPanel } from "../components/settings/SuperAgentPanel";
|
|
10
|
+
import { ProfilePanel } from "../components/settings/ProfilePanel";
|
|
10
11
|
import { MemoryPanel } from "../components/settings/MemoryPanel";
|
|
11
12
|
import { SkillsSettings } from "../components/settings/SkillsSettings";
|
|
12
13
|
import { ModelsTab } from "./base/ModelsTab";
|
|
@@ -21,7 +22,7 @@ import { STORAGE } from "../constants";
|
|
|
21
22
|
import { t } from "../i18n";
|
|
22
23
|
|
|
23
24
|
type TabKey =
|
|
24
|
-
| "identity" | "super_agent" | "engines" | "memory" | "skills" | "telegram" | "devices"
|
|
25
|
+
| "identity" | "super_agent" | "profile" | "engines" | "memory" | "skills" | "telegram" | "devices"
|
|
25
26
|
| "voice" | "deck" | "desktop" | "web" | "advanced";
|
|
26
27
|
|
|
27
28
|
const SECTIONS: TabSection[] = [
|
|
@@ -35,6 +36,7 @@ const SECTIONS: TabSection[] = [
|
|
|
35
36
|
title: t("settings.agents_section"),
|
|
36
37
|
items: [
|
|
37
38
|
{ key: "super_agent", label: t("settings.tabs.super_agent"), icon: Bot },
|
|
39
|
+
{ key: "profile", label: t("settings.tabs.profile"), icon: IdCard },
|
|
38
40
|
{ key: "engines", label: t("settings.tabs.engines"), icon: Cpu },
|
|
39
41
|
{ key: "memory", label: "Memory (RAG)", icon: Database },
|
|
40
42
|
{ key: "skills", label: t("skills_page.title"), icon: Sparkles },
|
|
@@ -73,6 +75,7 @@ const WIDE_TABS = new Set<TabKey>(["engines", "telegram", "memory", "skills", "w
|
|
|
73
75
|
const PANELS: Record<TabKey, () => ReactElement> = {
|
|
74
76
|
identity: () => <IdentityPanel />,
|
|
75
77
|
super_agent: () => <SuperAgentPanel />,
|
|
78
|
+
profile: () => <ProfilePanel />,
|
|
76
79
|
engines: () => <ModelsTab />,
|
|
77
80
|
memory: () => <MemoryPanel />,
|
|
78
81
|
skills: () => <SkillsSettings />,
|
|
@@ -113,6 +116,7 @@ function tabFromPath(pathname: string): TabKey {
|
|
|
113
116
|
const raw = pathname.split("/").filter(Boolean)[1] || "identity";
|
|
114
117
|
switch (raw) {
|
|
115
118
|
case "super-agent": return "super_agent";
|
|
119
|
+
case "profile": return "profile";
|
|
116
120
|
case "engines": return "engines";
|
|
117
121
|
case "memory": return "memory";
|
|
118
122
|
case "skills": return "skills";
|