@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
|
@@ -1,44 +1,39 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: apx-voice
|
|
3
3
|
scope: optional
|
|
4
|
-
description:
|
|
4
|
+
description: APX TTS — Piper (local), ElevenLabs/OpenAI/Gemini (cloud), unified /voice/turn channel, `apx voice` CLI. Load when the user wants to speak with APX, configure a voice engine, or troubleshoot silent output.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# apx-voice
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
TTS facade in `core/voice/` with five engines. STT lives separately in `host/daemon/transcription.js` (Whisper). The "voice channel" combines both for mic→agent→speaker.
|
|
10
10
|
|
|
11
11
|
## Engines
|
|
12
12
|
|
|
13
|
-
| id | Local? | Needs key? |
|
|
14
|
-
|
|
15
|
-
| `piper` | yes | no |
|
|
16
|
-
| `elevenlabs` | no | yes | Excellent
|
|
17
|
-
| `openai` | no | yes |
|
|
18
|
-
| `gemini` | no | yes |
|
|
19
|
-
| `mock` | yes | no | Silent
|
|
13
|
+
| id | Local? | Needs key? | Notes |
|
|
14
|
+
|---|---|---|---|
|
|
15
|
+
| `piper` | yes | no | Local, offline. Requires `piper` CLI + `.onnx` model. es_AR-daniela-high recommended. |
|
|
16
|
+
| `elevenlabs` | no | yes | Excellent. Free tier 10k chars/mo. `eleven_multilingual_v2`. |
|
|
17
|
+
| `openai` | no | yes | Reuses `engines.openai.api_key`. `tts-1`. |
|
|
18
|
+
| `gemini` | no | yes | Returns raw L16 PCM — APX wraps in WAV automatically. |
|
|
19
|
+
| `mock` | yes | no | Silent WAV; placeholder for tests. |
|
|
20
20
|
|
|
21
|
-
`auto`
|
|
21
|
+
`auto` probes: piper → elevenlabs → openai → gemini → mock.
|
|
22
22
|
|
|
23
23
|
## Concrete CLI calls
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
|
-
#
|
|
27
|
-
apx voice providers
|
|
28
|
-
|
|
29
|
-
# Synthesize and play
|
|
26
|
+
apx voice providers # what's configured + available
|
|
30
27
|
apx voice say "Hello from APX" --provider piper
|
|
31
|
-
apx voice say "Hello from APX" --provider gemini
|
|
32
|
-
apx voice say "
|
|
33
|
-
apx voice say "..." --no-play # generate WAV, don't play
|
|
28
|
+
apx voice say "Hello from APX" --provider gemini --voice Aoede
|
|
29
|
+
apx voice say "..." --no-play # generate WAV, don't play
|
|
34
30
|
|
|
35
|
-
#
|
|
36
|
-
apx voice listen
|
|
37
|
-
apx voice listen --
|
|
38
|
-
apx voice listen --provider <id> # override the STT/transcription provider
|
|
31
|
+
apx voice listen # mic → STT, records until silence (sox) or Ctrl+C
|
|
32
|
+
apx voice listen --seconds 5 # fixed-duration capture
|
|
33
|
+
apx voice listen --provider <id> # override STT provider
|
|
39
34
|
```
|
|
40
35
|
|
|
41
|
-
Playback uses system binaries (`afplay`, `paplay`, `aplay`, `play`, `ffplay`)
|
|
36
|
+
Playback uses system binaries (`afplay`, `paplay`, `aplay`, `play`, `ffplay`). If none found, you get the file path and no playback.
|
|
42
37
|
|
|
43
38
|
## Configuration
|
|
44
39
|
|
|
@@ -60,9 +55,7 @@ Playback uses system binaries (`afplay`, `paplay`, `aplay`, `play`, `ffplay`)
|
|
|
60
55
|
|
|
61
56
|
`apx config set voice.tts.provider <name>` to switch.
|
|
62
57
|
|
|
63
|
-
## Quick setup
|
|
64
|
-
|
|
65
|
-
### Piper local (recommended, no internet)
|
|
58
|
+
## Quick setup: Piper local (recommended, no internet)
|
|
66
59
|
|
|
67
60
|
```bash
|
|
68
61
|
# 1. Install binary (macOS arm64)
|
|
@@ -70,21 +63,18 @@ curl -L https://github.com/rhasspy/piper/releases/latest/download/piper_macos_aa
|
|
|
70
63
|
-o /tmp/piper.tar.gz
|
|
71
64
|
sudo tar xzf /tmp/piper.tar.gz -C /usr/local/bin --strip-components=1
|
|
72
65
|
|
|
73
|
-
# 2. Voice model (es_AR
|
|
74
|
-
mkdir -p ~/.apx/voices
|
|
75
|
-
cd ~/.apx/voices
|
|
66
|
+
# 2. Voice model (es_AR, "daniela")
|
|
67
|
+
mkdir -p ~/.apx/voices && cd ~/.apx/voices
|
|
76
68
|
curl -LO https://huggingface.co/rhasspy/piper-voices/resolve/main/es/es_AR/daniela/high/es_AR-daniela-high.onnx
|
|
77
69
|
curl -LO https://huggingface.co/rhasspy/piper-voices/resolve/main/es/es_AR/daniela/high/es_AR-daniela-high.onnx.json
|
|
78
70
|
|
|
79
|
-
# 3.
|
|
71
|
+
# 3. Configure + test
|
|
80
72
|
apx config set voice.tts.provider piper
|
|
81
73
|
apx config set voice.tts.piper.model "$HOME/.apx/voices/es_AR-daniela-high.onnx"
|
|
82
|
-
|
|
83
|
-
# 4. Test
|
|
84
|
-
apx voice say "hola, soy APX" --provider piper # Spanish text exercises the es_AR voice
|
|
74
|
+
apx voice say "hola, soy APX" --provider piper
|
|
85
75
|
```
|
|
86
76
|
|
|
87
|
-
|
|
77
|
+
## Quick setup: Gemini cloud
|
|
88
78
|
|
|
89
79
|
```bash
|
|
90
80
|
apx config set voice.tts.provider gemini
|
|
@@ -93,45 +83,35 @@ apx config set engines.gemini.api_key '<GEMINI_KEY>' # reuse for LLM router
|
|
|
93
83
|
apx voice say "Hello from APX" --provider gemini
|
|
94
84
|
```
|
|
95
85
|
|
|
96
|
-
##
|
|
86
|
+
## Unified voice channel
|
|
97
87
|
|
|
98
|
-
`POST /voice/turn` is one round-trip: send audio (or text), get back `{ user_text, reply_text, reply_audio_path }`. STT in, agent loop, TTS out.
|
|
88
|
+
`POST /voice/turn` is one round-trip: send audio (or text), get back `{ user_text, reply_text, reply_audio_path }`. STT in, agent loop, TTS out. For overlay and future "voice room" clients.
|
|
99
89
|
|
|
100
90
|
```bash
|
|
101
|
-
# Drive from curl with already-transcribed text (skip STT)
|
|
102
91
|
curl -X POST http://127.0.0.1:7430/voice/turn \
|
|
103
92
|
-H "Authorization: Bearer $(cat ~/.apx/daemon.token)" \
|
|
104
93
|
-H "Content-Type: application/json" \
|
|
105
94
|
-d '{"text":"Hello APX","channel":"voice"}'
|
|
106
95
|
```
|
|
107
96
|
|
|
108
|
-
Telegram voice messages and
|
|
97
|
+
Telegram voice messages and overlay mascot still have their own STT pipelines — they don't go through `/voice/turn` yet.
|
|
109
98
|
|
|
110
99
|
## Anti-examples
|
|
111
100
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
# If only mock is "available", configure a real provider.
|
|
116
|
-
|
|
117
|
-
# DON'T set voice.tts.provider to a provider with no key.
|
|
118
|
-
# It will fall through `auto` to the next, but that's not what you asked for.
|
|
119
|
-
|
|
120
|
-
# DON'T expect Gemini TTS to give you an MP3.
|
|
121
|
-
# Today it returns raw L16 PCM. APX wraps it in a 44-byte WAV header so afplay
|
|
122
|
-
# accepts it. Files are .wav, mime "audio/wav". If you need MP3, convert with ffmpeg.
|
|
123
|
-
```
|
|
101
|
+
- DON'T trust `apx voice providers` saying "mock available" as green light — mock is silence. Configure a real provider.
|
|
102
|
+
- DON'T set `voice.tts.provider` to a provider with no key. It falls through `auto` to the next, but that's not what you asked.
|
|
103
|
+
- DON'T expect Gemini TTS to return MP3 — it returns raw L16 PCM; APX wraps in WAV. Files are `.wav`, mime `audio/wav`. Convert with ffmpeg if you need MP3.
|
|
124
104
|
|
|
125
105
|
## Troubleshooting silent output
|
|
126
106
|
|
|
127
107
|
1. `apx voice providers` — what's actually available?
|
|
128
|
-
2. `apx voice say "test" --provider <engine> --no-play` —
|
|
129
|
-
3. `file <path>` —
|
|
130
|
-
4. `afplay <path>`
|
|
131
|
-
5. If 3 fails for Gemini, you may be on
|
|
108
|
+
2. `apx voice say "test" --provider <engine> --no-play` — file exists?
|
|
109
|
+
3. `file <path>` — valid container? Gemini output should be `RIFF WAVE Microsoft PCM`.
|
|
110
|
+
4. `afplay <path>` — does the OS player open it?
|
|
111
|
+
5. If 3 fails for Gemini, you may be on APX before the PCM-wrap fix (commit `ba5c416`+).
|
|
132
112
|
|
|
133
113
|
## Don't
|
|
134
114
|
|
|
135
|
-
-
|
|
136
|
-
-
|
|
137
|
-
-
|
|
115
|
+
- Paste base64 audio into chat. Use file paths or `send_voice` / `send_audio`.
|
|
116
|
+
- Switch providers mid-routine without testing — quality varies a lot across Piper voices and cloud engines.
|
|
117
|
+
- Expect TTS streaming yet — `apx voice say` returns a complete file. `/tts/stream` is open work.
|
|
@@ -1,12 +1,42 @@
|
|
|
1
|
-
//
|
|
2
|
-
// future palettes). Same data backing `list_skills` tool, but here without
|
|
3
|
-
// auth-binding to a project — anyone with a valid daemon token can ask
|
|
4
|
-
// "which skills are around right now?".
|
|
1
|
+
// `/skills` listing + Skill Inspector control surface for UI clients.
|
|
5
2
|
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
3
|
+
// GET /skills catalog (slug + condensed description)
|
|
4
|
+
// GET /skills/inspector inspector config + index status
|
|
5
|
+
// PUT /skills/inspector toggle / tune inspector config
|
|
6
|
+
// POST /skills/index (re)build the inspector vector index
|
|
7
|
+
// POST /skills/inspect dry-run the inspector for a prompt
|
|
8
|
+
//
|
|
9
|
+
// The listing is the same data backing `list_skills` (no auth-binding to a
|
|
10
|
+
// project). The inspector routes mirror /embeddings/* so the web admin can
|
|
11
|
+
// configure the skill RAG exactly like it configures the memory RAG.
|
|
8
12
|
import { listSkills } from "#core/agent/skills/loader.js";
|
|
9
13
|
import { condenseSkillDescription } from "#core/agent/skills/catalog.js";
|
|
14
|
+
import {
|
|
15
|
+
inspectPromptForSkills,
|
|
16
|
+
INSPECTOR_DEFAULTS,
|
|
17
|
+
} from "#core/agent/skills/inspector.js";
|
|
18
|
+
import {
|
|
19
|
+
ensureIndex,
|
|
20
|
+
planIndex,
|
|
21
|
+
readIndex,
|
|
22
|
+
} from "#core/agent/skills/index-store.js";
|
|
23
|
+
import { readConfig, writeConfig } from "#core/config/index.js";
|
|
24
|
+
|
|
25
|
+
const KNOWN_KEYS = Object.keys(INSPECTOR_DEFAULTS);
|
|
26
|
+
|
|
27
|
+
function mergedInspectorConfig(cfg) {
|
|
28
|
+
return { ...INSPECTOR_DEFAULTS, ...(cfg?.skills?.inspector || {}) };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function indexStatus() {
|
|
32
|
+
const idx = readIndex();
|
|
33
|
+
return {
|
|
34
|
+
count: Object.keys(idx.items || {}).length,
|
|
35
|
+
embedder: idx.embedder || null,
|
|
36
|
+
dim: idx.dim || null,
|
|
37
|
+
updated_at: idx.updated_at || null,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
10
40
|
|
|
11
41
|
export function register(app /*, ctx */) {
|
|
12
42
|
app.get("/skills", (req, res) => {
|
|
@@ -27,4 +57,108 @@ export function register(app /*, ctx */) {
|
|
|
27
57
|
res.status(500).json({ error: e.message });
|
|
28
58
|
}
|
|
29
59
|
});
|
|
60
|
+
|
|
61
|
+
// ---- Inspector config + status -----------------------------------------
|
|
62
|
+
|
|
63
|
+
app.get("/skills/inspector", (_req, res) => {
|
|
64
|
+
try {
|
|
65
|
+
const cfg = readConfig();
|
|
66
|
+
res.json({
|
|
67
|
+
config: mergedInspectorConfig(cfg),
|
|
68
|
+
defaults: INSPECTOR_DEFAULTS,
|
|
69
|
+
keys: KNOWN_KEYS,
|
|
70
|
+
index: indexStatus(),
|
|
71
|
+
});
|
|
72
|
+
} catch (e) {
|
|
73
|
+
res.status(500).json({ error: e.message });
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
app.put("/skills/inspector", (req, res) => {
|
|
78
|
+
try {
|
|
79
|
+
const patch = req.body || {};
|
|
80
|
+
const cfg = readConfig();
|
|
81
|
+
cfg.skills = cfg.skills || {};
|
|
82
|
+
const current = mergedInspectorConfig(cfg);
|
|
83
|
+
const next = { ...current };
|
|
84
|
+
|
|
85
|
+
for (const [k, v] of Object.entries(patch)) {
|
|
86
|
+
if (!KNOWN_KEYS.includes(k)) continue;
|
|
87
|
+
const def = INSPECTOR_DEFAULTS[k];
|
|
88
|
+
if (typeof def === "boolean") next[k] = !!v;
|
|
89
|
+
else if (typeof def === "number") {
|
|
90
|
+
const n = Number(v);
|
|
91
|
+
if (Number.isFinite(n)) next[k] = n;
|
|
92
|
+
} else {
|
|
93
|
+
next[k] = v;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
cfg.skills.inspector = next;
|
|
98
|
+
writeConfig(cfg);
|
|
99
|
+
res.json({ ok: true, config: next, index: indexStatus() });
|
|
100
|
+
} catch (e) {
|
|
101
|
+
res.status(500).json({ error: e.message });
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// ---- Index build --------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
app.post("/skills/index", async (req, res) => {
|
|
108
|
+
try {
|
|
109
|
+
const { project_path, force } = req.body || {};
|
|
110
|
+
const cfg = readConfig();
|
|
111
|
+
const plan = planIndex({ projectPath: project_path });
|
|
112
|
+
const out = await ensureIndex({
|
|
113
|
+
projectPath: project_path,
|
|
114
|
+
embedOpts: { globalConfig: cfg },
|
|
115
|
+
force: !!force,
|
|
116
|
+
});
|
|
117
|
+
res.json({
|
|
118
|
+
ok: true,
|
|
119
|
+
embedder: out.embedder,
|
|
120
|
+
dim: out.dim,
|
|
121
|
+
planned: {
|
|
122
|
+
missing: plan.missing.length,
|
|
123
|
+
stale: plan.stale.length,
|
|
124
|
+
gone: plan.gone.length,
|
|
125
|
+
total: plan.total,
|
|
126
|
+
},
|
|
127
|
+
changed: {
|
|
128
|
+
added: out.changed.added.length,
|
|
129
|
+
refreshed: out.changed.refreshed.length,
|
|
130
|
+
removed: out.changed.removed.length,
|
|
131
|
+
kept: out.changed.kept.length,
|
|
132
|
+
},
|
|
133
|
+
index: indexStatus(),
|
|
134
|
+
});
|
|
135
|
+
} catch (e) {
|
|
136
|
+
res.status(500).json({ error: e.message });
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// ---- Dry-run ------------------------------------------------------------
|
|
141
|
+
|
|
142
|
+
app.post("/skills/inspect", async (req, res) => {
|
|
143
|
+
try {
|
|
144
|
+
const { prompt, project_path } = req.body || {};
|
|
145
|
+
if (!prompt || typeof prompt !== "string") {
|
|
146
|
+
return res.status(400).json({ error: "prompt required" });
|
|
147
|
+
}
|
|
148
|
+
const cfg = readConfig();
|
|
149
|
+
// Force enabled for the dry-run so the operator sees what it WOULD do
|
|
150
|
+
// even when the feature is currently off.
|
|
151
|
+
const probed = structuredClone(cfg);
|
|
152
|
+
probed.skills = probed.skills || {};
|
|
153
|
+
probed.skills.inspector = { ...mergedInspectorConfig(cfg), enabled: true };
|
|
154
|
+
const out = await inspectPromptForSkills({
|
|
155
|
+
prompt,
|
|
156
|
+
projectPath: project_path,
|
|
157
|
+
globalConfig: probed,
|
|
158
|
+
});
|
|
159
|
+
res.json({ trace: out.trace, contextNote: out.contextNote });
|
|
160
|
+
} catch (e) {
|
|
161
|
+
res.status(500).json({ error: e.message });
|
|
162
|
+
}
|
|
163
|
+
});
|
|
30
164
|
}
|
|
@@ -15,10 +15,30 @@ import { appendGlobalMessage } from "#core/stores/messages.js";
|
|
|
15
15
|
import { createWebConfirmAdapter } from "#core/confirmation/adapters/web.js";
|
|
16
16
|
import { tryResolveSkillCommand } from "#core/agent/skills/trigger.js";
|
|
17
17
|
import { suggestSkillForPrompt } from "#core/agent/skills/rag.js";
|
|
18
|
+
import { inspectPromptForSkills, isInspectorEnabled, summarizeTrace } from "#core/agent/skills/inspector.js";
|
|
18
19
|
import { CHANNELS } from "#core/constants/channels.js";
|
|
19
20
|
|
|
20
21
|
const log = loggerFor("super-agent");
|
|
21
22
|
|
|
23
|
+
// Emit a single, readable line so `apx log -f` shows exactly what the skill
|
|
24
|
+
// inspector decided this turn (which skills it loaded/hinted, the embedder, and
|
|
25
|
+
// the top similarity). Best-effort: logging must never break a reply.
|
|
26
|
+
function logInspectorDecision(trace, { trace_id, channel } = {}) {
|
|
27
|
+
if (!trace) return;
|
|
28
|
+
try {
|
|
29
|
+
const top = trace.scored?.[0];
|
|
30
|
+
const topStr = top ? ` top=${top.slug}@${top.sim}` : "";
|
|
31
|
+
log.info(`skill inspector: ${summarizeTrace(trace)} [${trace.embedder || "?"}]${topStr}`, {
|
|
32
|
+
trace_id,
|
|
33
|
+
channel,
|
|
34
|
+
loaded: trace.loaded || [],
|
|
35
|
+
hinted: trace.hinted || [],
|
|
36
|
+
});
|
|
37
|
+
} catch {
|
|
38
|
+
/* logging is best-effort */
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
22
42
|
// Persist human web turns to the cross-channel message store so they feed the
|
|
23
43
|
// RAG index, search_messages, and the "active threads" awareness block. Only
|
|
24
44
|
// the human surfaces (web big chat + sidebar) — not generic "api"/automation
|
|
@@ -79,10 +99,25 @@ export function register(app, { projects, registries, plugins, project, config }
|
|
|
79
99
|
// slug is unknown.
|
|
80
100
|
const slashed = tryResolveSkillCommand(rawPrompt, { projectPath: p.path });
|
|
81
101
|
const prompt = slashed.handled ? slashed.prompt : rawPrompt;
|
|
102
|
+
const inspectorOn = isInspectorEnabled(config);
|
|
103
|
+
let inspectorTrace = null;
|
|
82
104
|
if (slashed.handled) {
|
|
83
105
|
ctx.contextNote = [ctx.contextNote, slashed.contextNote].filter(Boolean).join("\n\n");
|
|
106
|
+
} else if (inspectorOn) {
|
|
107
|
+
// Inspector middleware: per-turn semantic RAG. Replaces both the passive
|
|
108
|
+
// suggestSkillForPrompt nudge AND the static slug-dump in the system
|
|
109
|
+
// prompt — see runSuperAgent({ skipSkillsHint }).
|
|
110
|
+
const out = await inspectPromptForSkills({
|
|
111
|
+
prompt,
|
|
112
|
+
projectPath: p.path,
|
|
113
|
+
globalConfig: config,
|
|
114
|
+
});
|
|
115
|
+
inspectorTrace = out.trace;
|
|
116
|
+
if (out.contextNote) {
|
|
117
|
+
ctx.contextNote = [ctx.contextNote, out.contextNote].filter(Boolean).join("\n\n");
|
|
118
|
+
}
|
|
84
119
|
} else {
|
|
85
|
-
//
|
|
120
|
+
// Legacy path — passive nudge, still works when inspector is off.
|
|
86
121
|
const hint = await suggestSkillForPrompt(prompt, { projectPath: p.path });
|
|
87
122
|
if (hint) ctx.contextNote = [ctx.contextNote, hint].filter(Boolean).join("\n\n");
|
|
88
123
|
}
|
|
@@ -101,6 +136,14 @@ export function register(app, { projects, registries, plugins, project, config }
|
|
|
101
136
|
channel: ctx.channel,
|
|
102
137
|
});
|
|
103
138
|
|
|
139
|
+
// Surface the inspector decision to clients before model_start so the web
|
|
140
|
+
// debug panel / TUI can render "loaded: X" the moment the turn begins.
|
|
141
|
+
if (inspectorTrace) {
|
|
142
|
+
try { onEvent({ type: "skill_inspector", inspector: inspectorTrace }); }
|
|
143
|
+
catch { /* trace is best-effort */ }
|
|
144
|
+
logInspectorDecision(inspectorTrace, { trace_id: req.apxTraceId, channel: ctx.channel });
|
|
145
|
+
}
|
|
146
|
+
|
|
104
147
|
// Web/TUI channels receive a "confirmation_required" SSE event and respond
|
|
105
148
|
// via POST /super-agent/confirm/:correlationId (see api/confirm.js).
|
|
106
149
|
const requestConfirmation = createWebConfirmAdapter({ onEvent });
|
|
@@ -122,6 +165,7 @@ export function register(app, { projects, registries, plugins, project, config }
|
|
|
122
165
|
...(completionContract ? { completionContract: true } : {}),
|
|
123
166
|
onEvent,
|
|
124
167
|
requestConfirmation,
|
|
168
|
+
skipSkillsHint: inspectorOn,
|
|
125
169
|
});
|
|
126
170
|
projects.rebuild(p.id);
|
|
127
171
|
logWebTurn(ctx.channel, { prompt, replyText: saResult.text });
|
|
@@ -194,6 +238,16 @@ export function register(app, { projects, registries, plugins, project, config }
|
|
|
194
238
|
req.body || {};
|
|
195
239
|
if (!prompt) return res.status(400).json({ error: "prompt required" });
|
|
196
240
|
const ctx = resolveSuperAgentContext(req, p);
|
|
241
|
+
const inspectorOn = isInspectorEnabled(config);
|
|
242
|
+
if (inspectorOn) {
|
|
243
|
+
try {
|
|
244
|
+
const out = await inspectPromptForSkills({ prompt, projectPath: p.path, globalConfig: config });
|
|
245
|
+
if (out.contextNote) {
|
|
246
|
+
ctx.contextNote = [ctx.contextNote, out.contextNote].filter(Boolean).join("\n\n");
|
|
247
|
+
}
|
|
248
|
+
logInspectorDecision(out.trace, { trace_id: req.apxTraceId, channel: ctx.channel });
|
|
249
|
+
} catch { /* inspector failure must not block the turn */ }
|
|
250
|
+
}
|
|
197
251
|
try {
|
|
198
252
|
const saResult = await runSuperAgent({
|
|
199
253
|
globalConfig: config,
|
|
@@ -213,6 +267,7 @@ export function register(app, { projects, registries, plugins, project, config }
|
|
|
213
267
|
trace_id: req.apxTraceId,
|
|
214
268
|
channel: ctx.channel,
|
|
215
269
|
}),
|
|
270
|
+
skipSkillsHint: inspectorOn,
|
|
216
271
|
});
|
|
217
272
|
projects.rebuild(p.id);
|
|
218
273
|
logWebTurn(ctx.channel, { prompt, replyText: saResult.text });
|
package/src/host/daemon/index.js
CHANGED
|
@@ -218,6 +218,23 @@ async function main() {
|
|
|
218
218
|
// store, and start the incremental RAG indexer. Best-effort — never blocks
|
|
219
219
|
// boot and never throws into the daemon.
|
|
220
220
|
initMemory({ config: cfg, log }).catch((e) => log(`memory: init failed: ${e?.message || e}`));
|
|
221
|
+
// Skill Inspector: if enabled, refresh its vector index in the background so
|
|
222
|
+
// any SKILL.md added/edited while the daemon was down is picked up without a
|
|
223
|
+
// manual `apx skills index`. Best-effort; never blocks boot.
|
|
224
|
+
(async () => {
|
|
225
|
+
try {
|
|
226
|
+
const { isInspectorEnabled } = await import("#core/agent/skills/inspector.js");
|
|
227
|
+
if (!isInspectorEnabled(cfg)) return;
|
|
228
|
+
const { backgroundRefreshIfStale } = await import("#core/agent/skills/index-store.js");
|
|
229
|
+
const r = backgroundRefreshIfStale({
|
|
230
|
+
embedOpts: { globalConfig: cfg },
|
|
231
|
+
onDone: (out) => log(`skill inspector: index refreshed (${out.embedder}, +${out.changed.added.length} -${out.changed.removed.length} ~${out.changed.refreshed.length})`),
|
|
232
|
+
});
|
|
233
|
+
if (r.started) log(`skill inspector: reindexing ${r.missing} new / ${r.stale} stale / ${r.gone} gone skills…`);
|
|
234
|
+
} catch (e) {
|
|
235
|
+
log(`skill inspector: index refresh skipped (${e?.message || e})`);
|
|
236
|
+
}
|
|
237
|
+
})();
|
|
221
238
|
// Fire wake-up message after a short delay so plugins (Telegram) are ready
|
|
222
239
|
setTimeout(() => triggerWakeup(cfg, log), 3000);
|
|
223
240
|
// Preload whisper-server in the background so first desktop transcription is fast.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// APX CLI branding — a consistent "you're running APX vX" mark on every command.
|
|
2
|
+
//
|
|
3
|
+
// Two shapes:
|
|
4
|
+
// apxBanner(version, subtitle) big ASCII wordmark for branding-heavy moments
|
|
5
|
+
// (onboarding, top-level entry). Loud on purpose.
|
|
6
|
+
// apxHeader(version, subtitle) one-line "▸ APX CLI · vX · <subtitle>" for the
|
|
7
|
+
// everyday commands. Quiet, never in the way.
|
|
8
|
+
//
|
|
9
|
+
// Both write to STDERR so they never pollute piped stdout (`apx exec … | jq`,
|
|
10
|
+
// `apx config show > file`). Like mascot.js, they always print (so the mark is
|
|
11
|
+
// truly on every run), and self-suppress only when APX_QUIET / APX_NO_BANNER is
|
|
12
|
+
// set — the escape hatch for scripts and CI.
|
|
13
|
+
//
|
|
14
|
+
// Color: reuses raw ANSI like mascot.js. Honors NO_COLOR.
|
|
15
|
+
|
|
16
|
+
const NO_COLOR = !!process.env.NO_COLOR;
|
|
17
|
+
const c = (code) => (s) => (NO_COLOR ? s : `\x1b[${code}m${s}\x1b[0m`);
|
|
18
|
+
const B = c("1");
|
|
19
|
+
const DI = c("2");
|
|
20
|
+
const GR = c("32");
|
|
21
|
+
const CY = c("36");
|
|
22
|
+
const WH = c("97");
|
|
23
|
+
|
|
24
|
+
function suppressed() {
|
|
25
|
+
return !!(process.env.APX_NO_BANNER || process.env.APX_QUIET);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Compact, single-line header. The default for everyday subcommands.
|
|
29
|
+
// ▸ APX CLI · v1.34.0 · skills inspector
|
|
30
|
+
export function apxHeader(version, subtitle = "") {
|
|
31
|
+
if (suppressed()) return;
|
|
32
|
+
const tag = `${GR("▸")} ${B(WH("APX"))} ${DI("CLI")}`;
|
|
33
|
+
const ver = DI(`v${version}`);
|
|
34
|
+
const sub = subtitle ? ` ${DI("·")} ${CY(subtitle)}` : "";
|
|
35
|
+
process.stderr.write(`\n${tag} ${DI("·")} ${ver}${sub}\n\n`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Big ASCII wordmark for branding-heavy commands.
|
|
39
|
+
export function apxBanner(version, subtitle = "") {
|
|
40
|
+
if (suppressed()) return;
|
|
41
|
+
const g = (s) => GR(s);
|
|
42
|
+
const lines = [
|
|
43
|
+
"",
|
|
44
|
+
` ${g("█████╗ ██████╗ ██╗ ██╗")}`,
|
|
45
|
+
` ${g("██╔══██╗██╔══██╗╚██╗██╔╝")}`,
|
|
46
|
+
` ${g("███████║██████╔╝ ╚███╔╝ ")} ${B(WH("Agent Project Context"))}`,
|
|
47
|
+
` ${g("██╔══██║██╔═══╝ ██╔██╗ ")} ${DI(`v${version}`)}`,
|
|
48
|
+
` ${g("██║ ██║██║ ██╔╝ ██╗")}${subtitle ? ` ${CY(subtitle)}` : ""}`,
|
|
49
|
+
` ${g("╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝")}`,
|
|
50
|
+
"",
|
|
51
|
+
];
|
|
52
|
+
process.stderr.write(lines.join("\n") + "\n");
|
|
53
|
+
}
|