@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.
Files changed (208) 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 +136 -59
  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 +178 -124
  8. package/src/core/agent/prompts/channels/code.md +12 -10
  9. package/src/core/agent/prompts/channels/desktop.md +5 -32
  10. package/src/core/agent/prompts/channels/telegram.md +4 -15
  11. package/src/core/agent/prompts/channels/web_code.md +11 -11
  12. package/src/core/agent/prompts/core/agent-base.md +24 -0
  13. package/src/core/agent/prompts/core/project-agent.md +11 -0
  14. package/src/core/agent/prompts/core/super-agent.md +21 -0
  15. package/src/core/agent/prompts/discipline/action.md +10 -0
  16. package/src/core/agent/prompts/discipline/single-segment.md +6 -0
  17. package/src/core/agent/prompts/discipline/two-segment.md +11 -0
  18. package/src/core/agent/prompts/modes/code-build.md +1 -0
  19. package/src/core/agent/prompts/modes/code-plan.md +1 -0
  20. package/src/core/agent/prompts/modes/index.js +28 -0
  21. package/src/core/agent/self-memory.js +43 -1
  22. package/src/core/agent/skills/index-store.js +307 -0
  23. package/src/core/agent/skills/index.js +15 -1
  24. package/src/core/agent/skills/inspector.js +317 -0
  25. package/src/core/agent/skills/loader.js +22 -18
  26. package/src/core/agent/stream/turn-accumulator.js +73 -0
  27. package/src/core/agent/suggestions.js +37 -0
  28. package/src/core/agent/super-agent.js +7 -1
  29. package/src/core/agent/tools/handlers/_git.js +50 -0
  30. package/src/core/agent/tools/handlers/add-project.js +5 -2
  31. package/src/core/agent/tools/handlers/call-runtime.js +3 -2
  32. package/src/core/agent/tools/handlers/git-diff.js +44 -0
  33. package/src/core/agent/tools/handlers/git-log.js +38 -0
  34. package/src/core/agent/tools/handlers/git-show.js +34 -0
  35. package/src/core/agent/tools/handlers/git-status.js +61 -0
  36. package/src/core/agent/tools/handlers/transcribe-audio.js +1 -1
  37. package/src/core/agent/tools/helpers.js +2 -2
  38. package/src/core/agent/tools/names.js +169 -0
  39. package/src/core/agent/tools/registry-bridge.js +6 -14
  40. package/src/core/agent/tools/registry.js +103 -69
  41. package/src/core/apc/context-copy.js +27 -0
  42. package/src/core/apc/notes.js +19 -0
  43. package/src/core/apc/parser.js +12 -5
  44. package/src/core/apc/paths.js +87 -0
  45. package/src/core/apc/scaffold.js +82 -76
  46. package/src/core/apc/skill-sync.js +10 -0
  47. package/src/{host/daemon/plugins → core/channels}/telegram/dispatch.js +38 -16
  48. package/src/core/config/index.js +24 -2
  49. package/src/core/config/redact.js +95 -0
  50. package/src/core/constants/channels.js +2 -0
  51. package/src/core/constants/code-modes.js +10 -0
  52. package/src/core/constants/index.js +1 -0
  53. package/src/core/deck/manifest.js +186 -0
  54. package/src/core/engines/catalog.js +83 -0
  55. package/src/core/{tools → http-tools}/browser.js +0 -1
  56. package/src/core/{tools → http-tools}/fetch.js +0 -1
  57. package/src/core/{tools → http-tools}/glob.js +0 -1
  58. package/src/core/{tools → http-tools}/grep.js +0 -1
  59. package/src/core/{tools → http-tools}/registry.js +0 -1
  60. package/src/core/{tools → http-tools}/search.js +0 -1
  61. package/src/core/i18n/en.js +9 -0
  62. package/src/core/i18n/es.js +12 -0
  63. package/src/core/i18n/index.js +54 -0
  64. package/src/core/i18n/pt.js +9 -0
  65. package/src/core/identity/telegram.js +2 -1
  66. package/src/core/mcp/runner.js +272 -14
  67. package/src/core/mcp/sources.js +3 -2
  68. package/src/core/routines/index.js +16 -0
  69. package/src/{host/daemon/routines.js → core/routines/runner.js} +36 -103
  70. package/src/core/runtime-skills/apc-context/SKILL.md +159 -0
  71. package/src/core/runtime-skills/apx/SKILL.md +83 -0
  72. package/src/core/runtime-skills/apx-agency-agents/SKILL.md +125 -0
  73. package/src/core/runtime-skills/apx-agent/SKILL.md +97 -0
  74. package/src/core/runtime-skills/apx-mcp/SKILL.md +111 -0
  75. package/src/core/runtime-skills/apx-mcp-builder/SKILL.md +169 -0
  76. package/{skills → src/core/runtime-skills}/apx-project/SKILL.md +20 -29
  77. package/src/core/runtime-skills/apx-routine/SKILL.md +127 -0
  78. package/src/core/runtime-skills/apx-runtime/SKILL.md +99 -0
  79. package/src/core/runtime-skills/apx-sessions/SKILL.md +232 -0
  80. package/src/core/runtime-skills/apx-skill-builder/SKILL.md +129 -0
  81. package/{skills → src/core/runtime-skills}/apx-task/SKILL.md +18 -21
  82. package/src/core/runtime-skills/apx-telegram/SKILL.md +120 -0
  83. package/src/core/runtime-skills/apx-voice/SKILL.md +117 -0
  84. package/src/core/runtime-skills/{claude-code.md → claude-code/SKILL.md} +1 -0
  85. package/src/core/runtime-skills/{codex-cli.md → codex-cli/SKILL.md} +1 -0
  86. package/src/core/runtime-skills/{opencode-cli.md → opencode-cli/SKILL.md} +1 -0
  87. package/src/core/runtime-skills/{openrouter.md → openrouter/SKILL.md} +1 -0
  88. package/src/{host/daemon/env-detect.js → core/runtimes/detect.js} +1 -1
  89. package/src/core/stores/code-sessions.js +50 -2
  90. package/src/core/stores/routine-memory.js +1 -1
  91. package/src/core/stores/sessions-search.js +121 -0
  92. package/src/core/stores/sessions.js +38 -0
  93. package/src/core/vars/index.js +14 -0
  94. package/src/core/vars/interpolate.js +86 -0
  95. package/src/core/vars/sources.js +151 -0
  96. package/src/core/voice/audio-decode.js +38 -0
  97. package/src/core/voice/transcription.js +225 -0
  98. package/src/host/daemon/api/admin-config.js +5 -82
  99. package/src/host/daemon/api/agents.js +5 -5
  100. package/src/host/daemon/api/code.js +17 -169
  101. package/src/host/daemon/api/config.js +3 -4
  102. package/src/host/daemon/api/conversations.js +8 -29
  103. package/src/host/daemon/api/deck.js +37 -404
  104. package/src/host/daemon/api/engines.js +1 -80
  105. package/src/host/daemon/api/exec.js +1 -1
  106. package/src/host/daemon/api/mcps.js +32 -0
  107. package/src/host/daemon/api/routines.js +1 -1
  108. package/src/host/daemon/api/runtimes.js +4 -3
  109. package/src/host/daemon/api/sessions-search.js +24 -140
  110. package/src/host/daemon/api/sessions.js +12 -30
  111. package/src/host/daemon/api/shared.js +2 -1
  112. package/src/host/daemon/api/skills.js +140 -6
  113. package/src/host/daemon/api/super-agent.js +56 -1
  114. package/src/host/daemon/api/telegram.js +1 -11
  115. package/src/host/daemon/api/tools.js +6 -6
  116. package/src/host/daemon/api/transcribe.js +2 -2
  117. package/src/host/daemon/api/vars.js +137 -0
  118. package/src/host/daemon/api/voice.js +13 -290
  119. package/src/host/daemon/api.js +2 -0
  120. package/src/host/daemon/db.js +6 -6
  121. package/src/host/daemon/deck-exec.js +148 -0
  122. package/src/host/daemon/index.js +20 -3
  123. package/src/host/daemon/plugins/telegram/index.js +9 -9
  124. package/src/host/daemon/routines-scheduler.js +64 -0
  125. package/src/host/daemon/smoke.js +3 -2
  126. package/src/host/daemon/whisper-server.js +225 -0
  127. package/src/interfaces/cli/branding.js +53 -0
  128. package/src/interfaces/cli/commands/agent.js +3 -2
  129. package/src/interfaces/cli/commands/command.js +2 -3
  130. package/src/interfaces/cli/commands/messages.js +6 -2
  131. package/src/interfaces/cli/commands/pair.js +5 -4
  132. package/src/interfaces/cli/commands/search.js +1 -1
  133. package/src/interfaces/cli/commands/sessions.js +3 -2
  134. package/src/interfaces/cli/commands/skills.js +290 -55
  135. package/src/interfaces/cli/index.js +84 -2
  136. package/src/interfaces/web/dist/assets/index-C0fm31dY.js +618 -0
  137. package/src/interfaces/web/dist/assets/index-C0fm31dY.js.map +1 -0
  138. package/src/interfaces/web/dist/assets/index-UcAqlBO6.css +1 -0
  139. package/src/interfaces/web/dist/index.html +2 -2
  140. package/src/interfaces/web/package-lock.json +182 -182
  141. package/src/interfaces/web/src/components/ModelCombobox.tsx +2 -1
  142. package/src/interfaces/web/src/components/TelegramChannelDialog.tsx +1 -1
  143. package/src/interfaces/web/src/components/chat/AskAnswersCard.tsx +76 -0
  144. package/src/interfaces/web/src/components/chat/MessageBubble.tsx +37 -4
  145. package/src/interfaces/web/src/components/chat/MessageList.tsx +23 -1
  146. package/src/interfaces/web/src/components/chat/ModelPicker.tsx +3 -1
  147. package/src/interfaces/web/src/components/code/CodeArtifactsTab.tsx +4 -4
  148. package/src/interfaces/web/src/components/code/CodeChangesTab.tsx +1 -1
  149. package/src/interfaces/web/src/components/code/CodeFileTree.tsx +3 -2
  150. package/src/interfaces/web/src/components/code/CodeFileViewer.tsx +3 -2
  151. package/src/interfaces/web/src/components/code/CodeTerminal.tsx +3 -2
  152. package/src/interfaces/web/src/components/config/GlobalConfigEditor.tsx +2 -1
  153. package/src/interfaces/web/src/components/deck/WidgetRow.tsx +2 -1
  154. package/src/interfaces/web/src/components/inputs/KeyValueList.tsx +93 -0
  155. package/src/interfaces/web/src/components/inputs/VarTokenInput.tsx +449 -0
  156. package/src/interfaces/web/src/components/settings/DefaultRouterCard.tsx +2 -1
  157. package/src/interfaces/web/src/components/settings/EnginesPanel.tsx +2 -2
  158. package/src/interfaces/web/src/components/settings/MemoryPanel.tsx +73 -4
  159. package/src/interfaces/web/src/components/settings/SkillsInspectorPanel.tsx +222 -0
  160. package/src/interfaces/web/src/components/settings/providers/ProviderCard.tsx +3 -2
  161. package/src/interfaces/web/src/components/settings/providers/ProviderModal.tsx +3 -2
  162. package/src/interfaces/web/src/components/ui/chat-input.tsx +5 -4
  163. package/src/interfaces/web/src/components/ui/sidebar.tsx +3 -2
  164. package/src/interfaces/web/src/components/voice/VoiceProviderModal.tsx +2 -1
  165. package/src/interfaces/web/src/constants/index.ts +1 -1
  166. package/src/interfaces/web/src/hooks/useChat.ts +19 -0
  167. package/src/interfaces/web/src/i18n/en.ts +175 -7
  168. package/src/interfaces/web/src/i18n/es.ts +180 -15
  169. package/src/interfaces/web/src/lib/api/mcps.ts +25 -0
  170. package/src/interfaces/web/src/lib/api/skills.ts +70 -0
  171. package/src/interfaces/web/src/lib/api/vars.ts +38 -0
  172. package/src/interfaces/web/src/lib/api.ts +1 -0
  173. package/src/interfaces/web/src/screens/ProjectScreen.tsx +8 -31
  174. package/src/interfaces/web/src/screens/SettingsScreen.tsx +6 -2
  175. package/src/interfaces/web/src/screens/modules/CodeScreen.tsx +1 -1
  176. package/src/interfaces/web/src/screens/modules/DeckScreen.tsx +4 -3
  177. package/src/interfaces/web/src/screens/modules/DesktopScreen.tsx +7 -6
  178. package/src/interfaces/web/src/screens/modules/VoiceScreen.tsx +4 -3
  179. package/src/interfaces/web/src/screens/project/AgentDetailScreen.tsx +1 -1
  180. package/src/interfaces/web/src/screens/project/ConfigTab.tsx +132 -1
  181. package/src/interfaces/web/src/screens/project/McpsTab.tsx +549 -104
  182. package/src/interfaces/web/src/screens/project/RoutinesTab.tsx +1 -1
  183. package/src/interfaces/web/src/screens/project/VarsTab.tsx +300 -0
  184. package/src/interfaces/web/src/types/daemon.ts +15 -0
  185. package/skills/apx-agency-agents/SKILL.md +0 -141
  186. package/skills/apx-agent/SKILL.md +0 -100
  187. package/skills/apx-mcp-builder/SKILL.md +0 -183
  188. package/skills/apx-routine/SKILL.md +0 -140
  189. package/skills/apx-runtime/SKILL.md +0 -117
  190. package/skills/apx-sessions/SKILL.md +0 -281
  191. package/skills/apx-skill-builder/SKILL.md +0 -153
  192. package/skills/apx-telegram/SKILL.md +0 -131
  193. package/skills/apx-voice/SKILL.md +0 -137
  194. package/src/core/agent/prompts/action-discipline.md +0 -24
  195. package/src/core/agent/prompts/super-agent-base.md +0 -42
  196. package/src/host/daemon/transcription.js +0 -538
  197. package/src/host/daemon/whisper-transcribe.py +0 -73
  198. package/src/interfaces/web/dist/assets/index-Aaiw8BZN.css +0 -1
  199. package/src/interfaces/web/dist/assets/index-DPqtjDjh.js +0 -602
  200. package/src/interfaces/web/dist/assets/index-DPqtjDjh.js.map +0 -1
  201. /package/src/{host/daemon → core/apc}/projects-helpers.js +0 -0
  202. /package/src/{host/daemon/plugins → core/channels}/telegram/ask.js +0 -0
  203. /package/src/{host/daemon/plugins → core/channels}/telegram/helpers.js +0 -0
  204. /package/src/{host/daemon/plugins → core/channels}/telegram/media.js +0 -0
  205. /package/src/core/{tools → http-tools}/index.js +0 -0
  206. /package/src/{host/daemon/compact.js → core/stores/conversations-compactor.js} +0 -0
  207. /package/src/{host/daemon → core/stores}/conversations.js +0 -0
  208. /package/src/{host/daemon → core/util}/thinking.js +0 -0
@@ -0,0 +1,27 @@
1
+ // Snapshot a project's read-aloud context — AGENTS.md + .apc/memory.md —
2
+ // into a single labelled markdown string. Used by /deck/context/copy and
3
+ // (potentially) any other "give me the context" surface.
4
+ import fs from "node:fs/promises";
5
+ import { AGENTS_MD, apcMemoryFile } from "./paths.js";
6
+ import path from "node:path";
7
+
8
+ const CONTEXT_FILES = [
9
+ { rel: AGENTS_MD, label: AGENTS_MD },
10
+ { rel: `.apc/${"memory.md"}`, label: ".apc/memory.md", abs: (root) => apcMemoryFile(root) },
11
+ ];
12
+
13
+ export async function readProjectContext(projectPath) {
14
+ const chunks = [];
15
+ for (const entry of CONTEXT_FILES) {
16
+ const abs = entry.abs ? entry.abs(projectPath) : path.join(projectPath, entry.rel);
17
+ try {
18
+ const content = await fs.readFile(abs, "utf8");
19
+ if (content.trim()) {
20
+ chunks.push(`# ${entry.label}\n\n${content.trim()}\n`);
21
+ }
22
+ } catch {
23
+ // missing file is fine; we just skip it.
24
+ }
25
+ }
26
+ return chunks.join("\n---\n\n");
27
+ }
@@ -0,0 +1,19 @@
1
+ // Append-only project notes under .apc/notes/YYYY-MM-DD.md. Each note is a
2
+ // timestamped markdown block; the file is append-only so no UID management
3
+ // is required.
4
+ import fs from "node:fs/promises";
5
+ import path from "node:path";
6
+ import { apcNotesDir } from "./paths.js";
7
+
8
+ export async function appendProjectNote(projectPath, { title, body }) {
9
+ const notesDir = apcNotesDir(projectPath);
10
+ await fs.mkdir(notesDir, { recursive: true });
11
+ const today = new Date().toISOString().slice(0, 10);
12
+ const file = path.join(notesDir, `${today}.md`);
13
+ const ts = new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
14
+ const block = title
15
+ ? `\n## ${title}\n_${ts}_\n\n${body}\n`
16
+ : `\n### ${ts}\n\n${body}\n`;
17
+ await fs.appendFile(file, block, "utf8");
18
+ return file;
19
+ }
@@ -1,6 +1,13 @@
1
1
  // Core parsers for APC — pure ESM, no deps.
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
+ import {
5
+ apcAgentsDir,
6
+ apcAgentFile,
7
+ apcProjectFile,
8
+ agentsMdFile,
9
+ isApcProject,
10
+ } from "./paths.js";
4
11
 
5
12
  export const SLUG_RE = /^[a-z][a-z0-9_-]*$/;
6
13
  const H1_RE = /^#\s+Agents\s*$/i;
@@ -91,7 +98,7 @@ export function parseAgentFile(slug, text) {
91
98
 
92
99
  // Read all .apc/agents/<slug>.md files. Returns [] if none exist.
93
100
  export function readAgentsFromDir(root) {
94
- const dir = path.join(root, ".apc", "agents");
101
+ const dir = apcAgentsDir(root);
95
102
  if (!fs.existsSync(dir)) return [];
96
103
  return fs
97
104
  .readdirSync(dir)
@@ -189,7 +196,7 @@ function readVaultAgent(slug, { includeRemoved = false } = {}) {
189
196
 
190
197
  // Resolve a single agent for a project: local file → vault (layered) → null.
191
198
  export function resolveAgent(root, slug) {
192
- const localPath = path.join(root, ".apc", "agents", `${slug}.md`);
199
+ const localPath = apcAgentFile(root, slug);
193
200
  if (fs.existsSync(localPath)) {
194
201
  const agent = parseAgentFile(slug, fs.readFileSync(localPath, "utf8"));
195
202
  return { ...agent, source: "local" };
@@ -203,7 +210,7 @@ export { readVaultAgent };
203
210
 
204
211
  // Return slugs imported from vault in this project (from project.json)
205
212
  export function importedVaultSlugs(root) {
206
- const p = path.join(root, ".apc", "project.json");
213
+ const p = apcProjectFile(root);
207
214
  if (!fs.existsSync(p)) return [];
208
215
  try {
209
216
  const cfg = JSON.parse(fs.readFileSync(p, "utf8"));
@@ -235,7 +242,7 @@ export function readAgents(root) {
235
242
  const all = [...fromFiles, ...vaultAgents];
236
243
  const allSlugs = new Set(all.map((a) => a.slug));
237
244
 
238
- const agentsMdPath = path.join(root, "AGENTS.md");
245
+ const agentsMdPath = agentsMdFile(root);
239
246
  if (!fs.existsSync(agentsMdPath)) return all;
240
247
 
241
248
  const mdText = fs.readFileSync(agentsMdPath, "utf8");
@@ -255,7 +262,7 @@ export function readAgents(root) {
255
262
  export function findApfRoot(start = process.cwd()) {
256
263
  let cur = path.resolve(start);
257
264
  while (true) {
258
- if (fs.existsSync(path.join(cur, ".apc", "project.json"))) return cur;
265
+ if (isApcProject(cur)) return cur;
259
266
  const parent = path.dirname(cur);
260
267
  if (parent === cur) return null;
261
268
  cur = parent;
@@ -0,0 +1,87 @@
1
+ // Filesystem layout of an APC project. The `.apc/` folder is APX's footprint
2
+ // inside someone else's repo — everything that the daemon writes to a
3
+ // user-checked-out project lives here, plus the top-level AGENTS.md spec.
4
+ //
5
+ // Use the helpers below instead of joining the literal names yourself. A typo
6
+ // in `".apc"` or `"project.json"` will silently create an orphan tree instead
7
+ // of failing loud, which is exactly the bug class these constants prevent.
8
+ import fs from "node:fs";
9
+ import path from "node:path";
10
+
11
+ // Raw names — exported for the rare caller that needs to glob/match by name.
12
+ export const APC_DIR = ".apc";
13
+ export const APC_PROJECT_FILE = "project.json";
14
+ export const APC_PROJECT_CONFIG_FILE = "config.json";
15
+ export const APC_PROJECT_MEMORY_FILE = "memory.md";
16
+ export const APC_AGENTS_DIR = "agents";
17
+ export const APC_SKILLS_DIR = "skills";
18
+ export const APC_COMMANDS_DIR = "commands";
19
+ export const APC_NOTES_DIR = "notes";
20
+ export const APC_MCPS_FILE = "mcps.json";
21
+ export const APC_REMOVED_FILE = ".removed.json";
22
+ export const AGENTS_MD = "AGENTS.md";
23
+
24
+ // Path builders. `root` is the project root (the directory that contains
25
+ // `.apc/` and `AGENTS.md`).
26
+ export function apcDir(root) {
27
+ return path.join(root, APC_DIR);
28
+ }
29
+
30
+ export function apcProjectFile(root) {
31
+ return path.join(root, APC_DIR, APC_PROJECT_FILE);
32
+ }
33
+
34
+ export function apcProjectConfigFile(root) {
35
+ return path.join(root, APC_DIR, APC_PROJECT_CONFIG_FILE);
36
+ }
37
+
38
+ export function apcAgentsDir(root) {
39
+ return path.join(root, APC_DIR, APC_AGENTS_DIR);
40
+ }
41
+
42
+ export function apcAgentFile(root, slug) {
43
+ return path.join(root, APC_DIR, APC_AGENTS_DIR, `${slug}.md`);
44
+ }
45
+
46
+ export function apcAgentMemoryFile(root, slug) {
47
+ return path.join(root, APC_DIR, APC_AGENTS_DIR, slug, "memory.md");
48
+ }
49
+
50
+ export function apcRemovedFile(root) {
51
+ return path.join(root, APC_DIR, APC_AGENTS_DIR, APC_REMOVED_FILE);
52
+ }
53
+
54
+ export function apcSkillsDir(root) {
55
+ return path.join(root, APC_DIR, APC_SKILLS_DIR);
56
+ }
57
+
58
+ export function apcSkillFile(root, slug) {
59
+ return path.join(root, APC_DIR, APC_SKILLS_DIR, `${slug}.md`);
60
+ }
61
+
62
+ export function apcCommandsDir(root) {
63
+ return path.join(root, APC_DIR, APC_COMMANDS_DIR);
64
+ }
65
+
66
+ export function apcNotesDir(root) {
67
+ return path.join(root, APC_DIR, APC_NOTES_DIR);
68
+ }
69
+
70
+ // Project-level memory (super-agent / project-wide), distinct from the
71
+ // per-agent memory.md that lives under .apc/agents/<slug>/.
72
+ export function apcMemoryFile(root) {
73
+ return path.join(root, APC_DIR, APC_PROJECT_MEMORY_FILE);
74
+ }
75
+
76
+ export function apcMcpsFile(root) {
77
+ return path.join(root, APC_DIR, APC_MCPS_FILE);
78
+ }
79
+
80
+ export function agentsMdFile(root) {
81
+ return path.join(root, AGENTS_MD);
82
+ }
83
+
84
+ // True when `root` looks like an initialized APC project (has the marker file).
85
+ export function isApcProject(root) {
86
+ return fs.existsSync(apcProjectFile(root));
87
+ }
@@ -11,14 +11,22 @@ import {
11
11
  } from "./parser.js";
12
12
  import { readApcContextSkill } from "./skill-sync.js";
13
13
  import { nowIso } from "../util/time.js";
14
+ import {
15
+ apcDir,
16
+ apcProjectFile,
17
+ apcAgentsDir,
18
+ apcAgentFile,
19
+ agentsMdFile,
20
+ } from "./paths.js";
14
21
 
15
22
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
16
23
  // Now under src/core/apc/ — one more "../" to escape than before.
17
24
  const PACKAGE_ROOT = path.resolve(__dirname, "..", "..", "..");
25
+ // <packageRoot>/skills/<slug>/SKILL.md — the SLIM engine-side set: every skill
26
+ // here is replicated verbatim into project IDE rule files (`apx skills add`) and
27
+ // into ~/.<host>/skills/ (`apx skills sync`). The rich super-agent catalog lives
28
+ // at src/core/runtime-skills/ and is intentionally NOT copied out.
18
29
  const BUNDLED_SKILLS_DIR = path.join(PACKAGE_ROOT, "skills");
19
- // runtime-skills lives at src/core/runtime-skills/, one level up from this
20
- // file's new home in src/core/apc/ (was a sibling before the Phase 3 move).
21
- const RUNTIME_SKILLS_DIR = path.join(__dirname, "..", "runtime-skills");
22
30
 
23
31
  export const SPEC_VERSION = "0.1.0";
24
32
 
@@ -28,12 +36,12 @@ export const SPEC_VERSION = "0.1.0";
28
36
  // install/update from the canonical APC repo (see src/interfaces/cli/postinstall.js).
29
37
  // ---------------------------------------------------------------------------
30
38
 
31
- // Bundled skills apx lives in skills/apx/. apc-context is synced from
32
- // the canonical APC repo (../apc or GitHub) never edited in APX.
39
+ // Read one slim skill from <packageRoot>/skills/<slug>/SKILL.md. `apc-context`
40
+ // is special-cased to refresh from the canonical APC repo if available.
33
41
  function readBundledSkill(slug) {
34
42
  if (slug === "apc-context") {
35
43
  const synced = readApcContextSkill();
36
- return synced?.text || null;
44
+ if (synced?.text) return synced.text;
37
45
  }
38
46
  const file = path.join(BUNDLED_SKILLS_DIR, slug, "SKILL.md");
39
47
  if (!fs.existsSync(file)) return null;
@@ -134,19 +142,11 @@ const GLOBAL_SKILL_DIRS = [
134
142
  path.join(os.homedir(), ".agents", "skills"), // Antigravity/other skills.sh adopters
135
143
  ];
136
144
 
137
- function readRuntimeSkillFiles() {
138
- if (!fs.existsSync(RUNTIME_SKILLS_DIR)) return [];
139
- return fs.readdirSync(RUNTIME_SKILLS_DIR)
140
- .filter((name) => name.endsWith(".md"))
141
- .sort()
142
- .map((name) => ({
143
- slug: path.basename(name, ".md"),
144
- md: fs.readFileSync(path.join(RUNTIME_SKILLS_DIR, name), "utf8").trim(),
145
- }));
146
- }
147
-
148
145
  // Install APX + APC context skills into IDE rule files. Returns an array of result objects.
149
146
  // targetIds: array of target ids to install; null = all project targets.
147
+ // Writes the slim engine-side skill from <packageRoot>/skills/. The rich
148
+ // super-agent set in src/core/runtime-skills/ is intentionally never written
149
+ // into project IDE files.
150
150
  export function installIdeSkills(root, targetIds = null) {
151
151
  const apxRaw = readBundledSkill("apx");
152
152
  const apcRaw = readBundledSkill("apc-context");
@@ -197,36 +197,27 @@ export function installIdeSkills(root, targetIds = null) {
197
197
  // on the user's machine after `npm install -g .` (or `npm update -g apx`)
198
198
  // without anyone having to touch this file.
199
199
  //
200
- // Excluded: directory names starting with "." (e.g. .DS_Store), and any
201
- // runtime-only CLI skill that lives under src/core/runtime-skills/ those
202
- // are loaded in-process at daemon startup and are NOT for IDE consumption.
203
- // Public: bundled skill slugs grouped by scope.
204
- // public → pushed to every global skill dir on install / sync (default).
205
- // optional → not pushed by default; user opts in with --include-optional
206
- // or `apx skills add <slug> --global` for one-off install.
207
- // internal → APX-developer skills (mcp-builder, skill-builder, etc.); never
208
- // pushed globally, only available to APX itself via the bundled
209
- // copy. Avoids cluttering other IDEs with stuff their users won't
210
- // run.
200
+ // Excluded: directory names starting with "." (e.g. .DS_Store).
201
+ // Every slug under <packageRoot>/skills/ is part of the slim engine set and
202
+ // gets pushed to global dirs on `apx skills sync`. No scope filtering — the
203
+ // dir IS the contract.
211
204
  export function listBundledSkillSlugs() {
212
205
  return discoverBundledSkills().map((s) => s.slug);
213
206
  }
214
207
 
215
208
  export function listBundledSkills() {
216
- return discoverBundledSkills().map(({ slug, scope }) => ({ slug, scope }));
209
+ return discoverBundledSkills().map(({ slug }) => ({ slug }));
210
+ }
211
+
212
+ // Backwards-compat alias — every bundled slug here IS an engine slug now.
213
+ export function listEngineSkills() {
214
+ return listBundledSkills();
217
215
  }
218
216
 
219
- // Tiny frontmatter peek we only need the `scope:` field. Avoids pulling in
220
- // a full YAML parser for one optional line.
221
- function parseFrontmatterScope(md) {
222
- if (!md.startsWith("---\n")) return "public";
223
- const end = md.indexOf("\n---", 4);
224
- if (end === -1) return "public";
225
- const m = md.slice(4, end).match(/^scope:\s*(\w+)/m);
226
- if (!m) return "public";
227
- const s = m[1].toLowerCase();
228
- if (s === "internal" || s === "optional" || s === "public") return s;
229
- return "public";
217
+ // Legacy slugs APX used to ship to global dirs but no longer does — exposed so
218
+ // the CLI can report what `installGlobalSkills` will prune.
219
+ export function listLegacyPruneSlugs() {
220
+ return [...PRUNE_LEGACY_SLUGS];
230
221
  }
231
222
 
232
223
  function discoverBundledSkills() {
@@ -239,65 +230,80 @@ function discoverBundledSkills() {
239
230
  const skillFile = path.join(root, entry.name, "SKILL.md");
240
231
  if (!fs.existsSync(skillFile)) continue;
241
232
  const md = fs.readFileSync(skillFile, "utf8");
242
- out.push({ slug: entry.name, md, scope: parseFrontmatterScope(md) });
233
+ out.push({ slug: entry.name, md });
243
234
  }
244
235
  return out.sort((a, b) => a.slug.localeCompare(b.slug));
245
236
  }
246
237
 
247
- // Install bundled skills to every global ~/.../skills/ dir so Claude Code,
248
- // Cursor, Codex, and other IDEs see them.
238
+ // Install the slim engine skill set to every global ~/.../skills/ dir
239
+ // (Claude Code, Cursor, Codex, Antigravity/skills.sh). External engines only
240
+ // need to know how to talk TO apx — not the full APX sub-skill catalog. The
241
+ // rich bundled set in skills/<slug>/ stays in-repo for the APX super-agent.
242
+ //
243
+ // The set lives at skills/engines/<slug>/SKILL.md and is currently:
244
+ // apx, apx-mcp, apc-context.
249
245
  //
250
- // By default only `scope: public` skills land globally. Pass
251
- // includeOptional / includeInternal to push the other tiers (or call
252
- // `apx skills add <slug> --global` for a single one).
246
+ // `includeOptional` / `includeInternal` are kept as no-op flags for backward
247
+ // compatibility with `apx skills sync --include-…`; the slim set has no tiers.
253
248
  //
254
- // Pruning: if a slug that was previously installed has since been demoted to
255
- // internal/optional (or marked as non-public for the current call), we remove
256
- // the stale global copy unless prune=false. Keeps IDE skill lists clean.
249
+ // Pruning: removes stale APX-shipped slugs that are no longer in the engine
250
+ // set (the catalog of slugs APX has ever published, see PRUNE_LEGACY_SLUGS).
251
+ // Skills the user installed themselves are NOT touched.
257
252
  //
258
253
  // Returns an array of { dir, skill, file, status, scope }.
259
- // status ∈ {created, updated, unchanged, pruned, skipped}
254
+ // status ∈ {created, updated, unchanged, pruned}
255
+ const PRUNE_LEGACY_SLUGS = [
256
+ "apx-agency-agents",
257
+ "apx-agent",
258
+ "apx-mcp-builder",
259
+ "apx-project",
260
+ "apx-routine",
261
+ "apx-runtime",
262
+ "apx-sessions",
263
+ "apx-skill-builder",
264
+ "apx-task",
265
+ "apx-telegram",
266
+ "apx-voice",
267
+ // Runtime CLI docs that previously leaked into global dirs — these are
268
+ // loaded in-process by the daemon and should NOT live on disk in IDE skill
269
+ // dirs.
270
+ "claude-code",
271
+ "codex-cli",
272
+ "opencode-cli",
273
+ "openrouter",
274
+ ];
275
+
260
276
  export function installGlobalSkills({
261
- includeOptional = false,
262
- includeInternal = false,
263
277
  prune = true,
278
+ // No-ops kept for CLI backward compatibility (the slim engine set has no tiers).
279
+ includeOptional: _includeOptional = false,
280
+ includeInternal: _includeInternal = false,
264
281
  } = {}) {
265
- const all = discoverBundledSkills();
266
- const wanted = all.filter((s) => {
267
- if (s.scope === "internal") return includeInternal;
268
- if (s.scope === "optional") return includeOptional;
269
- return true; // public
270
- });
282
+ const wanted = discoverBundledSkills();
271
283
  const wantedSlugs = new Set(wanted.map((s) => s.slug));
272
- const knownSlugs = new Set(all.map((s) => s.slug));
273
284
 
274
285
  const results = [];
275
286
  for (const base of GLOBAL_SKILL_DIRS) {
276
- // Push the wanted set.
277
- for (const { slug, md, scope } of wanted) {
287
+ for (const { slug, md } of wanted) {
278
288
  const dest = path.join(base, slug, "SKILL.md");
279
289
  fs.mkdirSync(path.dirname(dest), { recursive: true });
280
290
  const existed = fs.existsSync(dest);
281
291
  const previous = existed ? fs.readFileSync(dest, "utf8") : null;
282
292
  if (previous === md) {
283
- results.push({ dir: base, skill: slug, file: dest, status: "unchanged", scope });
293
+ results.push({ dir: base, skill: slug, file: dest, status: "unchanged", scope: "engine" });
284
294
  continue;
285
295
  }
286
296
  fs.writeFileSync(dest, md, "utf8");
287
- results.push({ dir: base, skill: slug, file: dest, status: existed ? "updated" : "created", scope });
297
+ results.push({ dir: base, skill: slug, file: dest, status: existed ? "updated" : "created", scope: "engine" });
288
298
  }
289
- // Prune anything WE shipped previously but should no longer be there
290
- // (slug exists in the bundle but isn't `wanted` this run).
291
299
  if (prune) {
292
- for (const { slug, scope } of all) {
300
+ for (const slug of PRUNE_LEGACY_SLUGS) {
293
301
  if (wantedSlugs.has(slug)) continue;
294
- if (!knownSlugs.has(slug)) continue;
295
302
  const dest = path.join(base, slug, "SKILL.md");
296
303
  if (!fs.existsSync(dest)) continue;
297
304
  fs.unlinkSync(dest);
298
- // Best-effort: drop the now-empty <slug>/ dir too.
299
305
  try { fs.rmdirSync(path.dirname(dest)); } catch {}
300
- results.push({ dir: base, skill: slug, file: dest, status: "pruned", scope });
306
+ results.push({ dir: base, skill: slug, file: dest, status: "pruned", scope: "legacy" });
301
307
  }
302
308
  }
303
309
  }
@@ -423,7 +429,7 @@ function writeMigrateMd(apfDir, found) {
423
429
  // Get the stable APX storage ID for a project, generating one if it doesn't exist.
424
430
  // Called by the daemon when registering a project.
425
431
  export function getOrCreateApxId(root) {
426
- const p = path.join(root, ".apc", "project.json");
432
+ const p = apcProjectFile(root);
427
433
  if (!fs.existsSync(p)) return null;
428
434
  let cfg;
429
435
  try { cfg = JSON.parse(fs.readFileSync(p, "utf8")); } catch { return null; }
@@ -439,7 +445,7 @@ export function initApf(directory, { name } = {}) {
439
445
  const root = path.resolve(directory);
440
446
  fs.mkdirSync(root, { recursive: true });
441
447
 
442
- const apfDir = path.join(root, ".apc");
448
+ const apfDir = apcDir(root);
443
449
  fs.mkdirSync(path.join(apfDir, "agents"), { recursive: true });
444
450
  fs.mkdirSync(path.join(apfDir, "skills"), { recursive: true });
445
451
  fs.mkdirSync(path.join(apfDir, "commands"), { recursive: true });
@@ -469,7 +475,7 @@ export function initApf(directory, { name } = {}) {
469
475
  fs.writeFileSync(gitignore, APC_GITIGNORE);
470
476
  }
471
477
 
472
- const agentsMd = path.join(root, "AGENTS.md");
478
+ const agentsMd = agentsMdFile(root);
473
479
  if (!fs.existsSync(agentsMd)) {
474
480
  fs.writeFileSync(agentsMd, AGENTS_MD_TEMPLATE);
475
481
  }
@@ -485,13 +491,13 @@ export function initApf(directory, { name } = {}) {
485
491
  }
486
492
 
487
493
  export function ensureAgentDir(root, slug) {
488
- fs.mkdirSync(path.join(root, ".apc", "agents"), { recursive: true });
489
- return path.join(root, ".apc", "agents");
494
+ fs.mkdirSync(apcAgentsDir(root), { recursive: true });
495
+ return apcAgentsDir(root);
490
496
  }
491
497
 
492
498
  // Write .apc/agents/<slug>.md — the canonical agent definition file.
493
499
  export function writeAgentFile(root, slug, fields, body = "") {
494
- const dest = path.join(root, ".apc", "agents", `${slug}.md`);
500
+ const dest = apcAgentFile(root, slug);
495
501
  const lines = ["---"];
496
502
  const order = ["role", "model", "language", "description", "skills", "tools"];
497
503
  const written = new Set();
@@ -579,7 +585,7 @@ export function restoreVaultAgent(slug) {
579
585
 
580
586
  // Add a slug to the project's agents.imported list in project.json
581
587
  export function addImportedAgent(root, slug) {
582
- const p = path.join(root, ".apc", "project.json");
588
+ const p = apcProjectFile(root);
583
589
  let cfg = {};
584
590
  try { cfg = JSON.parse(fs.readFileSync(p, "utf8")); } catch {}
585
591
  if (!cfg.agents) cfg.agents = {};
@@ -8,7 +8,10 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
8
  // Repo root is three levels up, not two.
9
9
  export const PACKAGE_ROOT = path.resolve(__dirname, "..", "..", "..");
10
10
 
11
+ // Engine-side slim copy (replicated to ~/.<host>/skills/ by apx skills sync).
11
12
  export const APC_SKILL_REL = path.join("skills", "apc-context", "SKILL.md");
13
+ // Runtime-internal copy (loaded by the super-agent — never published outside).
14
+ export const APC_BUILTIN_SKILL_REL = path.join("src", "core", "runtime-skills", "apc-context", "SKILL.md");
12
15
  export const APC_SKILL_REMOTE =
13
16
  "https://raw.githubusercontent.com/agentprojectcontext/agentprojectcontext/main/skills/apc-context/SKILL.md";
14
17
 
@@ -95,5 +98,12 @@ export async function refreshApcContextSkill({ packageRoot = PACKAGE_ROOT, timeo
95
98
 
96
99
  fs.mkdirSync(path.dirname(dest), { recursive: true });
97
100
  fs.writeFileSync(dest, text, "utf8");
101
+
102
+ // Keep the runtime-internal copy (src/core/runtime-skills/apc-context/) in
103
+ // sync — that's where the super-agent loads it from.
104
+ const builtinDest = path.join(packageRoot, APC_BUILTIN_SKILL_REL);
105
+ fs.mkdirSync(path.dirname(builtinDest), { recursive: true });
106
+ fs.writeFileSync(builtinDest, text, "utf8");
107
+
98
108
  return { ok: true, source, refreshed: true };
99
109
  }
@@ -1,10 +1,38 @@
1
1
  // Inbound Telegram update dispatcher.
2
2
  //
3
- // Extracted from the ChannelPoller class so the index.js stays under ~800
4
- // lines and the routing logic for text/photo/voice/document updates lives in
5
- // its own module. The function takes the poller instance as `self`; every
6
- // `this.X` in the original method becomes `self.X` here. The poller exposes
7
- // _handleUpdate as a thin facade that delegates to this function.
3
+ // Extracted from the ChannelPoller class so index.js stays under ~800 lines
4
+ // and the routing logic for text/photo/voice/document updates lives on its
5
+ // own. Takes the poller instance as `self`; every `this.X` in the original
6
+ // method becomes `self.X` here. The poller exposes _handleUpdate as a thin
7
+ // facade that delegates to handleUpdate(this, u).
8
+ //
9
+ // IMPORTANT: this module needs the same imports the original index.js had
10
+ // in module scope, because the extracted body references identifiers like
11
+ // `appendGlobalMessage`, `CHANNELS`, `nowIso`, etc. Top-level imports here
12
+ // keep that scope intact — earlier splits forgot them and the bug only
13
+ // surfaced when a real telegram update arrived (ReferenceError at runtime).
14
+ import path from "node:path";
15
+ import { callEngine } from "#core/engines/index.js";
16
+ import { runSuperAgent, isSuperAgentEnabled } from "#core/agent/super-agent.js";
17
+ import { stripThinking } from "#core/util/thinking.js";
18
+ import { getRecentTelegramTurnsFromFs, appendGlobalMessage } from "#core/stores/messages.js";
19
+ import { compactChannelIfNeeded } from "#core/memory/index.js";
20
+ import { readAgents } from "#core/apc/parser.js";
21
+ import { buildAgentSystem } from "#core/agent/build-agent-system.js";
22
+ import { transcribe as transcribeAudioFile } from "#core/voice/transcription.js";
23
+ import { resolveAgentName, SUPERAGENT_ACTOR_ID } from "#core/identity/index.js";
24
+ import { registerSender, resolveAllowedTools } from "#core/identity/telegram.js";
25
+ import { buildRelationshipBlock } from "#core/agent/index.js";
26
+ import { getConfirmationStore as getConfirmStore } from "#core/confirmation/pending-store.js";
27
+ import { CHANNELS } from "#core/constants/channels.js";
28
+ import { tryResolveSkillCommand } from "#core/agent/skills/trigger.js";
29
+ import { createTelegramConfirmAdapter } from "#core/confirmation/adapters/telegram.js";
30
+ import * as askFlow from "./ask.js";
31
+ import { buildTelegramMeta, resolveBotToken, sleep } from "./helpers.js";
32
+ import { sendPhoto, sendVoice, sendDocument, sendAudio, downloadTelegramFile, API_BASE } from "./media.js";
33
+ import { t, resolveLang } from "#core/i18n/index.js";
34
+
35
+ const nowIso = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
8
36
 
9
37
  export async function handleUpdate(self, u) {
10
38
  self.lastUpdateAt = nowIso();
@@ -259,7 +287,7 @@ export async function handleUpdate(self, u) {
259
287
  // honor it for future messages.
260
288
  if (isReset) {
261
289
  try {
262
- const ack = "Done, context cleared. Starting fresh. What do you need?";
290
+ const ack = t("telegram.reset_ack", { lang: resolveLang(self.globalConfig) });
263
291
  await self._send({ chat_id, text: ack });
264
292
  appendGlobalMessage({
265
293
  channel: CHANNELS.TELEGRAM,
@@ -343,15 +371,7 @@ export async function handleUpdate(self, u) {
343
371
  // short localized heads-up the moment real work starts (first tool_start),
344
372
  // but only if the agent didn't already write its own "on it" line.
345
373
  let sentHeadsUp = false;
346
- const headsUpPhrase = () => {
347
- const lang = (self.globalConfig?.user?.language || "es").slice(0, 2);
348
- const byLang = {
349
- es: "Dale, estoy con eso… 🛠️",
350
- en: "On it — working on that… 🛠️",
351
- pt: "Já estou nisso… 🛠️",
352
- };
353
- return byLang[lang] || byLang.es;
354
- };
374
+ const headsUpPhrase = () => t("telegram.heads_up", { lang: resolveLang(self.globalConfig) });
355
375
  if (!replyText && isSuperAgentEnabled(self.globalConfig)) {
356
376
  const onEvent = async (ev) => {
357
377
  try {
@@ -523,7 +543,9 @@ export async function handleUpdate(self, u) {
523
543
  const finalClean = replyText ? stripThinking(replyText).trim() : "";
524
544
  let toSend = "";
525
545
  if (finalClean && finalClean !== lastStreamedText) toSend = finalClean;
526
- else if (!finalClean && streamedCount === 0) toSend = "Listo.";
546
+ else if (!finalClean && streamedCount === 0) {
547
+ toSend = t("telegram.fallback_listo", { lang: resolveLang(self.globalConfig) });
548
+ }
527
549
 
528
550
  stopTyping();
529
551
  if (!toSend) return; // everything was already streamed — nothing left to send
@@ -6,6 +6,7 @@ import fs from "node:fs";
6
6
  import path from "node:path";
7
7
  import { APX_HOME, CONFIG_PATH } from "./paths.js";
8
8
  import { PERMISSION_MODES, DEFAULT_PERMISSION_MODE } from "../constants/permissions.js";
9
+ import { agentsMdFile, apcProjectFile } from "../apc/paths.js";
9
10
 
10
11
  export {
11
12
  APX_HOME,
@@ -116,6 +117,27 @@ const DEFAULT_CONFIG = {
116
117
  compact_model: "ollama:gemma4:31b-cloud", // light LLM for compaction (Ollama, local endpoint)
117
118
  compact_fallback_model: "", // "" → falls back to super_agent.model (APX default)
118
119
  },
120
+ skills: {
121
+ // Skill Inspector — opt-in test feature. When enabled, the static
122
+ // "Available skills" hint block (slug dump) is removed from the system
123
+ // prompt; instead a local RAG inspects each turn's user prompt and
124
+ // injects either the matching skill's body (high confidence) or a hint
125
+ // to call load_skill (mid confidence). Below threshold → nothing.
126
+ // Re-evaluated every turn → natural decay (a skill that stopped matching
127
+ // disappears next turn).
128
+ // Embedding provider is whatever memory.embeddings resolves to (defaults
129
+ // to local: ollama → gemini → openai → offline TF). No paid keys needed.
130
+ inspector: {
131
+ enabled: false,
132
+ load_threshold: 0.55,
133
+ hint_threshold: 0.40,
134
+ margin: 0.04,
135
+ max_loaded: 1,
136
+ max_hints: 2,
137
+ prompt_floor: 8,
138
+ body_char_cap: 6000,
139
+ },
140
+ },
119
141
  voice: {
120
142
  // Text-to-speech configuration. Selector reads voice.tts.provider; "auto"
121
143
  // probes engines in preference order (piper → elevenlabs → openai →
@@ -395,10 +417,10 @@ export function effectiveHost(cfg) {
395
417
 
396
418
  export function addProject(cfg, projectPath) {
397
419
  const abs = path.resolve(projectPath);
398
- if (!fs.existsSync(path.join(abs, "AGENTS.md"))) {
420
+ if (!fs.existsSync(agentsMdFile(abs))) {
399
421
  throw new Error(`not an APC project: ${abs} (no AGENTS.md)`);
400
422
  }
401
- if (!fs.existsSync(path.join(abs, ".apc", "project.json"))) {
423
+ if (!fs.existsSync(apcProjectFile(abs))) {
402
424
  throw new Error(`not an APC project: ${abs} (no .apc/project.json)`);
403
425
  }
404
426
  const exists = cfg.projects.find((p) => path.resolve(p.path) === abs);