@agentprojectcontext/apx 1.33.1 → 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 +49 -61
- package/src/core/agent/a2a/reply.js +48 -0
- package/src/core/agent/build-agent-system.js +136 -59
- package/src/core/agent/channels/voice-context.js +98 -0
- package/src/core/agent/memory.js +2 -1
- package/src/core/agent/prompt-builder.js +178 -124
- 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/prompts/modes/code-build.md +1 -0
- package/src/core/agent/prompts/modes/code-plan.md +1 -0
- package/src/core/agent/prompts/modes/index.js +28 -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/skills/loader.js +22 -18
- package/src/core/agent/stream/turn-accumulator.js +73 -0
- package/src/core/agent/suggestions.js +37 -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/add-project.js +5 -2
- package/src/core/agent/tools/handlers/call-runtime.js +3 -2
- 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/handlers/transcribe-audio.js +1 -1
- package/src/core/agent/tools/helpers.js +2 -2
- package/src/core/agent/tools/names.js +169 -0
- package/src/core/agent/tools/registry-bridge.js +6 -14
- package/src/core/agent/tools/registry.js +103 -69
- package/src/core/apc/context-copy.js +27 -0
- package/src/core/apc/notes.js +19 -0
- package/src/core/apc/parser.js +12 -5
- package/src/core/apc/paths.js +87 -0
- package/src/core/apc/scaffold.js +82 -76
- package/src/core/apc/skill-sync.js +10 -0
- package/src/{host/daemon/plugins → core/channels}/telegram/dispatch.js +38 -16
- package/src/core/config/index.js +24 -2
- package/src/core/config/redact.js +95 -0
- package/src/core/constants/channels.js +2 -0
- package/src/core/constants/code-modes.js +10 -0
- package/src/core/constants/index.js +1 -0
- package/src/core/deck/manifest.js +186 -0
- package/src/core/engines/catalog.js +83 -0
- package/src/core/{tools → http-tools}/browser.js +0 -1
- package/src/core/{tools → http-tools}/fetch.js +0 -1
- package/src/core/{tools → http-tools}/glob.js +0 -1
- package/src/core/{tools → http-tools}/grep.js +0 -1
- package/src/core/{tools → http-tools}/registry.js +0 -1
- package/src/core/{tools → http-tools}/search.js +0 -1
- package/src/core/i18n/en.js +9 -0
- package/src/core/i18n/es.js +12 -0
- package/src/core/i18n/index.js +54 -0
- package/src/core/i18n/pt.js +9 -0
- package/src/core/identity/telegram.js +2 -1
- package/src/core/mcp/runner.js +272 -14
- package/src/core/mcp/sources.js +3 -2
- package/src/core/routines/index.js +16 -0
- package/src/{host/daemon/routines.js → core/routines/runner.js} +36 -103
- package/src/core/runtime-skills/apc-context/SKILL.md +159 -0
- package/src/core/runtime-skills/apx/SKILL.md +83 -0
- package/src/core/runtime-skills/apx-agency-agents/SKILL.md +125 -0
- package/src/core/runtime-skills/apx-agent/SKILL.md +97 -0
- package/src/core/runtime-skills/apx-mcp/SKILL.md +111 -0
- package/src/core/runtime-skills/apx-mcp-builder/SKILL.md +169 -0
- package/{skills → src/core/runtime-skills}/apx-project/SKILL.md +20 -29
- package/src/core/runtime-skills/apx-routine/SKILL.md +127 -0
- package/src/core/runtime-skills/apx-runtime/SKILL.md +99 -0
- package/src/core/runtime-skills/apx-sessions/SKILL.md +232 -0
- package/src/core/runtime-skills/apx-skill-builder/SKILL.md +129 -0
- package/{skills → src/core/runtime-skills}/apx-task/SKILL.md +18 -21
- package/src/core/runtime-skills/apx-telegram/SKILL.md +120 -0
- package/src/core/runtime-skills/apx-voice/SKILL.md +117 -0
- package/src/core/runtime-skills/{claude-code.md → claude-code/SKILL.md} +1 -0
- package/src/core/runtime-skills/{codex-cli.md → codex-cli/SKILL.md} +1 -0
- package/src/core/runtime-skills/{opencode-cli.md → opencode-cli/SKILL.md} +1 -0
- package/src/core/runtime-skills/{openrouter.md → openrouter/SKILL.md} +1 -0
- package/src/{host/daemon/env-detect.js → core/runtimes/detect.js} +1 -1
- package/src/core/stores/code-sessions.js +50 -2
- package/src/core/stores/routine-memory.js +1 -1
- package/src/core/stores/sessions-search.js +121 -0
- package/src/core/stores/sessions.js +38 -0
- package/src/core/vars/index.js +14 -0
- package/src/core/vars/interpolate.js +86 -0
- package/src/core/vars/sources.js +151 -0
- package/src/core/voice/audio-decode.js +38 -0
- package/src/core/voice/transcription.js +225 -0
- package/src/host/daemon/api/admin-config.js +5 -82
- package/src/host/daemon/api/agents.js +5 -5
- package/src/host/daemon/api/code.js +17 -169
- package/src/host/daemon/api/config.js +3 -4
- package/src/host/daemon/api/conversations.js +8 -29
- package/src/host/daemon/api/deck.js +37 -404
- package/src/host/daemon/api/engines.js +1 -80
- package/src/host/daemon/api/exec.js +1 -1
- package/src/host/daemon/api/mcps.js +32 -0
- package/src/host/daemon/api/routines.js +1 -1
- package/src/host/daemon/api/runtimes.js +4 -3
- package/src/host/daemon/api/sessions-search.js +24 -140
- package/src/host/daemon/api/sessions.js +12 -30
- package/src/host/daemon/api/shared.js +2 -1
- package/src/host/daemon/api/skills.js +140 -6
- package/src/host/daemon/api/super-agent.js +56 -1
- package/src/host/daemon/api/telegram.js +1 -11
- package/src/host/daemon/api/tools.js +6 -6
- package/src/host/daemon/api/transcribe.js +2 -2
- package/src/host/daemon/api/vars.js +137 -0
- package/src/host/daemon/api/voice.js +13 -290
- package/src/host/daemon/api.js +2 -0
- package/src/host/daemon/db.js +6 -6
- package/src/host/daemon/deck-exec.js +148 -0
- package/src/host/daemon/index.js +20 -3
- package/src/host/daemon/plugins/telegram/index.js +9 -9
- package/src/host/daemon/routines-scheduler.js +64 -0
- package/src/host/daemon/smoke.js +3 -2
- package/src/host/daemon/whisper-server.js +225 -0
- package/src/interfaces/cli/branding.js +53 -0
- package/src/interfaces/cli/commands/agent.js +3 -2
- package/src/interfaces/cli/commands/command.js +2 -3
- package/src/interfaces/cli/commands/messages.js +6 -2
- package/src/interfaces/cli/commands/pair.js +5 -4
- package/src/interfaces/cli/commands/search.js +1 -1
- package/src/interfaces/cli/commands/sessions.js +3 -2
- package/src/interfaces/cli/commands/skills.js +290 -55
- 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/package-lock.json +182 -182
- package/src/interfaces/web/src/components/ModelCombobox.tsx +2 -1
- package/src/interfaces/web/src/components/TelegramChannelDialog.tsx +1 -1
- package/src/interfaces/web/src/components/chat/AskAnswersCard.tsx +76 -0
- package/src/interfaces/web/src/components/chat/MessageBubble.tsx +37 -4
- package/src/interfaces/web/src/components/chat/MessageList.tsx +23 -1
- package/src/interfaces/web/src/components/chat/ModelPicker.tsx +3 -1
- package/src/interfaces/web/src/components/code/CodeArtifactsTab.tsx +4 -4
- package/src/interfaces/web/src/components/code/CodeChangesTab.tsx +1 -1
- package/src/interfaces/web/src/components/code/CodeFileTree.tsx +3 -2
- package/src/interfaces/web/src/components/code/CodeFileViewer.tsx +3 -2
- package/src/interfaces/web/src/components/code/CodeTerminal.tsx +3 -2
- package/src/interfaces/web/src/components/config/GlobalConfigEditor.tsx +2 -1
- package/src/interfaces/web/src/components/deck/WidgetRow.tsx +2 -1
- package/src/interfaces/web/src/components/inputs/KeyValueList.tsx +93 -0
- package/src/interfaces/web/src/components/inputs/VarTokenInput.tsx +449 -0
- package/src/interfaces/web/src/components/settings/DefaultRouterCard.tsx +2 -1
- package/src/interfaces/web/src/components/settings/EnginesPanel.tsx +2 -2
- package/src/interfaces/web/src/components/settings/MemoryPanel.tsx +73 -4
- package/src/interfaces/web/src/components/settings/SkillsInspectorPanel.tsx +222 -0
- package/src/interfaces/web/src/components/settings/providers/ProviderCard.tsx +3 -2
- package/src/interfaces/web/src/components/settings/providers/ProviderModal.tsx +3 -2
- package/src/interfaces/web/src/components/ui/chat-input.tsx +5 -4
- package/src/interfaces/web/src/components/ui/sidebar.tsx +3 -2
- package/src/interfaces/web/src/components/voice/VoiceProviderModal.tsx +2 -1
- package/src/interfaces/web/src/constants/index.ts +1 -1
- package/src/interfaces/web/src/hooks/useChat.ts +19 -0
- package/src/interfaces/web/src/i18n/en.ts +175 -7
- package/src/interfaces/web/src/i18n/es.ts +180 -15
- package/src/interfaces/web/src/lib/api/mcps.ts +25 -0
- package/src/interfaces/web/src/lib/api/skills.ts +70 -0
- package/src/interfaces/web/src/lib/api/vars.ts +38 -0
- package/src/interfaces/web/src/lib/api.ts +1 -0
- package/src/interfaces/web/src/screens/ProjectScreen.tsx +8 -31
- package/src/interfaces/web/src/screens/SettingsScreen.tsx +6 -2
- package/src/interfaces/web/src/screens/modules/CodeScreen.tsx +1 -1
- package/src/interfaces/web/src/screens/modules/DeckScreen.tsx +4 -3
- package/src/interfaces/web/src/screens/modules/DesktopScreen.tsx +7 -6
- package/src/interfaces/web/src/screens/modules/VoiceScreen.tsx +4 -3
- package/src/interfaces/web/src/screens/project/AgentDetailScreen.tsx +1 -1
- package/src/interfaces/web/src/screens/project/ConfigTab.tsx +132 -1
- package/src/interfaces/web/src/screens/project/McpsTab.tsx +549 -104
- package/src/interfaces/web/src/screens/project/RoutinesTab.tsx +1 -1
- package/src/interfaces/web/src/screens/project/VarsTab.tsx +300 -0
- package/src/interfaces/web/src/types/daemon.ts +15 -0
- package/skills/apx-agency-agents/SKILL.md +0 -141
- package/skills/apx-agent/SKILL.md +0 -100
- package/skills/apx-mcp-builder/SKILL.md +0 -183
- package/skills/apx-routine/SKILL.md +0 -140
- package/skills/apx-runtime/SKILL.md +0 -117
- package/skills/apx-sessions/SKILL.md +0 -281
- package/skills/apx-skill-builder/SKILL.md +0 -153
- package/skills/apx-telegram/SKILL.md +0 -131
- package/skills/apx-voice/SKILL.md +0 -137
- package/src/core/agent/prompts/action-discipline.md +0 -24
- package/src/core/agent/prompts/super-agent-base.md +0 -42
- package/src/host/daemon/transcription.js +0 -538
- package/src/host/daemon/whisper-transcribe.py +0 -73
- package/src/interfaces/web/dist/assets/index-Aaiw8BZN.css +0 -1
- package/src/interfaces/web/dist/assets/index-DPqtjDjh.js +0 -602
- package/src/interfaces/web/dist/assets/index-DPqtjDjh.js.map +0 -1
- /package/src/{host/daemon → core/apc}/projects-helpers.js +0 -0
- /package/src/{host/daemon/plugins → core/channels}/telegram/ask.js +0 -0
- /package/src/{host/daemon/plugins → core/channels}/telegram/helpers.js +0 -0
- /package/src/{host/daemon/plugins → core/channels}/telegram/media.js +0 -0
- /package/src/core/{tools → http-tools}/index.js +0 -0
- /package/src/{host/daemon/compact.js → core/stores/conversations-compactor.js} +0 -0
- /package/src/{host/daemon → core/stores}/conversations.js +0 -0
- /package/src/{host/daemon → core/util}/thinking.js +0 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// Secret redaction for the global config. Wraps any string secret with a
|
|
2
|
+
// `*** set *** (...<suffix>)` marker so the web admin can show "value is set"
|
|
3
|
+
// without leaking it, AND so PATCH callers can echo back the marker to mean
|
|
4
|
+
// "don't touch this one" — see isSecretMarker / mergeRedactedChannels below.
|
|
5
|
+
//
|
|
6
|
+
// The dotted paths in SECRET_PATHS are the single source of truth for "which
|
|
7
|
+
// keys are secrets". Anything new (a new engine api_key, a new TTS provider
|
|
8
|
+
// key, etc.) goes here and every redaction path picks it up.
|
|
9
|
+
|
|
10
|
+
const SECRET_MARKER_PREFIX = "*** set ***";
|
|
11
|
+
|
|
12
|
+
export const SECRET_PATHS = [
|
|
13
|
+
"engines.anthropic.api_key",
|
|
14
|
+
"engines.openai.api_key",
|
|
15
|
+
"engines.groq.api_key",
|
|
16
|
+
"engines.openrouter.api_key",
|
|
17
|
+
"engines.gemini.api_key",
|
|
18
|
+
"voice.tts.elevenlabs.api_key",
|
|
19
|
+
"voice.tts.openai.api_key",
|
|
20
|
+
"voice.tts.gemini.api_key",
|
|
21
|
+
"memory.embeddings.openai.api_key",
|
|
22
|
+
"memory.embeddings.gemini.api_key",
|
|
23
|
+
// Telegram bot tokens live inside an array — handled separately in redact()
|
|
24
|
+
// because dotted paths can't address array entries.
|
|
25
|
+
"telegram.channels.*.bot_token",
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
/** Replace a secret string with the visible marker, preserving the last 5 chars. */
|
|
29
|
+
export function secretMarker(value) {
|
|
30
|
+
if (typeof value !== "string" || !value.length) return value;
|
|
31
|
+
const suffix = value.slice(-5);
|
|
32
|
+
return `${SECRET_MARKER_PREFIX} (...${suffix})`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** True when a value is the placeholder a redacted view sends back unchanged. */
|
|
36
|
+
export function isSecretMarker(value) {
|
|
37
|
+
return typeof value === "string" && value.startsWith(SECRET_MARKER_PREFIX);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Deep-copy of `cfg` with every secret string replaced by its marker. */
|
|
41
|
+
export function redactConfig(cfg) {
|
|
42
|
+
const out = JSON.parse(JSON.stringify(cfg || {}));
|
|
43
|
+
const mark = (val) => (typeof val === "string" && val.length ? secretMarker(val) : val);
|
|
44
|
+
|
|
45
|
+
for (const dotted of SECRET_PATHS) {
|
|
46
|
+
if (dotted.includes("*")) continue;
|
|
47
|
+
const parts = dotted.split(".");
|
|
48
|
+
let cur = out;
|
|
49
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
50
|
+
if (!cur[parts[i]] || typeof cur[parts[i]] !== "object") { cur = null; break; }
|
|
51
|
+
cur = cur[parts[i]];
|
|
52
|
+
}
|
|
53
|
+
if (cur && cur[parts[parts.length - 1]]) {
|
|
54
|
+
cur[parts[parts.length - 1]] = mark(cur[parts[parts.length - 1]]);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const channels = out?.telegram?.channels;
|
|
58
|
+
if (Array.isArray(channels)) {
|
|
59
|
+
for (const ch of channels) {
|
|
60
|
+
if (ch && typeof ch.bot_token === "string" && ch.bot_token.length) {
|
|
61
|
+
ch.bot_token = mark(ch.bot_token);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Redact a single Telegram channel record. */
|
|
69
|
+
export function redactChannel(channel) {
|
|
70
|
+
if (!channel?.bot_token) return channel;
|
|
71
|
+
return { ...channel, bot_token: secretMarker(channel.bot_token) };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Merge a PATCH-shape `nextChannels` against the prior on-disk list. Any
|
|
76
|
+
* incoming channel whose bot_token is missing or a marker takes the prior
|
|
77
|
+
* token verbatim — so a UI that echoes the redacted view back doesn't wipe
|
|
78
|
+
* the real secret.
|
|
79
|
+
*/
|
|
80
|
+
export function mergeRedactedChannels(nextChannels, priorChannels) {
|
|
81
|
+
if (!Array.isArray(nextChannels)) return nextChannels;
|
|
82
|
+
const priorByName = new Map(
|
|
83
|
+
(Array.isArray(priorChannels) ? priorChannels : [])
|
|
84
|
+
.filter((c) => c && typeof c.name === "string")
|
|
85
|
+
.map((c) => [c.name, c])
|
|
86
|
+
);
|
|
87
|
+
return nextChannels.map((channel) => {
|
|
88
|
+
if (!channel || typeof channel !== "object") return channel;
|
|
89
|
+
const prior = priorByName.get(channel.name);
|
|
90
|
+
if (prior?.bot_token && (channel.bot_token === undefined || isSecretMarker(channel.bot_token))) {
|
|
91
|
+
return { ...channel, bot_token: prior.bot_token };
|
|
92
|
+
}
|
|
93
|
+
return channel;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
@@ -16,4 +16,6 @@ export const CHANNELS = Object.freeze({
|
|
|
16
16
|
DECK: "deck", // Mobile cockpit dashboard
|
|
17
17
|
DESKTOP: "desktop", // Electron capsule (always voice mode)
|
|
18
18
|
CODE: "code", // `apx code` — terminal coding session
|
|
19
|
+
DIRECT: "direct", // Planned: 1:1 channel that isn't a chat platform
|
|
20
|
+
WHATSAPP: "whatsapp", // Planned: WhatsApp bot integration
|
|
19
21
|
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Code session modes. PLAN = read-only exploration (the agent proposes
|
|
2
|
+
// changes but never mutates); BUILD = unrestricted execution. The value
|
|
3
|
+
// lives in code-sessions.json (session.mode) and is what api/code.js,
|
|
4
|
+
// stores/code-sessions.js, and agent/prompts/modes/ all branch on.
|
|
5
|
+
export const CODE_MODES = Object.freeze({
|
|
6
|
+
PLAN: "plan",
|
|
7
|
+
BUILD: "build",
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export const DEFAULT_CODE_MODE = CODE_MODES.BUILD;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
// APX Deck manifest — the data model the companion clients (deck, desktop
|
|
2
|
+
// capsule) read on boot. Pure data + decoration; no HTTP or filesystem.
|
|
3
|
+
// host/daemon/api/deck.js wraps this for the /deck/manifest endpoint.
|
|
4
|
+
|
|
5
|
+
export const CORE_WIDGETS = [
|
|
6
|
+
{
|
|
7
|
+
id: "apx-current-project",
|
|
8
|
+
title: "Proyecto actual",
|
|
9
|
+
source: "apx",
|
|
10
|
+
desktop: "project",
|
|
11
|
+
kind: "context",
|
|
12
|
+
status: "available",
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
id: "apx-voice",
|
|
16
|
+
title: "Voz APX",
|
|
17
|
+
source: "apx",
|
|
18
|
+
desktop: "general",
|
|
19
|
+
kind: "voice",
|
|
20
|
+
status: "available",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: "apx-agents",
|
|
24
|
+
title: "Agentes APX",
|
|
25
|
+
source: "apx",
|
|
26
|
+
desktop: "ai",
|
|
27
|
+
kind: "agents",
|
|
28
|
+
status: "available",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: "apx-notes",
|
|
32
|
+
title: "Notas APX",
|
|
33
|
+
source: "apx",
|
|
34
|
+
desktop: "project",
|
|
35
|
+
kind: "capture",
|
|
36
|
+
status: "available",
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
export const EXTERNAL_WIDGETS = [
|
|
41
|
+
["docker", "Docker", "infra"],
|
|
42
|
+
["dokploy", "Dokploy", "infra"],
|
|
43
|
+
["factorial", "Factorial", "work"],
|
|
44
|
+
["telegram", "Telegram", "comms"],
|
|
45
|
+
["gmail", "Gmail", "comms"],
|
|
46
|
+
["outlook", "Outlook", "comms"],
|
|
47
|
+
["teams", "Teams", "comms"],
|
|
48
|
+
["whatsapp", "WhatsApp", "comms"],
|
|
49
|
+
["zen", "Zen Browser", "ai"],
|
|
50
|
+
["claude", "Claude", "ai"],
|
|
51
|
+
["chatgpt", "ChatGPT", "ai"],
|
|
52
|
+
["cursor", "Cursor", "ai"],
|
|
53
|
+
["codex", "Codex", "ai"],
|
|
54
|
+
].map(([id, title, desktop]) => ({
|
|
55
|
+
id,
|
|
56
|
+
title,
|
|
57
|
+
source: "external",
|
|
58
|
+
desktop,
|
|
59
|
+
kind: "plugin",
|
|
60
|
+
status: "not_configured",
|
|
61
|
+
}));
|
|
62
|
+
|
|
63
|
+
export const DESKTOPS = [
|
|
64
|
+
{ id: "general", title: "Hoy" },
|
|
65
|
+
{ id: "project", title: "Proyecto" },
|
|
66
|
+
{ id: "ai", title: "IA" },
|
|
67
|
+
{ id: "comms", title: "Comunicaciones" },
|
|
68
|
+
{ id: "infra", title: "Infra" },
|
|
69
|
+
{ id: "work", title: "Tiempo laboral" },
|
|
70
|
+
{ id: "plugins", title: "Plugins" },
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
export const SAFE_ACTIONS = [
|
|
74
|
+
{
|
|
75
|
+
id: "apx.copy_context",
|
|
76
|
+
title: "Copiar contexto APX",
|
|
77
|
+
risk: "safe",
|
|
78
|
+
endpoint: "/projects/:pid/agents",
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: "apx.voice_turn",
|
|
82
|
+
title: "Hablar con APX",
|
|
83
|
+
risk: "safe",
|
|
84
|
+
endpoint: "/voice/turn",
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: "apx.super_agent",
|
|
88
|
+
title: "Pedir acción a APX",
|
|
89
|
+
risk: "confirm",
|
|
90
|
+
endpoint: "/projects/:pid/super-agent/chat",
|
|
91
|
+
},
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
// Widget ids the user is allowed to override. Keeps a rogue client from
|
|
95
|
+
// writing arbitrary keys into the global config under deck.widget_overrides.
|
|
96
|
+
// CORE_WIDGETS are intentionally NOT in here — they're built-in APX surfaces
|
|
97
|
+
// and don't make sense to disable.
|
|
98
|
+
export const TOGGLEABLE_WIDGETS = new Set(EXTERNAL_WIDGETS.map((w) => w.id));
|
|
99
|
+
|
|
100
|
+
function pickActiveProject(projectList) {
|
|
101
|
+
return projectList.find((project) => Number(project.id) !== 0) || projectList[0] || null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Apply runtime status + user overrides to the static EXTERNAL_WIDGETS list.
|
|
106
|
+
*
|
|
107
|
+
* 1. user explicitly disabled it → "disabled" (sticky, regardless of plugin
|
|
108
|
+
* auto-detect)
|
|
109
|
+
* 2. daemon has a running plugin → "available"
|
|
110
|
+
* 3. user toggled it on but no plugin backing → "configured"
|
|
111
|
+
* 4. nothing → leave the static "not_configured" default
|
|
112
|
+
*/
|
|
113
|
+
export function decorateExternalWidgets(pluginStatus = {}, overrides = {}) {
|
|
114
|
+
return EXTERNAL_WIDGETS.map((widget) => {
|
|
115
|
+
const override = overrides[widget.id];
|
|
116
|
+
const status = pluginStatus[widget.id];
|
|
117
|
+
const decorated = { ...widget };
|
|
118
|
+
if (status) decorated.daemon_status = status;
|
|
119
|
+
if (override?.enabled === false) {
|
|
120
|
+
decorated.status = "disabled";
|
|
121
|
+
} else if (status) {
|
|
122
|
+
decorated.status = status.enabled === false ? "disabled" : "available";
|
|
123
|
+
} else if (override?.enabled === true) {
|
|
124
|
+
decorated.status = "configured";
|
|
125
|
+
}
|
|
126
|
+
// Always echo the user-toggle so the app can render the switch
|
|
127
|
+
// independently of the running/available bit.
|
|
128
|
+
decorated.user_enabled = override?.enabled ?? null;
|
|
129
|
+
return decorated;
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Build the full /deck/manifest response body.
|
|
135
|
+
*
|
|
136
|
+
* Inputs are *resolved* runtime values, not the live managers — caller is
|
|
137
|
+
* responsible for catching errors in projects.list()/plugins.status() and
|
|
138
|
+
* passing the resulting arrays/maps in (or empty defaults).
|
|
139
|
+
*/
|
|
140
|
+
export function buildDeckManifest({
|
|
141
|
+
projectList = [],
|
|
142
|
+
pluginStatus = {},
|
|
143
|
+
overrides = {},
|
|
144
|
+
version,
|
|
145
|
+
startedAt,
|
|
146
|
+
config,
|
|
147
|
+
}) {
|
|
148
|
+
const activeProject = pickActiveProject(projectList);
|
|
149
|
+
return {
|
|
150
|
+
status: "ok",
|
|
151
|
+
daemon: {
|
|
152
|
+
name: "apx",
|
|
153
|
+
version,
|
|
154
|
+
host: config?.host || "127.0.0.1",
|
|
155
|
+
port: config?.port || 7430,
|
|
156
|
+
uptime_s: Math.round((Date.now() - startedAt) / 1000),
|
|
157
|
+
started_at: new Date(startedAt).toISOString(),
|
|
158
|
+
},
|
|
159
|
+
deck: {
|
|
160
|
+
name: "apx-deck",
|
|
161
|
+
desktops: DESKTOPS,
|
|
162
|
+
widgets: [...CORE_WIDGETS, ...decorateExternalWidgets(pluginStatus, overrides)],
|
|
163
|
+
suggested_actions: SAFE_ACTIONS,
|
|
164
|
+
},
|
|
165
|
+
apx: {
|
|
166
|
+
active_project: activeProject,
|
|
167
|
+
projects: projectList,
|
|
168
|
+
plugins: pluginStatus,
|
|
169
|
+
endpoints: {
|
|
170
|
+
health: "/health",
|
|
171
|
+
projects: "/projects",
|
|
172
|
+
plugins: "/plugins",
|
|
173
|
+
voice_turn: "/voice/turn",
|
|
174
|
+
transcribe_chunk: "/transcribe/chunk",
|
|
175
|
+
super_agent_chat: "/projects/:pid/super-agent/chat",
|
|
176
|
+
super_agent_stream: "/projects/:pid/super-agent/chat/stream",
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
safety: {
|
|
180
|
+
direct_shell: false,
|
|
181
|
+
arbitrary_commands: false,
|
|
182
|
+
dangerous_actions_require_confirmation: true,
|
|
183
|
+
allowed_actions_only: true,
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Live model catalogs per engine. Wraps each provider's "list models" endpoint
|
|
2
|
+
// behind one signature: listModels(engine, baseUrl?, apiKey?) → { models } or
|
|
3
|
+
// { error }. Pure transport — no daemon dependencies. Both the daemon HTTP
|
|
4
|
+
// adapter and CLI commands can reuse this.
|
|
5
|
+
import { fetchJsonWithTimeout } from "./_health.js";
|
|
6
|
+
|
|
7
|
+
export const DEFAULT_BASE = {
|
|
8
|
+
openai: "https://api.openai.com/v1",
|
|
9
|
+
groq: "https://api.groq.com/openai/v1",
|
|
10
|
+
openrouter: "https://openrouter.ai/api/v1",
|
|
11
|
+
gemini: "https://generativelanguage.googleapis.com/v1beta/openai",
|
|
12
|
+
anthropic: "https://api.anthropic.com/v1",
|
|
13
|
+
ollama: "http://localhost:11434",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// Gemini's native models endpoint returns a much richer catalog than the
|
|
17
|
+
// OpenAI-compat shim (which only echoes back a handful). We always query the
|
|
18
|
+
// native URL regardless of the user's configured base_url.
|
|
19
|
+
const GEMINI_NATIVE_BASE = "https://generativelanguage.googleapis.com/v1beta";
|
|
20
|
+
|
|
21
|
+
export async function listModels(engine, baseUrl, apiKey) {
|
|
22
|
+
const base = String(baseUrl || DEFAULT_BASE[engine] || "").replace(/\/$/, "");
|
|
23
|
+
|
|
24
|
+
if (engine === "ollama") {
|
|
25
|
+
const b = base || process.env.OLLAMA_HOST || "http://localhost:11434";
|
|
26
|
+
const r = await fetchJsonWithTimeout(`${b}/api/tags`, { timeoutMs: 2500 });
|
|
27
|
+
if (!r.ok) return { error: r.reason || "no se pudo contactar Ollama" };
|
|
28
|
+
const list = Array.isArray(r.json?.models) ? r.json.models : [];
|
|
29
|
+
return { models: list.map((m) => m?.name).filter((n) => typeof n === "string" && n) };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (engine === "anthropic") {
|
|
33
|
+
if (!apiKey) return { error: "falta api_key" };
|
|
34
|
+
const b = base || DEFAULT_BASE.anthropic;
|
|
35
|
+
const r = await fetchJsonWithTimeout(`${b}/models?limit=100`, {
|
|
36
|
+
timeoutMs: 5000,
|
|
37
|
+
headers: { "x-api-key": apiKey, "anthropic-version": "2023-06-01" },
|
|
38
|
+
});
|
|
39
|
+
if (!r.ok) return { error: r.reason || `HTTP ${r.status}` };
|
|
40
|
+
const data = Array.isArray(r.json?.data) ? r.json.data : [];
|
|
41
|
+
return { models: data.map((m) => m?.id).filter(Boolean) };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (engine === "gemini") {
|
|
45
|
+
if (!apiKey) return { error: "falta api_key" };
|
|
46
|
+
// Native Gemini API returns rich metadata, including supportedGenerationMethods
|
|
47
|
+
// so we can drop embeddings/vision-only entries. Names come back as
|
|
48
|
+
// "models/<id>"; strip the prefix.
|
|
49
|
+
const r = await fetchJsonWithTimeout(
|
|
50
|
+
`${GEMINI_NATIVE_BASE}/models?key=${encodeURIComponent(apiKey)}&pageSize=200`,
|
|
51
|
+
{ timeoutMs: 5000 },
|
|
52
|
+
);
|
|
53
|
+
if (!r.ok) return { error: r.reason || `HTTP ${r.status}` };
|
|
54
|
+
const data = Array.isArray(r.json?.models) ? r.json.models : [];
|
|
55
|
+
const models = data
|
|
56
|
+
.filter((m) => {
|
|
57
|
+
const methods = m?.supportedGenerationMethods;
|
|
58
|
+
if (!Array.isArray(methods)) return true;
|
|
59
|
+
return methods.includes("generateContent");
|
|
60
|
+
})
|
|
61
|
+
.map((m) => {
|
|
62
|
+
const name = typeof m?.name === "string" ? m.name : "";
|
|
63
|
+
return name.startsWith("models/") ? name.slice("models/".length) : name;
|
|
64
|
+
})
|
|
65
|
+
.filter(Boolean);
|
|
66
|
+
return { models };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// openai-compatible family: openai, groq, openrouter, azure, custom
|
|
70
|
+
if (!apiKey) return { error: "falta api_key" };
|
|
71
|
+
if (!base) return { error: "falta base_url" };
|
|
72
|
+
const r = await fetchJsonWithTimeout(`${base}/models`, {
|
|
73
|
+
timeoutMs: 5000,
|
|
74
|
+
headers: { authorization: `Bearer ${apiKey}` },
|
|
75
|
+
});
|
|
76
|
+
if (!r.ok) return { error: r.reason || `HTTP ${r.status}` };
|
|
77
|
+
const data = Array.isArray(r.json?.data)
|
|
78
|
+
? r.json.data
|
|
79
|
+
: Array.isArray(r.json?.models)
|
|
80
|
+
? r.json.models
|
|
81
|
+
: [];
|
|
82
|
+
return { models: data.map((m) => m?.id || m?.name).filter(Boolean) };
|
|
83
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Backend strings — English (en).
|
|
2
|
+
export default {
|
|
3
|
+
"telegram.heads_up": "On it — working on that… 🛠️",
|
|
4
|
+
"telegram.reset_ack": "Done, context cleared. Starting fresh. What do you need?",
|
|
5
|
+
"telegram.error_generic": "Something broke on my side — already logged.",
|
|
6
|
+
"telegram.fallback_listo": "Done.",
|
|
7
|
+
|
|
8
|
+
"common.unknown_error": "Something went wrong.",
|
|
9
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Backend strings — Spanish (es). Keep this file flat dot-paths only; the
|
|
2
|
+
// web admin has its own i18n tree.
|
|
3
|
+
export default {
|
|
4
|
+
// Telegram channel
|
|
5
|
+
"telegram.heads_up": "Dale, estoy con eso… 🛠️",
|
|
6
|
+
"telegram.reset_ack": "Listo, contexto borrado. Arranco un hilo nuevo, ¿qué necesitás?",
|
|
7
|
+
"telegram.error_generic": "Algo se rompió de mi lado — ya lo registré.",
|
|
8
|
+
"telegram.fallback_listo": "Listo.",
|
|
9
|
+
|
|
10
|
+
// Generic helpers reused from several surfaces
|
|
11
|
+
"common.unknown_error": "Algo salió mal.",
|
|
12
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Backend i18n for daemon-side messages (Telegram heads-up, system replies,
|
|
2
|
+
// any other user-facing string emitted from the host/core layer). The web
|
|
3
|
+
// admin has its own dict tree under src/interfaces/web/src/i18n/ — that one
|
|
4
|
+
// stays separate, this is for what the daemon sends back.
|
|
5
|
+
//
|
|
6
|
+
// Usage:
|
|
7
|
+
// import { t, resolveLang } from "#core/i18n/index.js";
|
|
8
|
+
// const lang = resolveLang(globalConfig);
|
|
9
|
+
// await sendTelegram(t("telegram.heads_up", { lang }));
|
|
10
|
+
//
|
|
11
|
+
// Adding a key: pick a clear dotted path, add it to every locale dict, and
|
|
12
|
+
// the unit test in tests/i18n.test.js will assert parity (no missing
|
|
13
|
+
// translations). Values can include {var} placeholders that t() will fill.
|
|
14
|
+
import en from "./en.js";
|
|
15
|
+
import es from "./es.js";
|
|
16
|
+
import pt from "./pt.js";
|
|
17
|
+
|
|
18
|
+
const DICTS = Object.freeze({ en, es, pt });
|
|
19
|
+
const DEFAULT_LANG = "es";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Pull the user's preferred language code from a globalConfig snapshot.
|
|
23
|
+
* Falls back to DEFAULT_LANG when nothing is set. The 2-char slice keeps
|
|
24
|
+
* "es-AR" / "en-US" / "pt-BR" working without per-region dicts.
|
|
25
|
+
*/
|
|
26
|
+
export function resolveLang(globalConfig) {
|
|
27
|
+
const raw = globalConfig?.user?.language;
|
|
28
|
+
return String(raw || DEFAULT_LANG).slice(0, 2).toLowerCase();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function format(s, vars) {
|
|
32
|
+
if (!vars) return s;
|
|
33
|
+
return s.replace(/\{(\w+)\}/g, (_m, k) => (k in vars ? String(vars[k]) : `{${k}}`));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Translate a key into the active locale. Missing keys fall back through:
|
|
38
|
+
* requested lang → DEFAULT_LANG → the key itself (as a last-resort
|
|
39
|
+
* placeholder so the caller can spot the gap).
|
|
40
|
+
*/
|
|
41
|
+
export function t(key, { lang = DEFAULT_LANG, vars } = {}) {
|
|
42
|
+
const code = String(lang || DEFAULT_LANG).slice(0, 2).toLowerCase();
|
|
43
|
+
const dict = DICTS[code] || DICTS[DEFAULT_LANG];
|
|
44
|
+
const value = dict?.[key] ?? DICTS[DEFAULT_LANG]?.[key] ?? key;
|
|
45
|
+
return format(value, vars);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Lower-level: get the active dict, e.g. for bulk lookups in a loop. */
|
|
49
|
+
export function getDict(lang) {
|
|
50
|
+
const code = String(lang || DEFAULT_LANG).slice(0, 2).toLowerCase();
|
|
51
|
+
return DICTS[code] || DICTS[DEFAULT_LANG];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export { DICTS, DEFAULT_LANG };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Backend strings — Portuguese (pt).
|
|
2
|
+
export default {
|
|
3
|
+
"telegram.heads_up": "Já estou nisso… 🛠️",
|
|
4
|
+
"telegram.reset_ack": "Pronto, contexto limpo. Começando do zero — do que você precisa?",
|
|
5
|
+
"telegram.error_generic": "Algo quebrou do meu lado — já registrei.",
|
|
6
|
+
"telegram.fallback_listo": "Pronto.",
|
|
7
|
+
|
|
8
|
+
"common.unknown_error": "Algo deu errado.",
|
|
9
|
+
};
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
upsertContact,
|
|
18
18
|
upsertTelegramChannel,
|
|
19
19
|
} from "../config/index.js";
|
|
20
|
+
import { SENDER_ROLES } from "../constants/roles.js";
|
|
20
21
|
|
|
21
22
|
function telegramDisplayName(from) {
|
|
22
23
|
const full = [from?.first_name, from?.last_name].filter(Boolean).join(" ").trim();
|
|
@@ -62,7 +63,7 @@ export function resolveAllowedTools(cfg, sender) {
|
|
|
62
63
|
if (sender?.isOwner) return "*";
|
|
63
64
|
const def = cfg?.telegram?.roles?.[sender?.role];
|
|
64
65
|
if (def && def.tools !== undefined) return def.tools;
|
|
65
|
-
if (sender?.role ===
|
|
66
|
+
if (sender?.role === SENDER_ROLES.GUEST) return [];
|
|
66
67
|
return "*";
|
|
67
68
|
}
|
|
68
69
|
|