@agentprojectcontext/apx 1.34.0 → 1.35.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/skills/apx/SKILL.md +1 -1
- package/src/core/agent/build-agent-system.js +134 -58
- package/src/core/agent/channels/voice-context.js +4 -4
- package/src/core/agent/prompt-builder.js +176 -123
- package/src/core/agent/prompts/channels/code.md +12 -10
- package/src/core/agent/prompts/channels/desktop.md +5 -32
- package/src/core/agent/prompts/channels/telegram.md +4 -15
- package/src/core/agent/prompts/channels/web_code.md +11 -11
- package/src/core/agent/prompts/core/agent-base.md +24 -0
- package/src/core/agent/prompts/core/project-agent.md +11 -0
- package/src/core/agent/prompts/core/super-agent.md +21 -0
- package/src/core/agent/prompts/discipline/action.md +10 -0
- package/src/core/agent/prompts/discipline/single-segment.md +6 -0
- package/src/core/agent/prompts/discipline/two-segment.md +11 -0
- package/src/core/agent/self-memory.js +43 -1
- package/src/core/agent/skills/index-store.js +307 -0
- package/src/core/agent/skills/index.js +15 -1
- package/src/core/agent/skills/inspector.js +317 -0
- package/src/core/agent/super-agent.js +7 -1
- package/src/core/agent/tools/handlers/_git.js +50 -0
- package/src/core/agent/tools/handlers/git-diff.js +44 -0
- package/src/core/agent/tools/handlers/git-log.js +38 -0
- package/src/core/agent/tools/handlers/git-show.js +34 -0
- package/src/core/agent/tools/handlers/git-status.js +61 -0
- package/src/core/agent/tools/names.js +31 -0
- package/src/core/agent/tools/registry.js +36 -5
- package/src/core/config/index.js +21 -0
- package/src/core/runtime-skills/apx/SKILL.md +27 -39
- package/src/core/runtime-skills/apx-agency-agents/SKILL.md +40 -56
- package/src/core/runtime-skills/apx-agent/SKILL.md +27 -30
- package/src/core/runtime-skills/apx-mcp/SKILL.md +31 -36
- package/src/core/runtime-skills/apx-mcp-builder/SKILL.md +37 -51
- package/src/core/runtime-skills/apx-project/SKILL.md +20 -29
- package/src/core/runtime-skills/apx-routine/SKILL.md +34 -47
- package/src/core/runtime-skills/apx-runtime/SKILL.md +32 -50
- package/src/core/runtime-skills/apx-sessions/SKILL.md +96 -145
- package/src/core/runtime-skills/apx-skill-builder/SKILL.md +53 -77
- package/src/core/runtime-skills/apx-task/SKILL.md +18 -21
- package/src/core/runtime-skills/apx-telegram/SKILL.md +43 -54
- package/src/core/runtime-skills/apx-voice/SKILL.md +36 -56
- package/src/host/daemon/api/skills.js +140 -6
- package/src/host/daemon/api/super-agent.js +56 -1
- package/src/host/daemon/index.js +17 -0
- package/src/interfaces/cli/branding.js +53 -0
- package/src/interfaces/cli/commands/skills.js +254 -0
- package/src/interfaces/cli/index.js +84 -2
- package/src/interfaces/web/dist/assets/index-C0fm31dY.js +618 -0
- package/src/interfaces/web/dist/assets/index-C0fm31dY.js.map +1 -0
- package/src/interfaces/web/dist/assets/index-UcAqlBO6.css +1 -0
- package/src/interfaces/web/dist/index.html +2 -2
- package/src/interfaces/web/src/components/chat/MessageBubble.tsx +21 -1
- package/src/interfaces/web/src/components/settings/MemoryPanel.tsx +68 -0
- package/src/interfaces/web/src/components/settings/SkillsInspectorPanel.tsx +222 -0
- package/src/interfaces/web/src/hooks/useChat.ts +19 -0
- package/src/interfaces/web/src/i18n/en.ts +1 -0
- package/src/interfaces/web/src/i18n/es.ts +1 -0
- package/src/interfaces/web/src/lib/api/skills.ts +70 -0
- package/src/interfaces/web/src/screens/SettingsScreen.tsx +6 -2
- package/src/interfaces/web/src/types/daemon.ts +10 -0
- package/src/core/agent/prompts/action-discipline.md +0 -24
- package/src/core/agent/prompts/super-agent-base.md +0 -42
- package/src/interfaces/web/dist/assets/index-DdmSRtsz.css +0 -1
- package/src/interfaces/web/dist/assets/index-M4FspaCH.js +0 -613
- package/src/interfaces/web/dist/assets/index-M4FspaCH.js.map +0 -1
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import useSWR from "swr";
|
|
3
|
+
import { Sparkles, RefreshCw, Wand2 } from "lucide-react";
|
|
4
|
+
import { Section } from "../Section";
|
|
5
|
+
import { Button, Field, Input, Loading, Badge, Switch } from "../ui";
|
|
6
|
+
import { useToast } from "../Toast";
|
|
7
|
+
import { Skills, type InspectTrace } from "../../lib/api/skills";
|
|
8
|
+
|
|
9
|
+
// Skill Inspector — per-turn skill RAG middleware. When ON, the static
|
|
10
|
+
// "available skills" slug-dump is removed from the agent's system prompt and a
|
|
11
|
+
// local RAG injects, per turn, only the skill(s) the user's message actually
|
|
12
|
+
// needs. This panel toggles the feature, tunes its thresholds, (re)builds the
|
|
13
|
+
// vector index, and offers a live dry-run so you can see what it would surface.
|
|
14
|
+
//
|
|
15
|
+
// Mirrors MemoryPanel (RAG embeddings): same Section/Field/Button idiom. Config
|
|
16
|
+
// persists under config.skills.inspector.* via the inspector PUT endpoint, so
|
|
17
|
+
// no separate global-config patch is needed.
|
|
18
|
+
|
|
19
|
+
// Numeric knobs with human labels + sane ranges. We keep them as plain number
|
|
20
|
+
// inputs (same idiom as the embeddings model fields) rather than sliders so the
|
|
21
|
+
// values are explicit and copy-pasteable.
|
|
22
|
+
const KNOBS: { key: keyof NumericKnobs; label: string; hint: string; step: number; min: number; max: number }[] = [
|
|
23
|
+
{ key: "load_threshold", label: "Umbral de carga", hint: "Similitud mínima para inyectar el CUERPO de la skill (alto = más estricto).", step: 0.01, min: 0, max: 1 },
|
|
24
|
+
{ key: "hint_threshold", label: "Umbral de sugerencia", hint: "Similitud mínima para solo SUGERIR la skill (que el agente la cargue si quiere).", step: 0.01, min: 0, max: 1 },
|
|
25
|
+
{ key: "margin", label: "Margen sobre el 2º", hint: "El top debe superar al segundo por este margen para cargar su cuerpo (evita empates flojos).", step: 0.01, min: 0, max: 1 },
|
|
26
|
+
{ key: "max_loaded", label: "Máx. cuerpos cargados", hint: "Cuántas skills se inyectan completas por turno.", step: 1, min: 0, max: 5 },
|
|
27
|
+
{ key: "max_hints", label: "Máx. sugerencias", hint: "Cuántas skills extra se nombran como sugerencia.", step: 1, min: 0, max: 8 },
|
|
28
|
+
{ key: "prompt_floor", label: "Largo mínimo del prompt", hint: "Mensajes más cortos que esto se ignoran (evita 'ok', 'hola').", step: 1, min: 0, max: 40 },
|
|
29
|
+
{ key: "body_char_cap", label: "Tope de chars del cuerpo", hint: "Recorta cuerpos de skill largos para no inflar el contexto.", step: 500, min: 500, max: 20000 },
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
type NumericKnobs = {
|
|
33
|
+
load_threshold: number; hint_threshold: number; margin: number;
|
|
34
|
+
max_loaded: number; max_hints: number; prompt_floor: number; body_char_cap: number;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export function SkillsInspectorPanel() {
|
|
38
|
+
const toast = useToast();
|
|
39
|
+
const { data, mutate, isLoading } = useSWR("/skills/inspector", () => Skills.inspector());
|
|
40
|
+
const [busy, setBusy] = useState(false);
|
|
41
|
+
const [probe, setProbe] = useState("");
|
|
42
|
+
const [probeResult, setProbeResult] = useState<InspectTrace | null>(null);
|
|
43
|
+
|
|
44
|
+
if (isLoading || !data) return <Loading />;
|
|
45
|
+
|
|
46
|
+
const cfg = data.config;
|
|
47
|
+
const idx = data.index;
|
|
48
|
+
|
|
49
|
+
const apply = async (patch: Record<string, unknown>) => {
|
|
50
|
+
setBusy(true);
|
|
51
|
+
try {
|
|
52
|
+
await Skills.updateInspector(patch);
|
|
53
|
+
await mutate();
|
|
54
|
+
} catch (e) {
|
|
55
|
+
toast.error(`No se pudo guardar: ${(e as Error).message}`);
|
|
56
|
+
} finally {
|
|
57
|
+
setBusy(false);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const runIndex = async (force = false) => {
|
|
62
|
+
setBusy(true);
|
|
63
|
+
try {
|
|
64
|
+
const r = await Skills.index({ force });
|
|
65
|
+
toast.success(
|
|
66
|
+
`Indexado con ${r.embedder} (dim ${r.dim}): +${r.changed.added} ~${r.changed.refreshed} -${r.changed.removed}.`,
|
|
67
|
+
);
|
|
68
|
+
await mutate();
|
|
69
|
+
} catch (e) {
|
|
70
|
+
toast.error(`Falló el index: ${(e as Error).message}`);
|
|
71
|
+
} finally {
|
|
72
|
+
setBusy(false);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const runProbe = async () => {
|
|
77
|
+
if (!probe.trim()) return;
|
|
78
|
+
setBusy(true);
|
|
79
|
+
setProbeResult(null);
|
|
80
|
+
try {
|
|
81
|
+
const r = await Skills.inspect(probe.trim());
|
|
82
|
+
setProbeResult(r.trace);
|
|
83
|
+
} catch (e) {
|
|
84
|
+
toast.error(`Falló el dry-run: ${(e as Error).message}`);
|
|
85
|
+
} finally {
|
|
86
|
+
setBusy(false);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div className="space-y-6">
|
|
92
|
+
<Section
|
|
93
|
+
title="Skill Inspector (RAG por turno)"
|
|
94
|
+
description="Función experimental. Cuando está activa, el agente NO recibe la lista completa de skills en su prompt; en cada mensaje un RAG local decide qué skill(s) cargar — el cuerpo completo si hay match fuerte, una sugerencia si hay match medio, nada si no aplica. Se reevalúa cada turno: una skill que dejó de ser relevante desaparece del contexto."
|
|
95
|
+
>
|
|
96
|
+
<div className="space-y-4">
|
|
97
|
+
<Field
|
|
98
|
+
label="Activar inspector"
|
|
99
|
+
hint="Apagado = comportamiento clásico (lista de slugs + sugerencia pasiva). Encendido = el RAG decide por turno."
|
|
100
|
+
>
|
|
101
|
+
<Switch
|
|
102
|
+
checked={cfg.enabled}
|
|
103
|
+
disabled={busy}
|
|
104
|
+
onChange={(v) => apply({ enabled: v })}
|
|
105
|
+
label={cfg.enabled ? "Encendido" : "Apagado"}
|
|
106
|
+
/>
|
|
107
|
+
</Field>
|
|
108
|
+
|
|
109
|
+
<div className="flex flex-wrap items-center gap-2 pt-1">
|
|
110
|
+
<Badge tone={idx.count > 0 ? "success" : "warning"}>
|
|
111
|
+
Índice: {idx.count} skills
|
|
112
|
+
</Badge>
|
|
113
|
+
<Badge tone="muted">{idx.embedder || "sin indexar"}</Badge>
|
|
114
|
+
{idx.dim ? <Badge tone="muted">dim {idx.dim}</Badge> : null}
|
|
115
|
+
{idx.updated_at ? (
|
|
116
|
+
<span className="text-xs text-muted-foreground">
|
|
117
|
+
actualizado {new Date(idx.updated_at).toLocaleString()}
|
|
118
|
+
</span>
|
|
119
|
+
) : null}
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
<div className="flex flex-wrap items-center gap-3 pt-1">
|
|
123
|
+
<Button variant="secondary" onClick={() => runIndex(false)} loading={busy}>
|
|
124
|
+
<RefreshCw size={14} /> Reindexar
|
|
125
|
+
</Button>
|
|
126
|
+
<Button variant="secondary" onClick={() => runIndex(true)} loading={busy}>
|
|
127
|
+
<RefreshCw size={14} /> Reindexar (forzado)
|
|
128
|
+
</Button>
|
|
129
|
+
<span className="text-xs text-muted-foreground">
|
|
130
|
+
El embedder sale de Memoria (RAG). Local con Ollama, u offline si no hay proveedor.
|
|
131
|
+
</span>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
</Section>
|
|
135
|
+
|
|
136
|
+
<Section
|
|
137
|
+
title="Umbrales y límites"
|
|
138
|
+
description="Ajustá qué tan agresivo es el inspector. Subir los umbrales = menos falsos positivos pero más riesgo de perderse una skill; bajarlos = lo contrario."
|
|
139
|
+
>
|
|
140
|
+
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
|
141
|
+
{KNOBS.map((k) => (
|
|
142
|
+
<Field key={k.key} label={k.label} hint={k.hint}>
|
|
143
|
+
<Input
|
|
144
|
+
type="number"
|
|
145
|
+
step={k.step}
|
|
146
|
+
min={k.min}
|
|
147
|
+
max={k.max}
|
|
148
|
+
defaultValue={String(cfg[k.key])}
|
|
149
|
+
disabled={busy}
|
|
150
|
+
onBlur={(ev) => {
|
|
151
|
+
const n = Number(ev.target.value);
|
|
152
|
+
if (Number.isFinite(n) && n !== cfg[k.key]) apply({ [k.key]: n });
|
|
153
|
+
}}
|
|
154
|
+
className="max-w-[12rem]"
|
|
155
|
+
/>
|
|
156
|
+
</Field>
|
|
157
|
+
))}
|
|
158
|
+
</div>
|
|
159
|
+
</Section>
|
|
160
|
+
|
|
161
|
+
<Section
|
|
162
|
+
title="Probar (dry-run)"
|
|
163
|
+
description="Escribí un mensaje como lo haría un usuario y mirá qué skills cargaría/sugeriría el inspector — sin llamar al modelo. Fuerza el inspector activo aunque esté apagado arriba."
|
|
164
|
+
>
|
|
165
|
+
<div className="space-y-3">
|
|
166
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
167
|
+
<Input
|
|
168
|
+
value={probe}
|
|
169
|
+
placeholder="ej: necesito crear un video promocional con voz en off"
|
|
170
|
+
disabled={busy}
|
|
171
|
+
onChange={(ev) => setProbe(ev.target.value)}
|
|
172
|
+
onKeyDown={(ev) => { if (ev.key === "Enter") runProbe(); }}
|
|
173
|
+
className="max-w-xl flex-1"
|
|
174
|
+
/>
|
|
175
|
+
<Button variant="primary" onClick={runProbe} loading={busy}>
|
|
176
|
+
<Wand2 size={14} /> Probar
|
|
177
|
+
</Button>
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
{probeResult && (
|
|
181
|
+
<div className="rounded-md border border-border/60 bg-muted/30 p-3 text-sm">
|
|
182
|
+
<div className="mb-2 flex flex-wrap items-center gap-2">
|
|
183
|
+
<Sparkles size={14} className="text-muted-foreground" />
|
|
184
|
+
<span className="text-muted-foreground">{probeResult.embedder || "—"}</span>
|
|
185
|
+
{probeResult.jit ? <Badge tone="warning">JIT (índice vacío)</Badge> : null}
|
|
186
|
+
{probeResult.reason && !probeResult.loaded?.length && !probeResult.hinted?.length ? (
|
|
187
|
+
<Badge tone="muted">{probeResult.reason}</Badge>
|
|
188
|
+
) : null}
|
|
189
|
+
</div>
|
|
190
|
+
|
|
191
|
+
{probeResult.loaded?.length ? (
|
|
192
|
+
<div className="mb-1">
|
|
193
|
+
<span className="text-muted-foreground">Cargadas: </span>
|
|
194
|
+
{probeResult.loaded.map((s) => (
|
|
195
|
+
<Badge key={s} tone="success" className="mr-1">{s}</Badge>
|
|
196
|
+
))}
|
|
197
|
+
</div>
|
|
198
|
+
) : null}
|
|
199
|
+
|
|
200
|
+
{probeResult.hinted?.length ? (
|
|
201
|
+
<div className="mb-1">
|
|
202
|
+
<span className="text-muted-foreground">Sugeridas: </span>
|
|
203
|
+
{probeResult.hinted.map((s) => (
|
|
204
|
+
<Badge key={s} tone="info" className="mr-1">{s}</Badge>
|
|
205
|
+
))}
|
|
206
|
+
</div>
|
|
207
|
+
) : null}
|
|
208
|
+
|
|
209
|
+
{probeResult.scored?.length ? (
|
|
210
|
+
<div className="mt-2 space-y-0.5 font-mono text-xs text-muted-foreground">
|
|
211
|
+
{probeResult.scored.map((s) => (
|
|
212
|
+
<div key={s.slug}>{s.sim.toFixed(3)} {s.slug}</div>
|
|
213
|
+
))}
|
|
214
|
+
</div>
|
|
215
|
+
) : null}
|
|
216
|
+
</div>
|
|
217
|
+
)}
|
|
218
|
+
</div>
|
|
219
|
+
</Section>
|
|
220
|
+
</div>
|
|
221
|
+
);
|
|
222
|
+
}
|
|
@@ -32,6 +32,13 @@ export interface ChatMsg {
|
|
|
32
32
|
usage?: ChatUsage;
|
|
33
33
|
/** Operational notes (engine fallbacks, retries, suppressions). */
|
|
34
34
|
notes?: string[];
|
|
35
|
+
/** Skill Inspector decision for this turn (when the feature is on): which
|
|
36
|
+
* skills the per-turn RAG loaded inline vs merely hinted. */
|
|
37
|
+
inspector?: {
|
|
38
|
+
embedder?: string;
|
|
39
|
+
loaded?: string[];
|
|
40
|
+
hinted?: string[];
|
|
41
|
+
};
|
|
35
42
|
}
|
|
36
43
|
|
|
37
44
|
export interface SendOptions {
|
|
@@ -138,6 +145,18 @@ export function applyStreamEvent(turn: ChatMsg, ev: ChatStreamEvent): ChatMsg {
|
|
|
138
145
|
return withNote(`retry (${ev.reason || "?"})`);
|
|
139
146
|
case "tools_suppressed":
|
|
140
147
|
return withNote(`tools suppressed: ${(ev.tools || []).join(", ")}`);
|
|
148
|
+
case "skill_inspector": {
|
|
149
|
+
const insp = ev.inspector;
|
|
150
|
+
if (!insp || (!insp.loaded?.length && !insp.hinted?.length)) return turn;
|
|
151
|
+
return {
|
|
152
|
+
...turn,
|
|
153
|
+
inspector: {
|
|
154
|
+
embedder: insp.embedder,
|
|
155
|
+
loaded: insp.loaded || [],
|
|
156
|
+
hinted: insp.hinted || [],
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
141
160
|
case "assistant_text":
|
|
142
161
|
return ev.text ? { ...turn, parts: [...turn.parts, { kind: "text", text: ev.text }] } : turn;
|
|
143
162
|
case "tool_start":
|
|
@@ -11,6 +11,55 @@ export type SkillsList = {
|
|
|
11
11
|
skills: SkillEntry[];
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
+
export interface InspectorConfig {
|
|
15
|
+
enabled: boolean;
|
|
16
|
+
load_threshold: number;
|
|
17
|
+
hint_threshold: number;
|
|
18
|
+
margin: number;
|
|
19
|
+
max_loaded: number;
|
|
20
|
+
max_hints: number;
|
|
21
|
+
prompt_floor: number;
|
|
22
|
+
body_char_cap: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface IndexStatus {
|
|
26
|
+
count: number;
|
|
27
|
+
embedder: string | null;
|
|
28
|
+
dim: number | null;
|
|
29
|
+
updated_at: string | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface InspectorState {
|
|
33
|
+
config: InspectorConfig;
|
|
34
|
+
defaults: InspectorConfig;
|
|
35
|
+
keys: string[];
|
|
36
|
+
index: IndexStatus;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface IndexResult {
|
|
40
|
+
ok: boolean;
|
|
41
|
+
embedder: string;
|
|
42
|
+
dim: number;
|
|
43
|
+
planned: { missing: number; stale: number; gone: number; total: number };
|
|
44
|
+
changed: { added: number; refreshed: number; removed: number; kept: number };
|
|
45
|
+
index: IndexStatus;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface InspectTrace {
|
|
49
|
+
enabled: boolean;
|
|
50
|
+
reason?: string;
|
|
51
|
+
embedder?: string;
|
|
52
|
+
scored?: { slug: string; sim: number }[];
|
|
53
|
+
loaded?: string[];
|
|
54
|
+
hinted?: string[];
|
|
55
|
+
jit?: boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface InspectResult {
|
|
59
|
+
trace: InspectTrace;
|
|
60
|
+
contextNote: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
14
63
|
export const Skills = {
|
|
15
64
|
/**
|
|
16
65
|
* List installed skills (bundled + user + optional project-scoped). The
|
|
@@ -22,4 +71,25 @@ export const Skills = {
|
|
|
22
71
|
? `/skills?project_path=${encodeURIComponent(projectPath)}`
|
|
23
72
|
: "/skills",
|
|
24
73
|
),
|
|
74
|
+
|
|
75
|
+
/** Skill Inspector config + index status. */
|
|
76
|
+
inspector: () => http.get<InspectorState>("/skills/inspector"),
|
|
77
|
+
|
|
78
|
+
/** Patch inspector config (toggle / tune thresholds). */
|
|
79
|
+
updateInspector: (patch: Partial<InspectorConfig>) =>
|
|
80
|
+
http.put<{ ok: boolean; config: InspectorConfig; index: IndexStatus }>(
|
|
81
|
+
"/skills/inspector",
|
|
82
|
+
patch,
|
|
83
|
+
),
|
|
84
|
+
|
|
85
|
+
/** (Re)build the inspector vector index. */
|
|
86
|
+
index: (body: { project_path?: string; force?: boolean } = {}) =>
|
|
87
|
+
http.post<IndexResult>("/skills/index", body),
|
|
88
|
+
|
|
89
|
+
/** Dry-run the inspector for a prompt (forces enabled). */
|
|
90
|
+
inspect: (prompt: string, projectPath?: string) =>
|
|
91
|
+
http.post<InspectResult>("/skills/inspect", {
|
|
92
|
+
prompt,
|
|
93
|
+
project_path: projectPath,
|
|
94
|
+
}),
|
|
25
95
|
};
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { type ReactElement } from "react";
|
|
2
2
|
import { useLocation, useNavigate } from "react-router-dom";
|
|
3
3
|
import {
|
|
4
|
-
Bot, Cpu, Database, KeyRound, MessageCircle, Palette, ScrollText, Send, Smartphone, User,
|
|
4
|
+
Bot, Cpu, Database, KeyRound, MessageCircle, Palette, 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
10
|
import { MemoryPanel } from "../components/settings/MemoryPanel";
|
|
11
|
+
import { SkillsInspectorPanel } from "../components/settings/SkillsInspectorPanel";
|
|
11
12
|
import { ModelsTab } from "./base/ModelsTab";
|
|
12
13
|
import { TelegramSettingsTabs } from "../components/settings/TelegramSettingsTabs";
|
|
13
14
|
import { DevicesPanel } from "../components/settings/DevicesPanel";
|
|
@@ -17,7 +18,7 @@ import { STORAGE } from "../constants";
|
|
|
17
18
|
import { t } from "../i18n";
|
|
18
19
|
|
|
19
20
|
type TabKey =
|
|
20
|
-
| "identity" | "super_agent" | "engines" | "memory" | "telegram" | "devices" | "appearance" | "advanced";
|
|
21
|
+
| "identity" | "super_agent" | "engines" | "memory" | "skills" | "telegram" | "devices" | "appearance" | "advanced";
|
|
21
22
|
|
|
22
23
|
const SECTIONS: TabSection[] = [
|
|
23
24
|
{
|
|
@@ -33,6 +34,7 @@ const SECTIONS: TabSection[] = [
|
|
|
33
34
|
{ key: "super_agent", label: t("settings.tabs.super_agent"), icon: Bot },
|
|
34
35
|
{ key: "engines", label: t("settings.tabs.engines"), icon: Cpu },
|
|
35
36
|
{ key: "memory", label: "Memoria (RAG)", icon: Database },
|
|
37
|
+
{ key: "skills", label: "Skills (RAG)", icon: Sparkles },
|
|
36
38
|
],
|
|
37
39
|
},
|
|
38
40
|
{
|
|
@@ -59,6 +61,7 @@ const PANELS: Record<TabKey, () => ReactElement> = {
|
|
|
59
61
|
super_agent: () => <SuperAgentPanel />,
|
|
60
62
|
engines: () => <ModelsTab />,
|
|
61
63
|
memory: () => <MemoryPanel />,
|
|
64
|
+
skills: () => <SkillsInspectorPanel />,
|
|
62
65
|
telegram: () => <TelegramSettingsTabs />,
|
|
63
66
|
devices: () => <DevicesPanel />,
|
|
64
67
|
appearance: () => <AppearancePanel />,
|
|
@@ -95,6 +98,7 @@ function tabFromPath(pathname: string): TabKey {
|
|
|
95
98
|
case "super-agent": return "super_agent";
|
|
96
99
|
case "engines": return "engines";
|
|
97
100
|
case "memory": return "memory";
|
|
101
|
+
case "skills": return "skills";
|
|
98
102
|
case "telegram": return "telegram";
|
|
99
103
|
case "devices": return "devices";
|
|
100
104
|
case "appearance": return "appearance";
|
|
@@ -284,6 +284,16 @@ export interface ChatStreamEvent {
|
|
|
284
284
|
streak?: number;
|
|
285
285
|
// tool_start / tool_result / tool_deduped
|
|
286
286
|
trace?: ToolTrace;
|
|
287
|
+
// skill_inspector: which skills the per-turn RAG loaded/hinted this turn
|
|
288
|
+
inspector?: {
|
|
289
|
+
enabled?: boolean;
|
|
290
|
+
reason?: string;
|
|
291
|
+
embedder?: string;
|
|
292
|
+
scored?: { slug: string; sim: number }[];
|
|
293
|
+
loaded?: string[];
|
|
294
|
+
hinted?: string[];
|
|
295
|
+
jit?: boolean;
|
|
296
|
+
};
|
|
287
297
|
// final
|
|
288
298
|
result?: {
|
|
289
299
|
text?: string;
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
## Action Discipline (mandatory)
|
|
2
|
-
- NEVER acknowledge an action without executing it in the same turn. If you are going to do something, call the tool FIRST, then report the result.
|
|
3
|
-
- NEVER use empty acknowledgments like "Ok", "Got it", "Sure", "Understood", "On it", "Give me a moment", "I'll do that now" as standalone responses when a tool call is expected. These are invalid responses.
|
|
4
|
-
- Action first, report after. Produce the tool call in the same response as your acknowledgment.
|
|
5
|
-
- If you cannot execute the action (missing permission, unclear params, tool not available), explain WHY — do not promise and disappear.
|
|
6
|
-
- If the user asks you to do multiple things, do them all in the same turn using sequential tool calls if needed.
|
|
7
|
-
|
|
8
|
-
## Two-segment turns with tools — intro short, answer substantive (mandatory)
|
|
9
|
-
A turn that calls one or more tools produces TWO text segments shown to the user:
|
|
10
|
-
|
|
11
|
-
1. **Pre-tool intro** — a SHORT, NATURAL filler in the user's language BEFORE the tool runs. 2 to 8 words. NEVER contains the answer / data / acknowledgment. Examples: "Dale, voy a anotar eso", "Reviso eso", "Un momento, busco", "Going to remember that".
|
|
12
|
-
2. **Post-tool answer** — the SUBSTANTIVE result AFTER the tool returns. Carries the data, the confirmation, or the next question. Examples: "Listo, anoté que sos Tech Lead en Bytetravel.", "Encontré 3 routines activas: …".
|
|
13
|
-
|
|
14
|
-
Hard rules:
|
|
15
|
-
- The pre-tool intro NEVER includes the substantive content. Do NOT say "Anoté que sos Tech Lead" BEFORE the remember tool runs — at that point the tool hasn't executed yet.
|
|
16
|
-
- The post-tool answer NEVER restates what the intro already said. They serve different purposes: the intro is filler, the answer is the result.
|
|
17
|
-
- Greet AT MOST ONCE per turn. If you already opened with "hola" in the intro, the answer starts with the actual result, no greeting.
|
|
18
|
-
- A turn with NO tool calls produces a single segment — go straight to the answer, no filler intro needed.
|
|
19
|
-
- A simple chit-chat reply (no tool) is one segment: the reply itself.
|
|
20
|
-
|
|
21
|
-
## Chit-chat & greetings (only path out of a forced tool turn)
|
|
22
|
-
- If the user is just greeting, chatting, or thanking you with NO actionable request ("hola", "hi", "buenas", "gracias", "👍", "ok"), you must STILL satisfy the tool-choice contract: call `finish` with a brief friendly reply in the user's language. Do NOT call any other tool just because tools are available — `finish` is the correct tool for chit-chat.
|
|
23
|
-
- A greeting that piggybacks a real request ("hola, listame las rutinas") is NOT chit-chat — handle the request normally with the right tool.
|
|
24
|
-
- When in doubt between chit-chat and a vague request, ask ONE short clarifying question via `finish` — never invent a topic or run an unrelated tool to "be useful".
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
# Role
|
|
2
|
-
You are **APX itself**, the default daemon-level action agent for Agent Project Context (APC). In code/CLI this role is the *super-agent* — that's the **mode** ("the APX agent talking when no project agent was named"), not your name. Your real name, owner, language, timezone and locale come from the **User & identity** section below; never call yourself "super-agent" to the user.
|
|
3
|
-
|
|
4
|
-
You are an **action agent**, not a chatbot or code explainer: you USE TOOLS to do real things on the user's system. APX (the daemon, config, projects, agents, sessions, message logs) is your own body and toolbox — speak in the first person ("my sessions", "let me check", "I ran…"), never as a third party ("APX can…"). Assume you can do what's asked and reach for the right tool; don't hedge about limits until a tool actually fails.
|
|
5
|
-
|
|
6
|
-
If a message starts with "[audio]", the rest is a speech transcription — treat it as the user's normal message.
|
|
7
|
-
|
|
8
|
-
# Tools
|
|
9
|
-
The runtime sends your exact callable tool schemas on every turn — that is your real capability list. Use them; never recite a tool catalog at the user. On lightweight channels only a core subset is sent; the rest still exist — pull them in by acting, or via load_skill. For factual/inventory questions, CALL a tool first; don't ask the user to specify a project unless the tool fails.
|
|
10
|
-
|
|
11
|
-
You also ship **apx-\* skills** with the exact syntax for multi-step APX operations (routines, projects, MCPs, agents, telegram, runtimes, tasks, voice). When the user needs precise syntax/behavior for one of these, `load_skill` the matching one BEFORE running commands — don't guess flags or invent cron grammar, ports, or paths.
|
|
12
|
-
|
|
13
|
-
# What you must NOT do
|
|
14
|
-
- Don't explain code or describe what a tool *would* do — call it and report the result.
|
|
15
|
-
- Don't give AI disclaimers. You DO have history and memory (see Memory below) — never say "I have no memory of past conversations".
|
|
16
|
-
- Don't tell the user to run an `apx …` command to get info you can fetch with a tool. You operate APX; run the tool yourself. (Only mention CLI when they explicitly ask "how do I do this from the terminal?")
|
|
17
|
-
- Don't end with "give me a second" / "I'll try later", and don't reply with a bare "ok"/"checking"/"one moment". Every message carries a result, finding, or one concrete question.
|
|
18
|
-
- If a message is short or ambiguous, ask ONE short clarifying question in the user's language — don't invent a topic.
|
|
19
|
-
|
|
20
|
-
# Memory & history
|
|
21
|
-
You have durable memory across all channels — never deny it. Two sources:
|
|
22
|
-
- **Sessions & chat logs**: when asked what you worked on, about a "previous/last session", or "what we talked about", call `search_sessions` (defaults to your own apx sessions — pass `engine` only when the user names claude/codex, `all:true` only when they want every engine; pass `id` to open a transcript) and/or `search_messages`. Answer didactically in prose (in the user's language — e.g. "last time we worked on X and Y"), not as a raw list of titles. If your sessions are thin, say so and offer to look across engines — never conclude you "have no history".
|
|
23
|
-
- **Your notebook (self-memory)**: `~/.apx/memory.md`, a bounded slice injected above (as "# Your notebook" or folded into "# Relevant memory"). At the end of any turn where something durable happened (a decision, a completed task, an agreed fact), save the gist with `remember` so your other channels know it too. Keep notes to one self-contained sentence. Use `create_task` for one-off TODOs and project-agent memory for project-scoped facts. When a "# Relevant memory" block is present, treat its bullets as known facts; if a fresh chat opens and something there is still open, bring it up naturally in the user's language (e.g. "yesterday we were on X — shall we continue?") — weave in only what's relevant, don't dump the block.
|
|
24
|
-
|
|
25
|
-
# How you operate
|
|
26
|
-
- APC projects live anywhere on disk; the default workspace is APX home, not a user repo. Registered projects appear below as a tiny index — call tools for details.
|
|
27
|
-
- Permission mode is injected in its own section. total = execute freely. automatico = read/list/safe read-only shell (apx --help, ls, find, rg, grep, docker ps) run directly; destructive/external/runtime/MCP/outbound/config/filesystem-mutating actions need explicit confirmation. permiso = only allowed_tools run directly; the rest need confirmation. When a tool schema has `confirmed`, set confirmed=true only after explicit user confirmation for that exact action.
|
|
28
|
-
- Filesystem search: use targeted tools (find, fd, rg, grep -rn, concrete globs) — never `ls -R` on large trees.
|
|
29
|
-
- Register projects with add_project only — never hand-write AGENTS.md or .apc/project.json via shell.
|
|
30
|
-
- Never paste base64/data-URIs in message text — send images/audio/files via send_telegram media params or paths.
|
|
31
|
-
|
|
32
|
-
# Hard rules
|
|
33
|
-
1. NEVER invent project names, agent slugs, model ids, MCP names, or paths. Look them up via list_* first.
|
|
34
|
-
2. Inventory requests with no project mean **all projects** — call the tool with no project argument; never answer "specify a project" when a global list tool exists.
|
|
35
|
-
3. If a tool errors, retry with different arguments before asking the user.
|
|
36
|
-
4. Write in the user's configured language (see User & identity). Follow the Channel context formatting rules when present. Stay concise unless asked for detail.
|
|
37
|
-
5. Prior turns disambiguate references only ("the first one" → earlier mention); re-call tools for any factual data — past turns are not a cache. /reset or /new means answer fresh.
|
|
38
|
-
6. **SELF-RUN**: "yourself"/"same"/"default"/no agent named → act as APX; don't call list_agents, don't pass an agent argument. **DELEGATE**: a named APC agent → call_agent. **DISPATCH**: an external runtime (claude-code, codex…) → call_runtime, passing the named agent or omitting it to run as yourself. **VAULT**: new agent from a template → list_vault_agents, then import_agent if there's a match.
|
|
39
|
-
7. **Projects**: no project named → use the default workspace. EXCEPTION — routines and project-scoped work need a REAL project: if asked to create a routine (or agent/memory) without a named project, ask which one. Never create routines in the default/id=0 workspace.
|
|
40
|
-
8. **Identity**: user changes their name/your name/personality → set_identity, then confirm.
|
|
41
|
-
9. **Skills on demand**: load_skill only when the user needs exact syntax/behavior matching a skill description (pass project_path from CWD when present) — not for unrelated questions.
|
|
42
|
-
10. **CWD**: when Channel context includes `CWD: <path>`, "this directory/project/here" means that path — use it directly, don't ask.
|