@agentprojectcontext/apx 1.77.2 → 1.78.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 (294) hide show
  1. package/README.md +23 -9
  2. package/package.json +12 -12
  3. package/src/core/agent/build-agent-system.js +3 -2
  4. package/src/core/agent/constants.js +3 -2
  5. package/src/core/agent/loop/greeting-guard.js +55 -0
  6. package/src/core/agent/loop/side-effects.js +52 -0
  7. package/src/core/agent/prompt-builder.js +49 -8
  8. package/src/core/agent/run-agent.js +13 -48
  9. package/src/core/agent/security.js +1 -1
  10. package/src/core/agent/self-memory.js +4 -2
  11. package/src/core/agent/skills/index-store.js +2 -2
  12. package/src/core/agent/skills/loader.js +3 -22
  13. package/src/core/agent/super-agent.js +0 -2
  14. package/src/core/agent/tools/handlers/call-runtime.js +4 -4
  15. package/src/core/agent/tools/handlers/search-sessions.js +1 -1
  16. package/src/core/agent/tools/handlers/set-permission-mode.js +11 -4
  17. package/src/core/agent/tools/names.js +58 -0
  18. package/src/core/apc/context-copy.js +1 -1
  19. package/src/core/apc/frontmatter.js +85 -0
  20. package/src/core/apc/parser.js +9 -21
  21. package/src/core/apc/paths.js +8 -8
  22. package/src/core/apc/scaffold.js +5 -4
  23. package/src/core/channels/telegram/config.js +201 -0
  24. package/src/core/config/index.js +25 -183
  25. package/src/core/config/paths.js +54 -1
  26. package/src/core/confirmation/adapters/web.js +1 -1
  27. package/src/core/deck/manifest.js +12 -12
  28. package/src/core/desktop/autostart.js +3 -2
  29. package/src/core/desktop/process.js +4 -4
  30. package/src/core/engines/presets.js +2 -2
  31. package/src/core/http-tools/browser.js +1 -1
  32. package/src/core/http-tools/catalog.js +551 -0
  33. package/src/core/http-tools/fetch.js +1 -1
  34. package/src/core/http-tools/glob.js +3 -3
  35. package/src/core/http-tools/grep.js +2 -9
  36. package/src/core/http-tools/inline-handlers.js +59 -0
  37. package/src/core/http-tools/registry.js +31 -615
  38. package/src/core/http-tools/search.js +2 -2
  39. package/src/core/identity/self.js +6 -7
  40. package/src/core/identity/telegram.js +3 -3
  41. package/src/core/logging.js +6 -5
  42. package/src/core/mcp/sources.js +12 -16
  43. package/src/core/memory/indexer.js +2 -5
  44. package/src/core/profiles/lifecycle.js +0 -2
  45. package/src/core/profiles/store.js +2 -7
  46. package/src/core/routines/runner.js +1 -1
  47. package/src/core/runtime-skills/apx-agency-agents/SKILL.md +2 -2
  48. package/src/core/runtime-skills/apx-profile/SKILL.md +3 -3
  49. package/src/core/runtime-skills/apx-project/SKILL.md +1 -1
  50. package/src/core/runtime-skills/apx-task/SKILL.md +8 -8
  51. package/src/core/runtime-skills/apx-telegram/SKILL.md +4 -4
  52. package/src/core/runtime-skills/apx-voice/SKILL.md +4 -4
  53. package/src/core/runtimes/detect.js +2 -2
  54. package/src/core/sessions/index.js +800 -0
  55. package/src/core/stores/code-sessions.js +3 -14
  56. package/src/core/stores/engine-sessions.js +0 -4
  57. package/src/core/stores/messages.js +0 -5
  58. package/src/core/stores/organization.js +9 -11
  59. package/src/core/stores/routines.js +6 -11
  60. package/src/core/stores/runtime-callbacks.js +2 -1
  61. package/src/core/util/json-file.js +128 -0
  62. package/src/core/vars/sources.js +14 -21
  63. package/src/core/voice/stt-models.js +1 -1
  64. package/src/core/voice/tts.js +5 -4
  65. package/src/host/daemon/api/admin-config.js +5 -5
  66. package/src/host/daemon/api/admin.js +8 -7
  67. package/src/host/daemon/api/agents.js +16 -16
  68. package/src/host/daemon/api/artifact-preview.js +8 -8
  69. package/src/host/daemon/api/artifacts.js +7 -7
  70. package/src/host/daemon/api/code.js +8 -8
  71. package/src/host/daemon/api/config.js +6 -6
  72. package/src/host/daemon/api/confirm.js +2 -2
  73. package/src/host/daemon/api/connections.js +2 -2
  74. package/src/host/daemon/api/conversations.js +12 -12
  75. package/src/host/daemon/api/deck.js +6 -6
  76. package/src/host/daemon/api/desktop.js +8 -8
  77. package/src/host/daemon/api/embeddings.js +4 -4
  78. package/src/host/daemon/api/engines.js +8 -7
  79. package/src/host/daemon/api/exec.js +5 -4
  80. package/src/host/daemon/api/files-project.js +6 -6
  81. package/src/host/daemon/api/health.js +2 -2
  82. package/src/host/daemon/api/identity.js +3 -3
  83. package/src/host/daemon/api/inbox.js +2 -2
  84. package/src/host/daemon/api/integrations.js +23 -19
  85. package/src/host/daemon/api/mcps.js +16 -12
  86. package/src/host/daemon/api/messages.js +5 -5
  87. package/src/host/daemon/api/organization.js +8 -8
  88. package/src/host/daemon/api/pairing.js +10 -7
  89. package/src/host/daemon/api/plugins.js +3 -3
  90. package/src/host/daemon/api/prefix.js +20 -0
  91. package/src/host/daemon/api/profiles.js +9 -9
  92. package/src/host/daemon/api/projects.js +5 -5
  93. package/src/host/daemon/api/routines.js +8 -8
  94. package/src/host/daemon/api/run.js +2 -2
  95. package/src/host/daemon/api/runtimes.js +6 -6
  96. package/src/host/daemon/api/sessions-search.js +18 -11
  97. package/src/host/daemon/api/sessions.js +6 -6
  98. package/src/host/daemon/api/shared.js +59 -13
  99. package/src/host/daemon/api/skills.js +12 -12
  100. package/src/host/daemon/api/super-agent.js +4 -4
  101. package/src/host/daemon/api/tasks.js +11 -11
  102. package/src/host/daemon/api/telegram.js +19 -20
  103. package/src/host/daemon/api/tools.js +7 -7
  104. package/src/host/daemon/api/top-level.js +7 -7
  105. package/src/host/daemon/api/transcribe.js +6 -6
  106. package/src/host/daemon/api/tts.js +3 -3
  107. package/src/host/daemon/api/vars.js +11 -8
  108. package/src/host/daemon/api/voice.js +6 -6
  109. package/src/host/daemon/api/web.js +28 -32
  110. package/src/host/daemon/api.js +77 -48
  111. package/src/host/daemon/db.js +1 -1
  112. package/src/host/daemon/desktop-ws.js +7 -3
  113. package/src/host/daemon/index.js +11 -5
  114. package/src/host/daemon/plugins/desktop/index.js +1 -1
  115. package/src/host/daemon/plugins/telegram/index.js +1 -1
  116. package/src/host/daemon/stt-venv.js +20 -81
  117. package/src/host/daemon/wakeup.js +1 -1
  118. package/src/interfaces/acp/index.js +3 -3
  119. package/src/interfaces/acp/session.js +2 -2
  120. package/src/interfaces/cli/commands/a2a.js +2 -2
  121. package/src/interfaces/cli/commands/agent.js +3 -3
  122. package/src/interfaces/cli/commands/artifact.js +13 -13
  123. package/src/interfaces/cli/commands/chat.js +3 -3
  124. package/src/interfaces/cli/commands/command.js +2 -2
  125. package/src/interfaces/cli/commands/config.js +4 -4
  126. package/src/interfaces/cli/commands/daemon.js +17 -18
  127. package/src/interfaces/cli/commands/desktop.js +7 -14
  128. package/src/interfaces/cli/commands/exec.js +2 -2
  129. package/src/interfaces/cli/commands/log.js +3 -4
  130. package/src/interfaces/cli/commands/mcp.js +9 -9
  131. package/src/interfaces/cli/commands/memory.js +2 -2
  132. package/src/interfaces/cli/commands/messages.js +5 -5
  133. package/src/interfaces/cli/commands/model.js +0 -18
  134. package/src/interfaces/cli/commands/obsidian.js +5 -5
  135. package/src/interfaces/cli/commands/org.js +5 -5
  136. package/src/interfaces/cli/commands/pair.js +6 -6
  137. package/src/interfaces/cli/commands/plugins.js +2 -2
  138. package/src/interfaces/cli/commands/profile.js +10 -10
  139. package/src/interfaces/cli/commands/project-config.js +6 -6
  140. package/src/interfaces/cli/commands/project.js +10 -10
  141. package/src/interfaces/cli/commands/routine.js +8 -9
  142. package/src/interfaces/cli/commands/runtime.js +2 -2
  143. package/src/interfaces/cli/commands/search.js +0 -1
  144. package/src/interfaces/cli/commands/session.js +12 -40
  145. package/src/interfaces/cli/commands/sessions.js +21 -798
  146. package/src/interfaces/cli/commands/setup.js +16 -6
  147. package/src/interfaces/cli/commands/skills.js +1 -2
  148. package/src/interfaces/cli/commands/status.js +3 -3
  149. package/src/interfaces/cli/commands/task.js +8 -8
  150. package/src/interfaces/cli/commands/telegram.js +21 -21
  151. package/src/interfaces/cli/commands/voice.js +6 -6
  152. package/src/interfaces/cli/help/index.js +2245 -0
  153. package/src/interfaces/cli/http.js +9 -4
  154. package/src/interfaces/cli/index.js +14 -2857
  155. package/src/interfaces/cli/routes/acp.js +13 -0
  156. package/src/interfaces/cli/routes/agent.js +27 -0
  157. package/src/interfaces/cli/routes/artifact.js +28 -0
  158. package/src/interfaces/cli/routes/chat.js +11 -0
  159. package/src/interfaces/cli/routes/code.js +11 -0
  160. package/src/interfaces/cli/routes/command.js +21 -0
  161. package/src/interfaces/cli/routes/config.js +16 -0
  162. package/src/interfaces/cli/routes/connections.js +11 -0
  163. package/src/interfaces/cli/routes/conversations.js +21 -0
  164. package/src/interfaces/cli/routes/daemon.js +19 -0
  165. package/src/interfaces/cli/routes/desktop.js +20 -0
  166. package/src/interfaces/cli/routes/env.js +13 -0
  167. package/src/interfaces/cli/routes/exec.js +11 -0
  168. package/src/interfaces/cli/routes/identity.js +11 -0
  169. package/src/interfaces/cli/routes/index.js +71 -0
  170. package/src/interfaces/cli/routes/init.js +11 -0
  171. package/src/interfaces/cli/routes/log.js +20 -0
  172. package/src/interfaces/cli/routes/mcp.js +22 -0
  173. package/src/interfaces/cli/routes/memory.js +11 -0
  174. package/src/interfaces/cli/routes/messages.js +16 -0
  175. package/src/interfaces/cli/routes/model.js +11 -0
  176. package/src/interfaces/cli/routes/obsidian.js +17 -0
  177. package/src/interfaces/cli/routes/org.js +34 -0
  178. package/src/interfaces/cli/routes/overlay.js +10 -0
  179. package/src/interfaces/cli/routes/pair.js +17 -0
  180. package/src/interfaces/cli/routes/panel.js +16 -0
  181. package/src/interfaces/cli/routes/permission.js +11 -0
  182. package/src/interfaces/cli/routes/plugins.js +21 -0
  183. package/src/interfaces/cli/routes/profile.js +27 -0
  184. package/src/interfaces/cli/routes/project.js +41 -0
  185. package/src/interfaces/cli/routes/restart.js +15 -0
  186. package/src/interfaces/cli/routes/routine.js +28 -0
  187. package/src/interfaces/cli/routes/run.js +11 -0
  188. package/src/interfaces/cli/routes/search.js +11 -0
  189. package/src/interfaces/cli/routes/send.js +11 -0
  190. package/src/interfaces/cli/routes/session.js +26 -0
  191. package/src/interfaces/cli/routes/sessions.js +15 -0
  192. package/src/interfaces/cli/routes/setup.js +18 -0
  193. package/src/interfaces/cli/routes/skills.js +20 -0
  194. package/src/interfaces/cli/routes/status.js +12 -0
  195. package/src/interfaces/cli/routes/task.js +26 -0
  196. package/src/interfaces/cli/routes/telegram.js +39 -0
  197. package/src/interfaces/cli/routes/update.js +24 -0
  198. package/src/interfaces/cli/routes/voice.js +17 -0
  199. package/src/interfaces/desktop/main.js +6 -7
  200. package/src/interfaces/desktop/renderer.js +0 -5
  201. package/src/interfaces/mcp-server/index.js +15 -14
  202. package/src/interfaces/tui/_shims/globals.d.ts +8 -0
  203. package/src/interfaces/tui/tsconfig.json +4 -1
  204. package/src/interfaces/web/README.md +6 -6
  205. package/src/interfaces/web/dist/assets/index-CBR_-QyA.js +824 -0
  206. package/src/interfaces/web/dist/assets/index-CBR_-QyA.js.map +1 -0
  207. package/src/interfaces/web/dist/index.html +1 -1
  208. package/src/interfaces/web/src/components/AddProjectDialog.tsx +1 -1
  209. package/src/interfaces/web/src/components/agents/AgentFormFields.tsx +1 -1
  210. package/src/interfaces/web/src/components/chat/ChatList.tsx +2 -2
  211. package/src/interfaces/web/src/components/chat/SkillPicker.tsx +1 -1
  212. package/src/interfaces/web/src/components/code/CodeFileTree.tsx +1 -1
  213. package/src/interfaces/web/src/components/code/CodeTerminal.tsx +1 -1
  214. package/src/interfaces/web/src/components/desktop/DesktopStatusCard.tsx +1 -1
  215. package/src/interfaces/web/src/components/files/FileBrowser.tsx +2 -2
  216. package/src/interfaces/web/src/components/memory/MemoryBrowser.tsx +2 -2
  217. package/src/interfaces/web/src/components/routines/ExecutionsList.tsx +1 -1
  218. package/src/interfaces/web/src/components/routines/RoutineEditor.tsx +2 -2
  219. package/src/interfaces/web/src/components/settings/DesktopSettingsPanel.tsx +1 -1
  220. package/src/interfaces/web/src/components/settings/MemoryPanel.tsx +1 -1
  221. package/src/interfaces/web/src/components/settings/SkillsInspectorPanel.tsx +1 -1
  222. package/src/interfaces/web/src/components/settings/SkillsManager.tsx +2 -2
  223. package/src/interfaces/web/src/components/tasks/TaskDetailPanel.tsx +1 -1
  224. package/src/interfaces/web/src/hooks/useDaemonStatus.ts +1 -1
  225. package/src/interfaces/web/src/hooks/useDevices.ts +1 -1
  226. package/src/interfaces/web/src/hooks/useEngines.ts +1 -1
  227. package/src/interfaces/web/src/hooks/useGlobalConfig.ts +2 -2
  228. package/src/interfaces/web/src/hooks/useIdentity.ts +1 -1
  229. package/src/interfaces/web/src/hooks/useInbox.ts +1 -1
  230. package/src/interfaces/web/src/hooks/useProfiles.ts +3 -3
  231. package/src/interfaces/web/src/hooks/useProjects.ts +1 -1
  232. package/src/interfaces/web/src/hooks/useTelegram.ts +3 -3
  233. package/src/interfaces/web/src/hooks/useTokenBootstrap.ts +30 -7
  234. package/src/interfaces/web/src/lib/api/admin.ts +11 -11
  235. package/src/interfaces/web/src/lib/api/agents.ts +14 -14
  236. package/src/interfaces/web/src/lib/api/artifacts.ts +11 -11
  237. package/src/interfaces/web/src/lib/api/code.ts +1 -1
  238. package/src/interfaces/web/src/lib/api/conversations.ts +8 -8
  239. package/src/interfaces/web/src/lib/api/deck.ts +3 -3
  240. package/src/interfaces/web/src/lib/api/desktop.ts +8 -8
  241. package/src/interfaces/web/src/lib/api/embeddings.ts +3 -3
  242. package/src/interfaces/web/src/lib/api/engines.ts +3 -3
  243. package/src/interfaces/web/src/lib/api/filesystem.ts +2 -2
  244. package/src/interfaces/web/src/lib/api/health.ts +1 -1
  245. package/src/interfaces/web/src/lib/api/identity.ts +2 -2
  246. package/src/interfaces/web/src/lib/api/inbox.ts +1 -1
  247. package/src/interfaces/web/src/lib/api/integrations.ts +8 -8
  248. package/src/interfaces/web/src/lib/api/mcps.ts +6 -6
  249. package/src/interfaces/web/src/lib/api/messages.ts +3 -3
  250. package/src/interfaces/web/src/lib/api/organization.ts +7 -7
  251. package/src/interfaces/web/src/lib/api/profiles.ts +8 -8
  252. package/src/interfaces/web/src/lib/api/projectFiles.ts +5 -5
  253. package/src/interfaces/web/src/lib/api/projects.ts +12 -12
  254. package/src/interfaces/web/src/lib/api/routines.ts +7 -7
  255. package/src/interfaces/web/src/lib/api/sessions.ts +2 -2
  256. package/src/interfaces/web/src/lib/api/skills.ts +11 -11
  257. package/src/interfaces/web/src/lib/api/super_agent.ts +3 -3
  258. package/src/interfaces/web/src/lib/api/tasks.ts +12 -12
  259. package/src/interfaces/web/src/lib/api/telegram.ts +14 -14
  260. package/src/interfaces/web/src/lib/api/tools.ts +1 -1
  261. package/src/interfaces/web/src/lib/api/vars.ts +4 -4
  262. package/src/interfaces/web/src/lib/api/voice.ts +6 -6
  263. package/src/interfaces/web/src/screens/base/AgentDefaultsTab.tsx +1 -1
  264. package/src/interfaces/web/src/screens/base/GlobalTasksTab.tsx +1 -1
  265. package/src/interfaces/web/src/screens/base/LogsTab.tsx +3 -3
  266. package/src/interfaces/web/src/screens/base/SessionsTab.tsx +1 -1
  267. package/src/interfaces/web/src/screens/modules/CodeScreen.tsx +1 -1
  268. package/src/interfaces/web/src/screens/modules/DeckScreen.tsx +1 -1
  269. package/src/interfaces/web/src/screens/modules/DesktopScreen.tsx +1 -1
  270. package/src/interfaces/web/src/screens/modules/VoiceScreen.tsx +1 -1
  271. package/src/interfaces/web/src/screens/project/AgentDetailScreen.tsx +8 -8
  272. package/src/interfaces/web/src/screens/project/AgentsTab.tsx +2 -2
  273. package/src/interfaces/web/src/screens/project/ChatTab.tsx +3 -3
  274. package/src/interfaces/web/src/screens/project/ConfigTab.tsx +1 -1
  275. package/src/interfaces/web/src/screens/project/McpsTab.tsx +3 -3
  276. package/src/interfaces/web/src/screens/project/Overview.tsx +6 -6
  277. package/src/interfaces/web/src/screens/project/RoutinesTab.tsx +2 -2
  278. package/src/interfaces/web/src/screens/project/StructureTab.tsx +1 -1
  279. package/src/interfaces/web/src/screens/project/TasksTab.tsx +1 -1
  280. package/src/interfaces/web/src/screens/project/VarsTab.tsx +1 -1
  281. package/src/interfaces/web/vite.config.ts +13 -18
  282. package/src/interfaces/web/dist/assets/index-BWWpTTSd.js +0 -824
  283. package/src/interfaces/web/dist/assets/index-BWWpTTSd.js.map +0 -1
  284. package/src/skills/apc-context/SKILL.md +0 -159
  285. /package/src/{host/daemon → core}/runtimes/_spawn.js +0 -0
  286. /package/src/{host/daemon → core}/runtimes/aider.js +0 -0
  287. /package/src/{host/daemon → core}/runtimes/antigravity.js +0 -0
  288. /package/src/{host/daemon → core}/runtimes/claude-code.js +0 -0
  289. /package/src/{host/daemon → core}/runtimes/codex.js +0 -0
  290. /package/src/{host/daemon → core}/runtimes/cursor-agent.js +0 -0
  291. /package/src/{host/daemon → core}/runtimes/gemini-cli.js +0 -0
  292. /package/src/{host/daemon → core}/runtimes/index.js +0 -0
  293. /package/src/{host/daemon → core}/runtimes/opencode.js +0 -0
  294. /package/src/{host/daemon → core}/runtimes/qwen-code.js +0 -0
package/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
  <p align="center">
12
12
  <a href="https://agentprojectcontext.github.io/apx/"><img src="https://img.shields.io/badge/Website-agentprojectcontext.github.io-3fb950?style=flat-square&logo=googlechrome&logoColor=white" alt="Website"></a>
13
13
  <a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-3fb950?style=flat-square" alt="License: MIT"></a>
14
- <img src="https://img.shields.io/badge/Node.js-20%2B-3fb950?style=flat-square&logo=nodedotjs&logoColor=white" alt="Node.js 20+">
14
+ <img src="https://img.shields.io/badge/Node.js-22%2B-3fb950?style=flat-square&logo=nodedotjs&logoColor=white" alt="Node.js 22+">
15
15
  <a href="https://github.com/agentprojectcontext/agentprojectcontext"><img src="https://img.shields.io/badge/Protocol-APC-3fb950?style=flat-square" alt="APC protocol"></a>
16
16
  </p>
17
17
 
@@ -49,7 +49,7 @@ APX is opinionated about storage: the filesystem is the source of truth. Project
49
49
  █ ‿ █
50
50
  ▀███████▀
51
51
  </pre>
52
- <sub>Meet <b>Roby</b> — your project's super-agent. He greets you across the <code>apx</code> CLI and the web admin.</sub>
52
+ <sub>The <b>super-agent</b> — it greets you across the <code>apx</code> CLI and the web admin.</sub>
53
53
  </div>
54
54
 
55
55
  ## Quick start
@@ -77,7 +77,8 @@ apx messages tail
77
77
  ## Examples
78
78
 
79
79
  Real commands — copy one, point it at an agent like `sofia`, and APX routes it to the right
80
- runtime. The session and memory land in `.apc/`.
80
+ runtime. The session transcript and its summary land in `~/.apx/`; only curated
81
+ memory and the agent definition live in `.apc/`.
81
82
 
82
83
  | What | Command |
83
84
  |------|---------|
@@ -90,7 +91,7 @@ runtime. The session and memory land in `.apc/`.
90
91
 
91
92
  ## Use cases
92
93
 
93
- - **Review PRs across any runtime** — point an agent at your repo; APX routes to Claude Code and falls back to Codex or OpenCode if one isn't installed. The session and its summary land in `.apc/`.
94
+ - **Review PRs across any runtime** — point an agent at your repo; APX routes to Claude Code and falls back to Codex or OpenCode if one isn't installed. The session and its summary land in `~/.apx/`.
94
95
  - **Operate your agents from Telegram** — talk to project agents from your phone. Identity roles gate who can do what, and every message is logged per channel for a full audit trail.
95
96
  - **Memory that lives in your repo** — curated, per-agent memory is plain markdown, committed and reviewable alongside your code. No vendor database, no hidden state, no lock-in.
96
97
  - **Run the same prompt across engines** — send one prompt through Anthropic, OpenAI, Gemini or a local Ollama model with `apx exec`, configured per project or globally.
@@ -101,7 +102,7 @@ runtime. The session and memory land in `.apc/`.
101
102
  npm install -g @agentprojectcontext/apx
102
103
  ```
103
104
 
104
- Requires Node.js 20+. The daemon starts automatically on first `apx` call.
105
+ Requires Node.js 22+. The daemon starts automatically on first `apx` call.
105
106
 
106
107
  ## Web admin
107
108
 
@@ -178,12 +179,21 @@ Activity belongs to APX runtime state, not `.apc/`. Message storage is local to
178
179
  JSONL messages include `type` (`user`, `agent`, `tool`, or `system`) plus `actor_id`, so chat views
179
180
  can distinguish Telegram users from APX agents and future subagents.
180
181
 
182
+ A **channel** is the surface a turn arrived on. The canonical list lives in
183
+ `src/core/constants/channels.js`; `voice` is a *mode*, not a channel.
184
+
181
185
  | Channel | What it captures |
182
186
  |---------|-----------------|
183
- | `runtime` | `apx run` invocations (prompt in, response out) |
184
- | `a2a` | Agent-to-agent calls made from within a session |
185
- | `telegram` | Telegram bot messages (stored globally in `~/.apx/messages/telegram/`) |
186
- | `exec` | Quick `apx exec` calls |
187
+ | `cli` | `apx exec` / `apx run` from the terminal |
188
+ | `telegram` | Telegram bot messages |
189
+ | `api` | Direct daemon HTTP calls |
190
+ | `web` | The admin panel's main chat |
191
+ | `web_sidebar` | The panel's side assistant |
192
+ | `web_code` | The panel's coding surface |
193
+ | `code` | `apx code` sessions |
194
+ | `deck` | The tablet/phone dashboard |
195
+ | `desktop` | The floating voice capsule (always voice mode) |
196
+ | `routine` | Scheduled routine runs |
187
197
 
188
198
  ## Runtimes
189
199
 
@@ -193,6 +203,10 @@ can distinguish Telegram users from APX agents and future subagents.
193
203
  | `codex` | OpenAI Codex CLI via non-interactive `codex exec --sandbox workspace-write --skip-git-repo-check` |
194
204
  | `opencode` | OpenCode CLI |
195
205
  | `aider` | Aider CLI |
206
+ | `cursor-agent` | Cursor's headless agent |
207
+ | `gemini-cli` | Google Gemini CLI |
208
+ | `qwen-code` | Qwen Code CLI |
209
+ | `antigravity` | Antigravity CLI |
196
210
 
197
211
  Global APX skill installation also writes named helper skills for `codex-cli`, `claude-code`,
198
212
  `opencode-cli`, and `openrouter`. They are intentionally narrow and should activate only when those
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentprojectcontext/apx",
3
- "version": "1.77.2",
3
+ "version": "1.78.0",
4
4
  "description": "APX — unified CLI + daemon for the Agent Project Context (APC) standard.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -29,16 +29,20 @@
29
29
  "!**/*.tsbuildinfo"
30
30
  ],
31
31
  "engines": {
32
- "node": ">=18"
32
+ "node": ">=22"
33
33
  },
34
34
  "scripts": {
35
35
  "start": "node src/host/daemon/index.js",
36
36
  "smoke": "node src/host/daemon/smoke.js",
37
37
  "test": "node --test --test-reporter=spec tests/*.test.js",
38
+ "test:ci": "node scripts/test-ci.js",
39
+ "lint": "eslint .",
40
+ "lint:fix": "eslint . --fix",
41
+ "typecheck:tui": "node scripts/typecheck-tui.js",
38
42
  "build:web": "node scripts/build-web.js",
39
- "preflight": "npm test && node scripts/build-web.js && (cd src/interfaces/web && npx --no-install tsc --noEmit)",
43
+ "preflight": "npm run lint && npm run test:ci && node scripts/build-web.js && (cd src/interfaces/web && npx --no-install tsc --noEmit) && npm run typecheck:tui",
40
44
  "upgrade": "pnpm install && pnpm add -g .",
41
- "prepare": "node scripts/install-githooks.js",
45
+ "prepare": "node scripts/install-githooks.js && node scripts/sync-apc-skill.js",
42
46
  "prepack": "node scripts/sync-apc-skill.js && node scripts/build-web.js",
43
47
  "postinstall": "node src/interfaces/cli/postinstall.js",
44
48
  "smoke:seam": "node --test --test-reporter=spec tests/smoke/*.smoke.js"
@@ -50,26 +54,20 @@
50
54
  "@opentui/solid": "^0.2.16",
51
55
  "@solid-primitives/event-bus": "^1.1.3",
52
56
  "@solid-primitives/scheduled": "^1.5.2",
53
- "chalk": "^5.6.2",
54
- "cli-cursor": "^5.0.0",
55
57
  "cli-sound": "^1.1.3",
56
58
  "clipboardy": "^4.0.0",
57
59
  "cron-parser": "^5.5.0",
58
60
  "effect": "^3.21.2",
59
- "esbuild-plugin-solid": "^0.6.0",
60
61
  "express": "^4.21.0",
61
62
  "fuzzysort": "^3.1.0",
62
- "iconv-lite": "^0.6.3",
63
63
  "node-fetch": "^3.3.2",
64
64
  "open": "^10.1.2",
65
65
  "opentui-spinner": "^0.0.6",
66
66
  "qrcode-terminal": "^0.12.0",
67
- "raw-body": "^3.0.0",
68
- "react": "^19.2.0",
69
67
  "remeda": "^2.21.0",
70
- "safer-buffer": "^2.1.2",
71
68
  "solid-js": "^1.9.13",
72
- "ws": "^8.21.3"
69
+ "ws": "^8.21.3",
70
+ "zod": "^4.4.3"
73
71
  },
74
72
  "optionalDependencies": {
75
73
  "better-sqlite3": "^11.3.0",
@@ -78,12 +76,14 @@
78
76
  "sqlite-vec": "^0.1.6"
79
77
  },
80
78
  "devDependencies": {
79
+ "@eslint/js": "^9.39.5",
81
80
  "@semantic-release/changelog": "^6.0.3",
82
81
  "@semantic-release/git": "^10.0.1",
83
82
  "@types/node": "^25.7.0",
84
83
  "conventional-changelog-conventionalcommits": "^9.3.1",
85
84
  "electron": "^33.4.11",
86
85
  "esbuild": "^0.28.0",
86
+ "eslint": "^9.39.5",
87
87
  "typescript": "^6.0.3"
88
88
  },
89
89
  "keywords": [
@@ -17,6 +17,7 @@ import {
17
17
  buildUserContextBlock,
18
18
  buildSegmentDiscipline,
19
19
  } from "./prompt-builder.js";
20
+ import { readJson } from "#core/util/json-file.js";
20
21
 
21
22
  // Cap the injected agent body so an over-long authored file can't blow the
22
23
  // token budget. Mirrors PROJECT_AGENTS_MAX_CHARS for AGENTS.md.
@@ -30,8 +31,8 @@ function listField(value) {
30
31
  function projectName(project) {
31
32
  if (project?.name) return project.name;
32
33
  try {
33
- const meta = JSON.parse(fs.readFileSync(apcProjectFile(project.path), "utf8"));
34
- return meta.name || project.path?.split("/").pop() || "";
34
+ const meta = readJson(apcProjectFile(project.path), {});
35
+ return meta?.name || project.path?.split("/").pop() || "";
35
36
  } catch {
36
37
  return project?.path?.split("/").pop() || "";
37
38
  }
@@ -15,10 +15,11 @@ export const MAX_TOOL_ITERS = 10;
15
15
  // genuinely exhausts this budget — a rare safety floor, not the default close.
16
16
  // Overridable per-deployment via config.super_agent.telegram_max_iters.
17
17
  export const TELEGRAM_TOOL_ITERS = 24;
18
- export const ACK_ONLY_TOOLS = new Set(["send_telegram"]);
18
+ // Defined in tools/names.js, next to the names they reference.
19
+ export { ACK_ONLY_TOOLS, TURN_ENDING_TOOLS } from "./tools/names.js";
19
20
  export const MAX_CONSECUTIVE_ACKS = 2;
20
21
  // Tools whose semantics REQUIRE handing control back to the user. After the
21
22
  // tool runs we break the loop — even under completionContract — because the
22
23
  // task literally cannot advance without a human reply. Without this, models
23
24
  // under forced toolChoice spam the same question across iterations.
24
- export const TURN_ENDING_TOOLS = new Set(["ask_questions"]);
25
+
@@ -0,0 +1,55 @@
1
+ // Collapse repeated greetings inside a single turn.
2
+ //
3
+ // A turn can emit several text segments (pre-tool narration, then the final
4
+ // answer) and weaker models greet in each one, so the user sees "¡Hola Manu!"
5
+ // twice. Keep the first greeting, strip any later one.
6
+ //
7
+ // Belt-and-suspenders over the action-discipline prompt rule: strong models
8
+ // follow that rule, gemini-flash and friends often do not.
9
+ //
10
+ // Extracted from runAgent, where it was two loose variables and a closure among
11
+ // twelve other concerns. As its own object it can be tested directly, which it
12
+ // could not be before.
13
+ // A leading greeting clause: "¡Hola Manu!", "Hola,", "Hi there!", "Buenas tardes…".
14
+ // Intentionally narrow — only the opening salutation up to its first terminator —
15
+ // so we never eat real content.
16
+ // The comma matters. Without it the class ran to the first sentence
17
+ // terminator, so "Hola de nuevo, ya está listo." matched in full and the guard
18
+ // deleted the whole sentence — the opposite of the "never eat real content"
19
+ // intent stated above. Stopping at a comma keeps the vocative ("¡Hola Manu!",
20
+ // "Hola de nuevo,") and leaves the sentence that follows.
21
+ //
22
+ // Known limit: a greeting glued to content with no separator at all
23
+ // ("Hey arranco.") is genuinely ambiguous and is still consumed. That is the
24
+ // rarer shape, and erring toward stripping there matches the original intent.
25
+ export const LEADING_GREETING_RE =
26
+ /^\s*[¡!]*\s*(hola+|holis?|buenas|buen[oa]s?\s+(d[ií]as|tardes|noches)|hey|hi|hello)\b[^,.!?¡\n]*[,.!?¡]*[\s]*/i;
27
+
28
+ /** If `text` opens with a greeting, return it with that greeting removed; else null. */
29
+ export function stripLeadingGreeting(text) {
30
+ const m = String(text).match(LEADING_GREETING_RE);
31
+ if (!m) return null;
32
+ return String(text).slice(m[0].length).replace(/^\s+/, "");
33
+ }
34
+
35
+ /**
36
+ * @returns {{ apply(text: string): string, greeted: boolean }} `apply` returns
37
+ * the text to show the user; call it once per emitted segment, in order.
38
+ */
39
+ export function createGreetingGuard() {
40
+ let greeted = false;
41
+ return {
42
+ apply(text) {
43
+ if (!text) return text;
44
+ if (greeted) {
45
+ const stripped = stripLeadingGreeting(text);
46
+ return stripped == null ? text : stripped;
47
+ }
48
+ if (LEADING_GREETING_RE.test(text)) greeted = true;
49
+ return text;
50
+ },
51
+ get greeted() {
52
+ return greeted;
53
+ },
54
+ };
55
+ }
@@ -0,0 +1,52 @@
1
+ // Remembers which world-changing tool calls a turn already made.
2
+ //
3
+ // Weaker models (Gemini especially) re-emit the SAME tool call across
4
+ // iterations — send_telegram three times with identical args, spamming the
5
+ // user. For tools that mutate something we record the (name + args) signature
6
+ // and answer a repeat with a synthetic "already done" instead of running it
7
+ // again. Read-only tools are exempt: they are idempotent and are legitimately
8
+ // repeated (list_tasks before and after a change).
9
+ //
10
+ // This is a user-facing protection, and it used to live as two loose locals
11
+ // inside runAgent alongside eleven other concerns, keyed off an inline array of
12
+ // tool-name literals. The names now come from tools/names.js (see
13
+ // SIDE_EFFECT_TOOLS) and the ledger is its own object, so both halves can be
14
+ // tested without driving a whole agent turn.
15
+ import { SIDE_EFFECT_TOOLS } from "../tools/names.js";
16
+
17
+ /**
18
+ * @returns {{
19
+ * signature(name: string, args: unknown): string|null,
20
+ * seen(sig: string|null): boolean,
21
+ * previous(sig: string): unknown,
22
+ * record(sig: string|null, result: unknown): void,
23
+ * }}
24
+ * `signature` returns null for read-only tools, which callers treat as
25
+ * "never dedupe this".
26
+ */
27
+ export function createSideEffectLedger() {
28
+ const executed = new Map();
29
+
30
+ return {
31
+ signature(name, args) {
32
+ if (!SIDE_EFFECT_TOOLS.has(name)) return null;
33
+ try {
34
+ return `${name}:${JSON.stringify(args)}`;
35
+ } catch {
36
+ // Circular or otherwise unserializable args: fall back to the tool
37
+ // name alone. Deduping slightly too eagerly is safer than sending the
38
+ // same message twice.
39
+ return `${name}:<unserializable>`;
40
+ }
41
+ },
42
+ seen(sig) {
43
+ return sig != null && executed.has(sig);
44
+ },
45
+ previous(sig) {
46
+ return executed.get(sig);
47
+ },
48
+ record(sig, result) {
49
+ if (sig != null) executed.set(sig, result);
50
+ },
51
+ };
52
+ }
@@ -125,19 +125,60 @@ function buildSegmentDiscipline({ channel, voice }) {
125
125
  // Project guidance — AGENTS.md of the pinned project, size-capped.
126
126
  // ---------------------------------------------------------------------------
127
127
 
128
- export const PROJECT_AGENTS_MAX_CHARS = 6000;
128
+ // Budget guard for a FOREIGN project's AGENTS.md — a project we were merely
129
+ // pointed at should not be able to blow the prompt budget. It is deliberately
130
+ // generous: a real contract runs well past the old 6k cap, and a contract that
131
+ // arrives half-read is worse than none (the agent follows the rules it can see
132
+ // and silently violates the ones it cannot).
133
+ //
134
+ // Two escape hatches, because truncating a contract is never harmless:
135
+ // - `super_agent.project_agents_max_chars` overrides it; 0 disables the cap.
136
+ // - The project APX is *running inside* is never capped (see isOwnProject).
137
+ export const PROJECT_AGENTS_MAX_CHARS = 24000;
138
+
139
+ // The project that owns the running process reads its own contract in full.
140
+ // This is the dogfood case: `apx exec` inside a repo, or the daemon started
141
+ // from it. Capping there truncated APX's own AGENTS.md mid-rule.
142
+ function isOwnProject(projectPath) {
143
+ try {
144
+ return path.resolve(projectPath) === path.resolve(process.cwd());
145
+ } catch {
146
+ return false;
147
+ }
148
+ }
149
+
150
+ function resolveAgentsCap(projectPath, globalConfig) {
151
+ if (isOwnProject(projectPath)) return 0;
152
+ const raw = globalConfig?.super_agent?.project_agents_max_chars;
153
+ if (raw === 0) return 0;
154
+ const n = Number(raw);
155
+ return Number.isFinite(n) && n > 0 ? n : PROJECT_AGENTS_MAX_CHARS;
156
+ }
157
+
158
+ // Cut on a line boundary so a rule is never severed mid-sentence, and say what
159
+ // was dropped — a bare "…(truncated)" gives the agent no way to know it is
160
+ // operating on a partial contract.
161
+ function capAgentsText(text, max) {
162
+ if (!max || text.length <= max) return text;
163
+ const head = text.slice(0, max);
164
+ const lastBreak = head.lastIndexOf("\n");
165
+ const kept = lastBreak > max * 0.5 ? head.slice(0, lastBreak) : head;
166
+ const dropped = text.length - kept.length;
167
+ return (
168
+ `${kept.trimEnd()}\n\n…(AGENTS.md truncated: ${dropped} of ${text.length} characters omitted. ` +
169
+ `Read the file directly before relying on rules not shown above.)`
170
+ );
171
+ }
129
172
 
130
- export function buildProjectAgentsBlock(projectPath) {
173
+ export function buildProjectAgentsBlock(projectPath, globalConfig = {}) {
131
174
  if (!projectPath) return "";
132
175
  try {
133
176
  const file = agentsMdFile(projectPath);
134
177
  if (!fs.existsSync(file)) return "";
135
- let text = fs.readFileSync(file, "utf8").trim();
178
+ const text = fs.readFileSync(file, "utf8").trim();
136
179
  if (!text) return "";
137
- if (text.length > PROJECT_AGENTS_MAX_CHARS) {
138
- text = text.slice(0, PROJECT_AGENTS_MAX_CHARS) + "\n\n…(AGENTS.md truncated)";
139
- }
140
- return `# Project guidance (AGENTS.md)\n\nStartup rules for THIS project — follow them:\n\n${text}`;
180
+ const capped = capAgentsText(text, resolveAgentsCap(projectPath, globalConfig));
181
+ return `# Project guidance (AGENTS.md)\n\nStartup rules for THIS project — follow them:\n\n${capped}`;
141
182
  } catch {
142
183
  return "";
143
184
  }
@@ -329,7 +370,7 @@ export function buildSuperAgentSystem({
329
370
  relationshipBlock,
330
371
  extraContext,
331
372
  buildProjectIndex(projects),
332
- buildProjectAgentsBlock(channelMeta?.projectPath),
373
+ buildProjectAgentsBlock(channelMeta?.projectPath, globalConfig),
333
374
  skipSkillsHint ? "" : buildSkillsHintBlock(listSkills),
334
375
  lazyToolsBlock,
335
376
  voiceBlock,
@@ -5,6 +5,7 @@ import {
5
5
  } from "./tools/tool-call-parser.js";
6
6
  import { resolveActiveModel, fallbackModels } from "./model-router.js";
7
7
  import { MAX_TOOL_ITERS, ACK_ONLY_TOOLS, MAX_CONSECUTIVE_ACKS, TURN_ENDING_TOOLS } from "./constants.js";
8
+ import { TOOLS } from "./tools/names.js";
8
9
  import { pseudoToolSystem, shouldRetryWithPseudoTools } from "./tools/pseudo-tools.js";
9
10
  import { filterToolSchemas } from "./tools-overlap.js";
10
11
  import { isRetryableEngineError, shortRetryReason } from "./retry.js";
@@ -21,6 +22,8 @@ import {
21
22
  createStuckDetector,
22
23
  stuckNudgeSignal,
23
24
  } from "./stuck-detector.js";
25
+ import { createGreetingGuard } from "./loop/greeting-guard.js";
26
+ import { createSideEffectLedger } from "./loop/side-effects.js";
24
27
 
25
28
  async function emitProgress(onEvent, event) {
26
29
  if (typeof onEvent !== "function") return;
@@ -46,19 +49,6 @@ function fallbackFinalText(trace, error) {
46
49
  return lines.join("\n");
47
50
  }
48
51
 
49
- // A leading greeting clause: "¡Hola Manu!", "Hola,", "Hi there!", "Buenas tardes…".
50
- // Intentionally narrow — only the opening salutation up to its first terminator —
51
- // so we never eat real content.
52
- const LEADING_GREETING_RE =
53
- /^\s*[¡!]*\s*(hola+|holis?|buenas|buen[oa]s?\s+(d[ií]as|tardes|noches)|hey|hi|hello)\b[^.!?¡\n]*[.!?¡]*[\s,]*/i;
54
-
55
- /** If `text` opens with a greeting, return it with that greeting removed; else null. */
56
- function stripLeadingGreeting(text) {
57
- const m = String(text).match(LEADING_GREETING_RE);
58
- if (!m) return null;
59
- return String(text).slice(m[0].length).replace(/^\s+/, "");
60
- }
61
-
62
52
  function previewTraceResult(result) {
63
53
  if (result === null || result === undefined) return "ok";
64
54
  if (typeof result === "string") return result.slice(0, 180);
@@ -78,7 +68,7 @@ function previewTraceResult(result) {
78
68
  export const FINISH_TOOL_SCHEMA = {
79
69
  type: "function",
80
70
  function: {
81
- name: "finish",
71
+ name: TOOLS.FINISH,
82
72
  description:
83
73
  "Call this ONLY when the user's request is fully complete and no step " +
84
74
  "remains. Put your final answer / summary of what you did in `summary` " +
@@ -274,7 +264,7 @@ export async function runAgent({
274
264
 
275
265
  const conversation = [...previousMessages, { role: "user", content: prompt }];
276
266
  const trace = [];
277
- let totalUsage = { input_tokens: 0, output_tokens: 0 };
267
+ const totalUsage = { input_tokens: 0, output_tokens: 0 };
278
268
  let lastText = "";
279
269
 
280
270
  // Collapse repeated greetings within a single turn. A turn can produce several
@@ -282,16 +272,8 @@ export async function runAgent({
282
272
  // each one, so the user sees "¡Hola Manu!" twice. Keep the first greeting,
283
273
  // strip any later one. Belt-and-suspenders over the action-discipline prompt
284
274
  // rule (which strong models follow but gemini-flash et al. often ignore).
285
- let greetedThisTurn = false;
286
- const dedupeGreeting = (text) => {
287
- if (!text) return text;
288
- if (greetedThisTurn) {
289
- const stripped = stripLeadingGreeting(text);
290
- return stripped == null ? text : stripped;
291
- }
292
- if (LEADING_GREETING_RE.test(text)) greetedThisTurn = true;
293
- return text;
294
- };
275
+ const greetingGuard = createGreetingGuard();
276
+ const dedupeGreeting = (text) => greetingGuard.apply(text);
295
277
  let usePseudoTools = false;
296
278
  let ackOnlyStreak = 0;
297
279
  // "Never end on silence": a model call that returns no tool calls AND no
@@ -307,24 +289,7 @@ export async function runAgent({
307
289
  // short-circuit duplicates with a synthetic "already done" result
308
290
  // instead of re-running. Read-only tools are exempt (idempotent and
309
291
  // sometimes legitimately repeated, like list_tasks before/after).
310
- const sideEffectExecuted = new Map();
311
- const SIDE_EFFECT_TOOLS = new Set([
312
- "send_telegram",
313
- "create_task",
314
- "write_file",
315
- "edit_file",
316
- "run_shell",
317
- "call_runtime",
318
- "add_project",
319
- "set_identity",
320
- ]);
321
- const sideEffectSignature = (name, args) => {
322
- try {
323
- return `${name}:${JSON.stringify(args)}`;
324
- } catch {
325
- return `${name}:<unserializable>`;
326
- }
327
- };
292
+ const sideEffects = createSideEffectLedger();
328
293
 
329
294
  // Stuck detection: catches the loops the side-effect dedupe can't — a
330
295
  // read-only call repeated with identical results, or the same call erroring
@@ -498,7 +463,7 @@ export async function runAgent({
498
463
 
499
464
  // Completion contract: `finish` declares the task done. Capture its
500
465
  // summary as the final text and stop processing the rest of this turn.
501
- if (name === "finish") {
466
+ if (name === TOOLS.FINISH) {
502
467
  finishSummary = typeof args.summary === "string" ? args.summary : "";
503
468
  break;
504
469
  }
@@ -517,13 +482,13 @@ export async function runAgent({
517
482
  iteration: iter + 1,
518
483
  });
519
484
  // Dedupe identical side-effecting calls within this turn.
520
- const sig = SIDE_EFFECT_TOOLS.has(name) ? sideEffectSignature(name, args) : null;
521
- if (sig && sideEffectExecuted.has(sig)) {
485
+ const sig = sideEffects.signature(name, args);
486
+ if (sideEffects.seen(sig)) {
522
487
  toolResult = {
523
488
  ok: true,
524
489
  deduped: true,
525
490
  note: `Ya ejecuté "${name}" con estos mismos argumentos en este turno; no lo repito.`,
526
- previous: sideEffectExecuted.get(sig),
491
+ previous: sideEffects.previous(sig),
527
492
  };
528
493
  await emitProgress(onEvent, {
529
494
  type: "tool_deduped",
@@ -570,7 +535,7 @@ export async function runAgent({
570
535
  if (toolHandlerCtx) toolHandlerCtx.securityGateCleared = false;
571
536
  }
572
537
  }
573
- if (sig) sideEffectExecuted.set(sig, summarizeForTrace(toolResult));
538
+ sideEffects.record(sig, summarizeForTrace(toolResult));
574
539
  }
575
540
 
576
541
  const traceItem = {
@@ -17,7 +17,7 @@ const RISK_ORDER = { LOW: 1, MEDIUM: 2, HIGH: 3 };
17
17
 
18
18
  // Loop-control / interaction tools that never touch the world — grading them
19
19
  // only burns tokens and trains the model to rubber-stamp the field.
20
- const RISK_EXEMPT_TOOLS = new Set(["finish", "ask_questions", "discover_tools"]);
20
+ import { RISK_EXEMPT_TOOLS } from "./tools/names.js";
21
21
 
22
22
  export function normalizeRisk(value) {
23
23
  const v = String(value || "").trim().toUpperCase();
@@ -14,11 +14,13 @@
14
14
  // The header inside the file picks up the current persona name from identity
15
15
  // (resolveAgentName) — never hardcode the agent name here.
16
16
  import fs from "node:fs";
17
- import os from "node:os";
18
17
  import path from "node:path";
18
+ import { SELF_MEMORY_PATH } from "../config/paths.js";
19
+
20
+ export { SELF_MEMORY_PATH };
19
21
  import { resolveAgentName } from "../identity/index.js";
20
22
 
21
- export const SELF_MEMORY_PATH = path.join(os.homedir(), ".apx", "memory.md");
23
+
22
24
 
23
25
  function notebookHeader() {
24
26
  let name = "";
@@ -36,13 +36,13 @@
36
36
 
37
37
  import fs from "node:fs";
38
38
  import path from "node:path";
39
- import os from "node:os";
39
+ import { SKILLS_INDEX_PATH } from "#core/config/paths.js";
40
40
 
41
41
  import { embedOne } from "#core/memory/embeddings.js";
42
42
  import { listSkills } from "./loader.js";
43
43
  import { condenseSkillDescription } from "./catalog.js";
44
44
 
45
- const INDEX_PATH = path.join(os.homedir(), ".apx", "skills", ".index.json");
45
+ const INDEX_PATH = SKILLS_INDEX_PATH;
46
46
 
47
47
  // ---------------------------------------------------------------------------
48
48
  // Disk I/O
@@ -30,9 +30,10 @@
30
30
 
31
31
  import fs from "node:fs";
32
32
  import path from "node:path";
33
- import os from "node:os";
33
+ import { SKILLS_DIR } from "#core/config/paths.js";
34
34
  import { fileURLToPath } from "node:url";
35
35
  import { apcSkillsDir } from "#core/apc/paths.js";
36
+ import { parseFrontmatter } from "#core/apc/frontmatter.js";
36
37
 
37
38
  const __filename = fileURLToPath(import.meta.url);
38
39
  const __dirname = path.dirname(__filename);
@@ -44,32 +45,12 @@ const PACKAGE_ROOT = path.resolve(__dirname, "..", "..", "..", "..");
44
45
  // <packageRoot>/skills/ so external tools that copy "skills/" from the repo
45
46
  // don't accidentally pull the rich set or the runtime CLI docs.
46
47
  const BUILTIN_SKILLS_DIR = path.join(PACKAGE_ROOT, "src", "core", "runtime-skills");
47
- const GLOBAL_DIR = path.join(os.homedir(), ".apx", "skills");
48
+ const GLOBAL_DIR = SKILLS_DIR;
48
49
 
49
50
  // ---------------------------------------------------------------------------
50
51
  // Frontmatter parsing (minimal — handles the YAML we ship)
51
52
  // ---------------------------------------------------------------------------
52
53
 
53
- function parseFrontmatter(raw) {
54
- if (!raw.startsWith("---")) return { fm: {}, body: raw };
55
- const end = raw.indexOf("\n---", 3);
56
- if (end < 0) return { fm: {}, body: raw };
57
-
58
- const fmBlock = raw.slice(3, end).trim();
59
- const body = raw.slice(end + 4).replace(/^\n/, "");
60
-
61
- const fm = {};
62
- for (const line of fmBlock.split("\n")) {
63
- const m = line.match(/^([a-zA-Z_][a-zA-Z0-9_-]*)\s*:\s*(.*)$/);
64
- if (!m) continue;
65
- let val = m[2].trim();
66
- if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
67
- val = val.slice(1, -1);
68
- }
69
- fm[m[1]] = val;
70
- }
71
- return { fm, body };
72
- }
73
54
 
74
55
  // ---------------------------------------------------------------------------
75
56
  // Directory scanners
@@ -80,8 +80,6 @@ export async function runSuperAgent({
80
80
  throw new Error("super-agent not enabled (set super_agent.enabled and .model in ~/.apx/config.json)");
81
81
  }
82
82
 
83
- const sa = globalConfig.super_agent;
84
-
85
83
  // Memory Broker (Pieza 4): assemble the [MEMORIA RELEVANTE] block before the
86
84
  // turn. Silent + bounded (≤ broker_budget_ms); skipped for tool-free callers
87
85
  // (summarize/ask) where injected context would only confuse the transcript.
@@ -1,4 +1,3 @@
1
- import fs from "node:fs";
2
1
  import path from "node:path";
3
2
  import { loggerFor } from "#core/logging.js";
4
3
  import { readAgents } from "#core/apc/parser.js";
@@ -15,9 +14,10 @@ import {
15
14
  findEngineSessionById,
16
15
  readEngineSessionContext,
17
16
  } from "#core/stores/engine-sessions.js";
18
- import { runProcess } from "#host/daemon/runtimes/_spawn.js";
19
- import { getRuntime, RUNTIME_IDS } from "#host/daemon/runtimes/index.js";
17
+ import { runProcess } from "#core/runtimes/_spawn.js";
18
+ import { getRuntime, RUNTIME_IDS } from "#core/runtimes/index.js";
20
19
  import { buildAgentSystem, resolveProject } from "../helpers.js";
20
+ import { readJson } from "#core/util/json-file.js";
21
21
 
22
22
  const log = loggerFor("call_runtime");
23
23
 
@@ -90,7 +90,7 @@ function resolveProjectForAgent(projects, project, slug) {
90
90
 
91
91
  function projectName(project) {
92
92
  try {
93
- const meta = JSON.parse(fs.readFileSync(apcProjectFile(project.path), "utf8"));
93
+ const meta = readJson(apcProjectFile(project.path), {});
94
94
  if (meta.name) return meta.name;
95
95
  } catch {}
96
96
  return path.basename(project.path);
@@ -3,7 +3,7 @@ import {
3
3
  ENGINES,
4
4
  collectAllSessions,
5
5
  findSessionAcrossEngines,
6
- } from "#interfaces/cli/commands/sessions.js";
6
+ } from "#core/sessions/index.js";
7
7
 
8
8
  // Your own work sessions. This is how you remember what you did — never tell
9
9
  // the user to run `apx session ...`; call this and answer from the result.