@agentprojectcontext/apx 1.33.1 → 1.34.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.
Files changed (169) hide show
  1. package/package.json +1 -1
  2. package/skills/apx/SKILL.md +49 -61
  3. package/src/core/agent/a2a/reply.js +48 -0
  4. package/src/core/agent/build-agent-system.js +4 -3
  5. package/src/core/agent/channels/voice-context.js +98 -0
  6. package/src/core/agent/memory.js +2 -1
  7. package/src/core/agent/prompt-builder.js +2 -1
  8. package/src/core/agent/prompts/modes/code-build.md +1 -0
  9. package/src/core/agent/prompts/modes/code-plan.md +1 -0
  10. package/src/core/agent/prompts/modes/index.js +28 -0
  11. package/src/core/agent/skills/loader.js +22 -18
  12. package/src/core/agent/stream/turn-accumulator.js +73 -0
  13. package/src/core/agent/suggestions.js +37 -0
  14. package/src/core/agent/tools/handlers/add-project.js +5 -2
  15. package/src/core/agent/tools/handlers/call-runtime.js +3 -2
  16. package/src/core/agent/tools/handlers/transcribe-audio.js +1 -1
  17. package/src/core/agent/tools/helpers.js +2 -2
  18. package/src/core/agent/tools/names.js +138 -0
  19. package/src/core/agent/tools/registry-bridge.js +6 -14
  20. package/src/core/agent/tools/registry.js +68 -65
  21. package/src/core/apc/context-copy.js +27 -0
  22. package/src/core/apc/notes.js +19 -0
  23. package/src/core/apc/parser.js +12 -5
  24. package/src/core/apc/paths.js +87 -0
  25. package/src/core/apc/scaffold.js +82 -76
  26. package/src/core/apc/skill-sync.js +10 -0
  27. package/src/{host/daemon/plugins → core/channels}/telegram/dispatch.js +38 -16
  28. package/src/core/config/index.js +3 -2
  29. package/src/core/config/redact.js +95 -0
  30. package/src/core/constants/channels.js +2 -0
  31. package/src/core/constants/code-modes.js +10 -0
  32. package/src/core/constants/index.js +1 -0
  33. package/src/core/deck/manifest.js +186 -0
  34. package/src/core/engines/catalog.js +83 -0
  35. package/src/core/{tools → http-tools}/browser.js +0 -1
  36. package/src/core/{tools → http-tools}/fetch.js +0 -1
  37. package/src/core/{tools → http-tools}/glob.js +0 -1
  38. package/src/core/{tools → http-tools}/grep.js +0 -1
  39. package/src/core/{tools → http-tools}/registry.js +0 -1
  40. package/src/core/{tools → http-tools}/search.js +0 -1
  41. package/src/core/i18n/en.js +9 -0
  42. package/src/core/i18n/es.js +12 -0
  43. package/src/core/i18n/index.js +54 -0
  44. package/src/core/i18n/pt.js +9 -0
  45. package/src/core/identity/telegram.js +2 -1
  46. package/src/core/mcp/runner.js +272 -14
  47. package/src/core/mcp/sources.js +3 -2
  48. package/src/core/routines/index.js +16 -0
  49. package/src/{host/daemon/routines.js → core/routines/runner.js} +36 -103
  50. package/src/core/runtime-skills/apc-context/SKILL.md +159 -0
  51. package/src/core/runtime-skills/apx/SKILL.md +95 -0
  52. package/src/core/runtime-skills/apx-mcp/SKILL.md +116 -0
  53. package/src/core/runtime-skills/{claude-code.md → claude-code/SKILL.md} +1 -0
  54. package/src/core/runtime-skills/{codex-cli.md → codex-cli/SKILL.md} +1 -0
  55. package/src/core/runtime-skills/{opencode-cli.md → opencode-cli/SKILL.md} +1 -0
  56. package/src/core/runtime-skills/{openrouter.md → openrouter/SKILL.md} +1 -0
  57. package/src/{host/daemon/env-detect.js → core/runtimes/detect.js} +1 -1
  58. package/src/core/stores/code-sessions.js +50 -2
  59. package/src/core/stores/routine-memory.js +1 -1
  60. package/src/core/stores/sessions-search.js +121 -0
  61. package/src/core/stores/sessions.js +38 -0
  62. package/src/core/vars/index.js +14 -0
  63. package/src/core/vars/interpolate.js +86 -0
  64. package/src/core/vars/sources.js +151 -0
  65. package/src/core/voice/audio-decode.js +38 -0
  66. package/src/core/voice/transcription.js +225 -0
  67. package/src/host/daemon/api/admin-config.js +5 -82
  68. package/src/host/daemon/api/agents.js +5 -5
  69. package/src/host/daemon/api/code.js +17 -169
  70. package/src/host/daemon/api/config.js +3 -4
  71. package/src/host/daemon/api/conversations.js +8 -29
  72. package/src/host/daemon/api/deck.js +37 -404
  73. package/src/host/daemon/api/engines.js +1 -80
  74. package/src/host/daemon/api/exec.js +1 -1
  75. package/src/host/daemon/api/mcps.js +32 -0
  76. package/src/host/daemon/api/routines.js +1 -1
  77. package/src/host/daemon/api/runtimes.js +4 -3
  78. package/src/host/daemon/api/sessions-search.js +24 -140
  79. package/src/host/daemon/api/sessions.js +12 -30
  80. package/src/host/daemon/api/shared.js +2 -1
  81. package/src/host/daemon/api/telegram.js +1 -11
  82. package/src/host/daemon/api/tools.js +6 -6
  83. package/src/host/daemon/api/transcribe.js +2 -2
  84. package/src/host/daemon/api/vars.js +137 -0
  85. package/src/host/daemon/api/voice.js +13 -290
  86. package/src/host/daemon/api.js +2 -0
  87. package/src/host/daemon/db.js +6 -6
  88. package/src/host/daemon/deck-exec.js +148 -0
  89. package/src/host/daemon/index.js +3 -3
  90. package/src/host/daemon/plugins/telegram/index.js +9 -9
  91. package/src/host/daemon/routines-scheduler.js +64 -0
  92. package/src/host/daemon/smoke.js +3 -2
  93. package/src/host/daemon/whisper-server.js +225 -0
  94. package/src/interfaces/cli/commands/agent.js +3 -2
  95. package/src/interfaces/cli/commands/command.js +2 -3
  96. package/src/interfaces/cli/commands/messages.js +6 -2
  97. package/src/interfaces/cli/commands/pair.js +5 -4
  98. package/src/interfaces/cli/commands/search.js +1 -1
  99. package/src/interfaces/cli/commands/sessions.js +3 -2
  100. package/src/interfaces/cli/commands/skills.js +36 -55
  101. package/src/interfaces/web/dist/assets/index-DdmSRtsz.css +1 -0
  102. package/src/interfaces/web/dist/assets/index-M4FspaCH.js +613 -0
  103. package/src/interfaces/web/dist/assets/index-M4FspaCH.js.map +1 -0
  104. package/src/interfaces/web/dist/index.html +2 -2
  105. package/src/interfaces/web/package-lock.json +182 -182
  106. package/src/interfaces/web/src/components/ModelCombobox.tsx +2 -1
  107. package/src/interfaces/web/src/components/TelegramChannelDialog.tsx +1 -1
  108. package/src/interfaces/web/src/components/chat/AskAnswersCard.tsx +76 -0
  109. package/src/interfaces/web/src/components/chat/MessageBubble.tsx +16 -3
  110. package/src/interfaces/web/src/components/chat/MessageList.tsx +23 -1
  111. package/src/interfaces/web/src/components/chat/ModelPicker.tsx +3 -1
  112. package/src/interfaces/web/src/components/code/CodeArtifactsTab.tsx +4 -4
  113. package/src/interfaces/web/src/components/code/CodeChangesTab.tsx +1 -1
  114. package/src/interfaces/web/src/components/code/CodeFileTree.tsx +3 -2
  115. package/src/interfaces/web/src/components/code/CodeFileViewer.tsx +3 -2
  116. package/src/interfaces/web/src/components/code/CodeTerminal.tsx +3 -2
  117. package/src/interfaces/web/src/components/config/GlobalConfigEditor.tsx +2 -1
  118. package/src/interfaces/web/src/components/deck/WidgetRow.tsx +2 -1
  119. package/src/interfaces/web/src/components/inputs/KeyValueList.tsx +93 -0
  120. package/src/interfaces/web/src/components/inputs/VarTokenInput.tsx +449 -0
  121. package/src/interfaces/web/src/components/settings/DefaultRouterCard.tsx +2 -1
  122. package/src/interfaces/web/src/components/settings/EnginesPanel.tsx +2 -2
  123. package/src/interfaces/web/src/components/settings/MemoryPanel.tsx +5 -4
  124. package/src/interfaces/web/src/components/settings/providers/ProviderCard.tsx +3 -2
  125. package/src/interfaces/web/src/components/settings/providers/ProviderModal.tsx +3 -2
  126. package/src/interfaces/web/src/components/ui/chat-input.tsx +5 -4
  127. package/src/interfaces/web/src/components/ui/sidebar.tsx +3 -2
  128. package/src/interfaces/web/src/components/voice/VoiceProviderModal.tsx +2 -1
  129. package/src/interfaces/web/src/constants/index.ts +1 -1
  130. package/src/interfaces/web/src/i18n/en.ts +174 -7
  131. package/src/interfaces/web/src/i18n/es.ts +179 -15
  132. package/src/interfaces/web/src/lib/api/mcps.ts +25 -0
  133. package/src/interfaces/web/src/lib/api/vars.ts +38 -0
  134. package/src/interfaces/web/src/lib/api.ts +1 -0
  135. package/src/interfaces/web/src/screens/ProjectScreen.tsx +8 -31
  136. package/src/interfaces/web/src/screens/modules/CodeScreen.tsx +1 -1
  137. package/src/interfaces/web/src/screens/modules/DeckScreen.tsx +4 -3
  138. package/src/interfaces/web/src/screens/modules/DesktopScreen.tsx +7 -6
  139. package/src/interfaces/web/src/screens/modules/VoiceScreen.tsx +4 -3
  140. package/src/interfaces/web/src/screens/project/AgentDetailScreen.tsx +1 -1
  141. package/src/interfaces/web/src/screens/project/ConfigTab.tsx +132 -1
  142. package/src/interfaces/web/src/screens/project/McpsTab.tsx +549 -104
  143. package/src/interfaces/web/src/screens/project/RoutinesTab.tsx +1 -1
  144. package/src/interfaces/web/src/screens/project/VarsTab.tsx +300 -0
  145. package/src/interfaces/web/src/types/daemon.ts +5 -0
  146. package/src/host/daemon/transcription.js +0 -538
  147. package/src/host/daemon/whisper-transcribe.py +0 -73
  148. package/src/interfaces/web/dist/assets/index-Aaiw8BZN.css +0 -1
  149. package/src/interfaces/web/dist/assets/index-DPqtjDjh.js +0 -602
  150. package/src/interfaces/web/dist/assets/index-DPqtjDjh.js.map +0 -1
  151. /package/src/{host/daemon → core/apc}/projects-helpers.js +0 -0
  152. /package/src/{host/daemon/plugins → core/channels}/telegram/ask.js +0 -0
  153. /package/src/{host/daemon/plugins → core/channels}/telegram/helpers.js +0 -0
  154. /package/src/{host/daemon/plugins → core/channels}/telegram/media.js +0 -0
  155. /package/src/core/{tools → http-tools}/index.js +0 -0
  156. /package/{skills → src/core/runtime-skills}/apx-agency-agents/SKILL.md +0 -0
  157. /package/{skills → src/core/runtime-skills}/apx-agent/SKILL.md +0 -0
  158. /package/{skills → src/core/runtime-skills}/apx-mcp-builder/SKILL.md +0 -0
  159. /package/{skills → src/core/runtime-skills}/apx-project/SKILL.md +0 -0
  160. /package/{skills → src/core/runtime-skills}/apx-routine/SKILL.md +0 -0
  161. /package/{skills → src/core/runtime-skills}/apx-runtime/SKILL.md +0 -0
  162. /package/{skills → src/core/runtime-skills}/apx-sessions/SKILL.md +0 -0
  163. /package/{skills → src/core/runtime-skills}/apx-skill-builder/SKILL.md +0 -0
  164. /package/{skills → src/core/runtime-skills}/apx-task/SKILL.md +0 -0
  165. /package/{skills → src/core/runtime-skills}/apx-telegram/SKILL.md +0 -0
  166. /package/{skills → src/core/runtime-skills}/apx-voice/SKILL.md +0 -0
  167. /package/src/{host/daemon/compact.js → core/stores/conversations-compactor.js} +0 -0
  168. /package/src/{host/daemon → core/stores}/conversations.js +0 -0
  169. /package/src/{host/daemon → core/util}/thinking.js +0 -0
@@ -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
+ }
@@ -1,4 +1,3 @@
1
- // daemon/tools/browser.js
2
1
  // Puppeteer-backed browser automation tools for APX.
3
2
  //
4
3
  // Logic adapted from the puppeteer-server MCP server
@@ -1,4 +1,3 @@
1
- // daemon/tools/fetch.js
2
1
  // Lightweight HTTP fetch tools — no Puppeteer, no Chromium. Starts in
3
2
  // milliseconds. Use this when you only need to hit an HTTP endpoint
4
3
  // (REST API, raw page HTML, JSON). For JS-rendered pages, real clicks,
@@ -1,4 +1,3 @@
1
- // daemon/tools/glob.js
2
1
  // Glob tool for APX — lists files matching a glob pattern.
3
2
  // Backends, in order of preference:
4
3
  // 1. fast-glob (npm) — full glob spec, brace expansion, negation patterns
@@ -1,4 +1,3 @@
1
- // daemon/tools/grep.js
2
1
  // Native Grep tool for APX — searches file contents by regex pattern.
3
2
  // Tries ripgrep (rg) first for speed, falls back to pure Node.js walk.
4
3
  //
@@ -1,4 +1,3 @@
1
- // daemon/tools/registry.js
2
1
  // Tool Registry on-demand for APX.
3
2
  //
4
3
  // Endpoints registered by api.js:
@@ -1,4 +1,3 @@
1
- // daemon/tools/search.js
2
1
  // WebSearch tool for APX — 3 modes:
3
2
  // 1. DuckDuckGo HTML scraping (no API key, uses node-fetch)
4
3
  // 2. Brave Search API (requires BRAVE_API_KEY env)
@@ -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 === "guest") return [];
66
+ if (sender?.role === SENDER_ROLES.GUEST) return [];
66
67
  return "*";
67
68
  }
68
69