@agentprojectcontext/apx 1.77.3 → 1.79.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 (356) 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 +43 -53
  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/tool-summary.js +65 -0
  15. package/src/core/agent/tools/handlers/call-runtime.js +4 -4
  16. package/src/core/agent/tools/handlers/list-commitments.js +80 -0
  17. package/src/core/agent/tools/handlers/list-tasks.js +66 -27
  18. package/src/core/agent/tools/handlers/record-commitment.js +68 -0
  19. package/src/core/agent/tools/handlers/search-sessions.js +1 -1
  20. package/src/core/agent/tools/handlers/send-telegram.js +68 -2
  21. package/src/core/agent/tools/handlers/set-permission-mode.js +11 -4
  22. package/src/core/agent/tools/names.js +64 -0
  23. package/src/core/agent/tools/registry.js +9 -0
  24. package/src/core/agent/tools/tool-call-parser.js +70 -1
  25. package/src/core/apc/context-copy.js +1 -1
  26. package/src/core/apc/frontmatter.js +85 -0
  27. package/src/core/apc/parser.js +9 -21
  28. package/src/core/apc/paths.js +8 -8
  29. package/src/core/apc/scaffold.js +5 -4
  30. package/src/core/channels/telegram/ask-callbacks.js +35 -0
  31. package/src/core/channels/telegram/config.js +201 -0
  32. package/src/core/channels/telegram/dispatch.js +3 -0
  33. package/src/core/channels/telegram/reply.js +30 -5
  34. package/src/core/config/index.js +25 -183
  35. package/src/core/config/paths.js +57 -1
  36. package/src/core/config/redact.js +22 -0
  37. package/src/core/confirmation/adapters/web.js +1 -1
  38. package/src/core/daemon/service.js +238 -0
  39. package/src/core/deck/manifest.js +12 -12
  40. package/src/core/desktop/autostart.js +3 -2
  41. package/src/core/desktop/process.js +4 -4
  42. package/src/core/engines/gemini.js +322 -60
  43. package/src/core/engines/openai-compatible.js +21 -2
  44. package/src/core/engines/presets.js +2 -2
  45. package/src/core/http-tools/browser.js +1 -1
  46. package/src/core/http-tools/catalog.js +551 -0
  47. package/src/core/http-tools/fetch.js +1 -1
  48. package/src/core/http-tools/glob.js +3 -3
  49. package/src/core/http-tools/grep.js +2 -9
  50. package/src/core/http-tools/inline-handlers.js +59 -0
  51. package/src/core/http-tools/registry.js +31 -615
  52. package/src/core/http-tools/search.js +2 -2
  53. package/src/core/identity/self.js +6 -7
  54. package/src/core/identity/telegram.js +3 -3
  55. package/src/core/logging.js +6 -5
  56. package/src/core/mcp/sources.js +12 -16
  57. package/src/core/memory/consolidate.js +225 -0
  58. package/src/core/memory/indexer.js +2 -5
  59. package/src/core/nudge/index.js +192 -0
  60. package/src/core/nudge/policy.js +143 -0
  61. package/src/core/nudge/store.js +141 -0
  62. package/src/core/profiles/bundled/secretary/PROFILE.md +8 -9
  63. package/src/core/profiles/bundled/secretary/config.schema.json +33 -3
  64. package/src/core/profiles/bundled/secretary/routines/day-close.json +7 -3
  65. package/src/core/profiles/bundled/secretary/routines/day-open.json +7 -3
  66. package/src/core/profiles/bundled/secretary/routines/watch.json +13 -0
  67. package/src/core/profiles/lifecycle.js +0 -2
  68. package/src/core/profiles/store.js +2 -7
  69. package/src/core/routines/runner.js +103 -4
  70. package/src/core/routines/signals.js +270 -0
  71. package/src/core/runtime-skills/apx-agency-agents/SKILL.md +2 -2
  72. package/src/core/runtime-skills/apx-profile/SKILL.md +3 -3
  73. package/src/core/runtime-skills/apx-project/SKILL.md +1 -1
  74. package/src/core/runtime-skills/apx-task/SKILL.md +8 -8
  75. package/src/core/runtime-skills/apx-telegram/SKILL.md +4 -4
  76. package/src/core/runtime-skills/apx-voice/SKILL.md +4 -4
  77. package/src/core/runtimes/detect.js +2 -2
  78. package/src/core/sessions/index.js +800 -0
  79. package/src/core/stores/code-sessions.js +3 -14
  80. package/src/core/stores/commitments.js +331 -0
  81. package/src/core/stores/engine-sessions.js +0 -4
  82. package/src/core/stores/messages.js +4 -5
  83. package/src/core/stores/organization.js +9 -11
  84. package/src/core/stores/routines.js +23 -14
  85. package/src/core/stores/runtime-callbacks.js +2 -1
  86. package/src/core/util/json-file.js +128 -0
  87. package/src/core/util/thinking.js +51 -0
  88. package/src/core/vars/sources.js +14 -21
  89. package/src/core/voice/stt-models.js +1 -1
  90. package/src/core/voice/tts.js +5 -4
  91. package/src/host/daemon/api/admin-config.js +5 -5
  92. package/src/host/daemon/api/admin.js +8 -7
  93. package/src/host/daemon/api/agents.js +16 -16
  94. package/src/host/daemon/api/artifact-preview.js +8 -8
  95. package/src/host/daemon/api/artifacts.js +7 -7
  96. package/src/host/daemon/api/code.js +8 -8
  97. package/src/host/daemon/api/commitments.js +135 -0
  98. package/src/host/daemon/api/config.js +6 -6
  99. package/src/host/daemon/api/confirm.js +2 -2
  100. package/src/host/daemon/api/connections.js +2 -2
  101. package/src/host/daemon/api/conversations.js +12 -12
  102. package/src/host/daemon/api/deck.js +6 -6
  103. package/src/host/daemon/api/desktop.js +8 -8
  104. package/src/host/daemon/api/embeddings.js +4 -4
  105. package/src/host/daemon/api/engines.js +8 -7
  106. package/src/host/daemon/api/exec.js +5 -4
  107. package/src/host/daemon/api/files-project.js +6 -6
  108. package/src/host/daemon/api/health.js +2 -2
  109. package/src/host/daemon/api/identity.js +3 -3
  110. package/src/host/daemon/api/inbox.js +2 -2
  111. package/src/host/daemon/api/integrations.js +23 -19
  112. package/src/host/daemon/api/mcps.js +16 -12
  113. package/src/host/daemon/api/messages.js +5 -5
  114. package/src/host/daemon/api/nudges.js +112 -0
  115. package/src/host/daemon/api/organization.js +8 -8
  116. package/src/host/daemon/api/pairing.js +6 -6
  117. package/src/host/daemon/api/plugins.js +3 -3
  118. package/src/host/daemon/api/prefix.js +20 -0
  119. package/src/host/daemon/api/profiles.js +9 -9
  120. package/src/host/daemon/api/projects.js +5 -5
  121. package/src/host/daemon/api/routines.js +32 -8
  122. package/src/host/daemon/api/run.js +2 -2
  123. package/src/host/daemon/api/runtimes.js +6 -6
  124. package/src/host/daemon/api/self-memory.js +50 -0
  125. package/src/host/daemon/api/sessions-search.js +18 -11
  126. package/src/host/daemon/api/sessions.js +6 -6
  127. package/src/host/daemon/api/shared.js +59 -13
  128. package/src/host/daemon/api/skills.js +12 -12
  129. package/src/host/daemon/api/super-agent.js +4 -4
  130. package/src/host/daemon/api/tasks.js +11 -11
  131. package/src/host/daemon/api/telegram.js +61 -24
  132. package/src/host/daemon/api/tools.js +7 -7
  133. package/src/host/daemon/api/top-level.js +7 -7
  134. package/src/host/daemon/api/transcribe.js +6 -6
  135. package/src/host/daemon/api/tts.js +3 -3
  136. package/src/host/daemon/api/vars.js +11 -8
  137. package/src/host/daemon/api/voice.js +9 -7
  138. package/src/host/daemon/api/web.js +28 -32
  139. package/src/host/daemon/api.js +83 -48
  140. package/src/host/daemon/callback-reconciler.js +16 -0
  141. package/src/host/daemon/db.js +1 -1
  142. package/src/host/daemon/desktop-ws.js +7 -3
  143. package/src/host/daemon/index.js +11 -5
  144. package/src/host/daemon/plugins/desktop/index.js +8 -2
  145. package/src/host/daemon/plugins/telegram/index.js +7 -2
  146. package/src/host/daemon/stt-venv.js +20 -81
  147. package/src/host/daemon/wakeup.js +18 -4
  148. package/src/interfaces/acp/index.js +3 -3
  149. package/src/interfaces/acp/session.js +2 -2
  150. package/src/interfaces/cli/commands/a2a.js +2 -2
  151. package/src/interfaces/cli/commands/agent.js +3 -3
  152. package/src/interfaces/cli/commands/artifact.js +13 -13
  153. package/src/interfaces/cli/commands/chat.js +3 -3
  154. package/src/interfaces/cli/commands/command.js +2 -2
  155. package/src/interfaces/cli/commands/commitment.js +154 -0
  156. package/src/interfaces/cli/commands/config.js +4 -4
  157. package/src/interfaces/cli/commands/daemon.js +74 -18
  158. package/src/interfaces/cli/commands/desktop.js +7 -14
  159. package/src/interfaces/cli/commands/exec.js +2 -2
  160. package/src/interfaces/cli/commands/log.js +3 -4
  161. package/src/interfaces/cli/commands/mcp.js +9 -9
  162. package/src/interfaces/cli/commands/memory.js +75 -2
  163. package/src/interfaces/cli/commands/messages.js +5 -5
  164. package/src/interfaces/cli/commands/model.js +0 -18
  165. package/src/interfaces/cli/commands/nudge.js +130 -0
  166. package/src/interfaces/cli/commands/obsidian.js +5 -5
  167. package/src/interfaces/cli/commands/org.js +5 -5
  168. package/src/interfaces/cli/commands/pair.js +6 -6
  169. package/src/interfaces/cli/commands/plugins.js +2 -2
  170. package/src/interfaces/cli/commands/profile.js +10 -10
  171. package/src/interfaces/cli/commands/project-config.js +6 -6
  172. package/src/interfaces/cli/commands/project.js +10 -10
  173. package/src/interfaces/cli/commands/routine.js +8 -9
  174. package/src/interfaces/cli/commands/runtime.js +2 -2
  175. package/src/interfaces/cli/commands/search.js +0 -1
  176. package/src/interfaces/cli/commands/session.js +12 -40
  177. package/src/interfaces/cli/commands/sessions.js +21 -798
  178. package/src/interfaces/cli/commands/setup.js +16 -6
  179. package/src/interfaces/cli/commands/skills.js +1 -2
  180. package/src/interfaces/cli/commands/status.js +3 -3
  181. package/src/interfaces/cli/commands/task.js +8 -8
  182. package/src/interfaces/cli/commands/telegram.js +21 -21
  183. package/src/interfaces/cli/commands/voice.js +6 -6
  184. package/src/interfaces/cli/help/index.js +2245 -0
  185. package/src/interfaces/cli/http.js +9 -4
  186. package/src/interfaces/cli/index.js +14 -2857
  187. package/src/interfaces/cli/routes/acp.js +13 -0
  188. package/src/interfaces/cli/routes/agent.js +27 -0
  189. package/src/interfaces/cli/routes/artifact.js +28 -0
  190. package/src/interfaces/cli/routes/chat.js +11 -0
  191. package/src/interfaces/cli/routes/code.js +11 -0
  192. package/src/interfaces/cli/routes/command.js +21 -0
  193. package/src/interfaces/cli/routes/commitment.js +19 -0
  194. package/src/interfaces/cli/routes/config.js +16 -0
  195. package/src/interfaces/cli/routes/connections.js +11 -0
  196. package/src/interfaces/cli/routes/conversations.js +21 -0
  197. package/src/interfaces/cli/routes/daemon.js +25 -0
  198. package/src/interfaces/cli/routes/desktop.js +20 -0
  199. package/src/interfaces/cli/routes/env.js +13 -0
  200. package/src/interfaces/cli/routes/exec.js +11 -0
  201. package/src/interfaces/cli/routes/identity.js +11 -0
  202. package/src/interfaces/cli/routes/index.js +75 -0
  203. package/src/interfaces/cli/routes/init.js +11 -0
  204. package/src/interfaces/cli/routes/log.js +20 -0
  205. package/src/interfaces/cli/routes/mcp.js +22 -0
  206. package/src/interfaces/cli/routes/memory.js +19 -0
  207. package/src/interfaces/cli/routes/messages.js +16 -0
  208. package/src/interfaces/cli/routes/model.js +11 -0
  209. package/src/interfaces/cli/routes/nudge.js +17 -0
  210. package/src/interfaces/cli/routes/obsidian.js +17 -0
  211. package/src/interfaces/cli/routes/org.js +34 -0
  212. package/src/interfaces/cli/routes/overlay.js +10 -0
  213. package/src/interfaces/cli/routes/pair.js +17 -0
  214. package/src/interfaces/cli/routes/panel.js +16 -0
  215. package/src/interfaces/cli/routes/permission.js +11 -0
  216. package/src/interfaces/cli/routes/plugins.js +21 -0
  217. package/src/interfaces/cli/routes/profile.js +27 -0
  218. package/src/interfaces/cli/routes/project.js +41 -0
  219. package/src/interfaces/cli/routes/restart.js +15 -0
  220. package/src/interfaces/cli/routes/routine.js +28 -0
  221. package/src/interfaces/cli/routes/run.js +11 -0
  222. package/src/interfaces/cli/routes/search.js +11 -0
  223. package/src/interfaces/cli/routes/send.js +11 -0
  224. package/src/interfaces/cli/routes/session.js +26 -0
  225. package/src/interfaces/cli/routes/sessions.js +15 -0
  226. package/src/interfaces/cli/routes/setup.js +18 -0
  227. package/src/interfaces/cli/routes/skills.js +20 -0
  228. package/src/interfaces/cli/routes/status.js +12 -0
  229. package/src/interfaces/cli/routes/task.js +26 -0
  230. package/src/interfaces/cli/routes/telegram.js +39 -0
  231. package/src/interfaces/cli/routes/update.js +24 -0
  232. package/src/interfaces/cli/routes/voice.js +17 -0
  233. package/src/interfaces/desktop/main.js +6 -7
  234. package/src/interfaces/desktop/renderer.js +0 -5
  235. package/src/interfaces/mcp-server/index.js +15 -14
  236. package/src/interfaces/tui/_shims/globals.d.ts +8 -0
  237. package/src/interfaces/tui/tsconfig.json +4 -1
  238. package/src/interfaces/web/README.md +6 -6
  239. package/src/interfaces/web/dist/assets/index-CvEoGtTf.js +849 -0
  240. package/src/interfaces/web/dist/assets/index-CvEoGtTf.js.map +1 -0
  241. package/src/interfaces/web/dist/assets/index-DzBBXFaO.css +1 -0
  242. package/src/interfaces/web/dist/index.html +2 -2
  243. package/src/interfaces/web/package-lock.json +11 -10
  244. package/src/interfaces/web/src/components/AddProjectDialog.tsx +1 -1
  245. package/src/interfaces/web/src/components/Section.tsx +18 -3
  246. package/src/interfaces/web/src/components/agents/AgentFormFields.tsx +1 -1
  247. package/src/interfaces/web/src/components/chat/ChatList.tsx +2 -2
  248. package/src/interfaces/web/src/components/chat/MessageBubble.tsx +13 -0
  249. package/src/interfaces/web/src/components/chat/SkillPicker.tsx +1 -1
  250. package/src/interfaces/web/src/components/code/CodeFileTree.tsx +1 -1
  251. package/src/interfaces/web/src/components/code/CodeTerminal.tsx +1 -1
  252. package/src/interfaces/web/src/components/cron/CronPicker.tsx +196 -0
  253. package/src/interfaces/web/src/components/desktop/DesktopStatusCard.tsx +1 -1
  254. package/src/interfaces/web/src/components/files/FileBrowser.tsx +2 -2
  255. package/src/interfaces/web/src/components/inbox/InboxList.tsx +145 -0
  256. package/src/interfaces/web/src/components/memory/MemoryBrowser.tsx +36 -6
  257. package/src/interfaces/web/src/components/routines/ExecutionsList.tsx +1 -1
  258. package/src/interfaces/web/src/components/routines/RoutineDetail.tsx +16 -4
  259. package/src/interfaces/web/src/components/routines/RoutineEditor.tsx +14 -4
  260. package/src/interfaces/web/src/components/routines/shared.ts +14 -5
  261. package/src/interfaces/web/src/components/settings/DesktopSettingsPanel.tsx +1 -1
  262. package/src/interfaces/web/src/components/settings/MemoryPanel.tsx +1 -1
  263. package/src/interfaces/web/src/components/settings/NudgePanel.tsx +183 -0
  264. package/src/interfaces/web/src/components/settings/ProfilePanel.tsx +36 -12
  265. package/src/interfaces/web/src/components/settings/SkillsInspectorPanel.tsx +1 -1
  266. package/src/interfaces/web/src/components/settings/SkillsManager.tsx +2 -2
  267. package/src/interfaces/web/src/components/tasks/TaskDetailPanel.tsx +1 -1
  268. package/src/interfaces/web/src/components/ui/filter-chips.tsx +47 -0
  269. package/src/interfaces/web/src/components/ui.tsx +1 -0
  270. package/src/interfaces/web/src/constants/index.ts +1 -0
  271. package/src/interfaces/web/src/hooks/useChat.ts +5 -1
  272. package/src/interfaces/web/src/hooks/useDaemonStatus.ts +1 -1
  273. package/src/interfaces/web/src/hooks/useDevices.ts +1 -1
  274. package/src/interfaces/web/src/hooks/useEngines.ts +1 -1
  275. package/src/interfaces/web/src/hooks/useGlobalConfig.ts +2 -2
  276. package/src/interfaces/web/src/hooks/useIdentity.ts +1 -1
  277. package/src/interfaces/web/src/hooks/useInbox.ts +1 -1
  278. package/src/interfaces/web/src/hooks/useNudges.ts +38 -0
  279. package/src/interfaces/web/src/hooks/useProfiles.ts +3 -3
  280. package/src/interfaces/web/src/hooks/useProjects.ts +1 -1
  281. package/src/interfaces/web/src/hooks/useTelegram.ts +3 -3
  282. package/src/interfaces/web/src/hooks/useTokenBootstrap.ts +3 -3
  283. package/src/interfaces/web/src/i18n/en.ts +127 -0
  284. package/src/interfaces/web/src/i18n/es.ts +127 -0
  285. package/src/interfaces/web/src/lib/api/admin.ts +11 -11
  286. package/src/interfaces/web/src/lib/api/agents.ts +14 -14
  287. package/src/interfaces/web/src/lib/api/artifacts.ts +11 -11
  288. package/src/interfaces/web/src/lib/api/code.ts +1 -1
  289. package/src/interfaces/web/src/lib/api/commitments.ts +57 -0
  290. package/src/interfaces/web/src/lib/api/conversations.ts +8 -8
  291. package/src/interfaces/web/src/lib/api/deck.ts +3 -3
  292. package/src/interfaces/web/src/lib/api/desktop.ts +8 -8
  293. package/src/interfaces/web/src/lib/api/embeddings.ts +3 -3
  294. package/src/interfaces/web/src/lib/api/engines.ts +3 -3
  295. package/src/interfaces/web/src/lib/api/filesystem.ts +2 -2
  296. package/src/interfaces/web/src/lib/api/health.ts +1 -1
  297. package/src/interfaces/web/src/lib/api/identity.ts +2 -2
  298. package/src/interfaces/web/src/lib/api/inbox.ts +1 -1
  299. package/src/interfaces/web/src/lib/api/integrations.ts +8 -8
  300. package/src/interfaces/web/src/lib/api/mcps.ts +6 -6
  301. package/src/interfaces/web/src/lib/api/messages.ts +3 -3
  302. package/src/interfaces/web/src/lib/api/notebook.ts +23 -0
  303. package/src/interfaces/web/src/lib/api/nudges.ts +53 -0
  304. package/src/interfaces/web/src/lib/api/organization.ts +7 -7
  305. package/src/interfaces/web/src/lib/api/profiles.ts +8 -8
  306. package/src/interfaces/web/src/lib/api/projectFiles.ts +5 -5
  307. package/src/interfaces/web/src/lib/api/projects.ts +12 -12
  308. package/src/interfaces/web/src/lib/api/routines.ts +7 -7
  309. package/src/interfaces/web/src/lib/api/sessions.ts +2 -2
  310. package/src/interfaces/web/src/lib/api/skills.ts +11 -11
  311. package/src/interfaces/web/src/lib/api/super_agent.ts +3 -3
  312. package/src/interfaces/web/src/lib/api/tasks.ts +12 -12
  313. package/src/interfaces/web/src/lib/api/telegram.ts +14 -14
  314. package/src/interfaces/web/src/lib/api/tools.ts +1 -1
  315. package/src/interfaces/web/src/lib/api/vars.ts +4 -4
  316. package/src/interfaces/web/src/lib/api/voice.ts +6 -6
  317. package/src/interfaces/web/src/lib/cron.ts +196 -0
  318. package/src/interfaces/web/src/lib/when.ts +32 -0
  319. package/src/interfaces/web/src/screens/InboxScreen.tsx +107 -77
  320. package/src/interfaces/web/src/screens/ProjectScreen.tsx +5 -2
  321. package/src/interfaces/web/src/screens/SettingsScreen.tsx +17 -3
  322. package/src/interfaces/web/src/screens/base/AgentDefaultsTab.tsx +1 -1
  323. package/src/interfaces/web/src/screens/base/CommitmentsTab.tsx +239 -0
  324. package/src/interfaces/web/src/screens/base/GlobalTasksTab.tsx +103 -20
  325. package/src/interfaces/web/src/screens/base/LogsTab.tsx +18 -3
  326. package/src/interfaces/web/src/screens/base/SessionsTab.tsx +1 -1
  327. package/src/interfaces/web/src/screens/modules/CodeScreen.tsx +1 -1
  328. package/src/interfaces/web/src/screens/modules/DeckScreen.tsx +1 -1
  329. package/src/interfaces/web/src/screens/modules/DesktopScreen.tsx +1 -1
  330. package/src/interfaces/web/src/screens/modules/VoiceScreen.tsx +1 -1
  331. package/src/interfaces/web/src/screens/project/AgentDetailScreen.tsx +8 -8
  332. package/src/interfaces/web/src/screens/project/AgentsTab.tsx +2 -2
  333. package/src/interfaces/web/src/screens/project/ChatTab.tsx +24 -6
  334. package/src/interfaces/web/src/screens/project/ConfigTab.tsx +1 -1
  335. package/src/interfaces/web/src/screens/project/McpsTab.tsx +3 -3
  336. package/src/interfaces/web/src/screens/project/Overview.tsx +6 -6
  337. package/src/interfaces/web/src/screens/project/RoutinesTab.tsx +15 -13
  338. package/src/interfaces/web/src/screens/project/StructureTab.tsx +1 -1
  339. package/src/interfaces/web/src/screens/project/TasksTab.tsx +1 -1
  340. package/src/interfaces/web/src/screens/project/VarsTab.tsx +1 -1
  341. package/src/interfaces/web/src/types/daemon.ts +10 -1
  342. package/src/interfaces/web/vite.config.ts +13 -18
  343. package/src/interfaces/web/dist/assets/index-BptlbKjU.js +0 -824
  344. package/src/interfaces/web/dist/assets/index-BptlbKjU.js.map +0 -1
  345. package/src/interfaces/web/dist/assets/index-D_EJEA1n.css +0 -1
  346. package/src/skills/apc-context/SKILL.md +0 -159
  347. /package/src/{host/daemon → core}/runtimes/_spawn.js +0 -0
  348. /package/src/{host/daemon → core}/runtimes/aider.js +0 -0
  349. /package/src/{host/daemon → core}/runtimes/antigravity.js +0 -0
  350. /package/src/{host/daemon → core}/runtimes/claude-code.js +0 -0
  351. /package/src/{host/daemon → core}/runtimes/codex.js +0 -0
  352. /package/src/{host/daemon → core}/runtimes/cursor-agent.js +0 -0
  353. /package/src/{host/daemon → core}/runtimes/gemini-cli.js +0 -0
  354. /package/src/{host/daemon → core}/runtimes/index.js +0 -0
  355. /package/src/{host/daemon → core}/runtimes/opencode.js +0 -0
  356. /package/src/{host/daemon → core}/runtimes/qwen-code.js +0 -0
@@ -6,7 +6,11 @@
6
6
  // POST /telegram/send_photo { chat_id?, photo, caption?, parse_mode?, channel? }
7
7
  // POST /telegram/send_voice { chat_id?, audio, caption?, duration?, channel? }
8
8
  // POST /telegram/send_audio { chat_id?, audio, caption?, title?, performer?, channel? }
9
- // POST /telegram/notify (alias of /telegram/send; daemon-initiated pushes)
9
+ // POST /telegram/notify { chat_id?, text, channel?, kind?, project_id?,
10
+ // severity?, unsolicited? }
11
+ // Daemon-initiated pushes. Goes through the
12
+ // interruption budget (core/nudge) and answers
13
+ // 429 with a retry_after_ms when suppressed.
10
14
  //
11
15
  // GET /telegram/channels — list configured channels
12
16
  // POST /telegram/channels { name, ... } — create or replace one channel
@@ -31,7 +35,6 @@ import {
31
35
  removeTelegramChannel,
32
36
  unsetTelegramChannelFields,
33
37
  listContacts,
34
- findContact,
35
38
  upsertContact,
36
39
  removeContact,
37
40
  listRoles,
@@ -40,14 +43,15 @@ import {
40
43
  } from "#core/config/index.js";
41
44
 
42
45
  import { redactChannel, isSecretMarker } from "#core/config/redact.js";
46
+ import { canNudge, recordNudge, nudgeFeedbackKeyboard } from "#core/nudge/index.js";
43
47
 
44
- export function register(app, { telegram }) {
45
- app.get("/telegram/status", (_req, res) => {
48
+ export function register(api, { telegram }) {
49
+ api.get("/telegram/status", (_req, res) => {
46
50
  if (!telegram) return res.json({ enabled: false, channels: [] });
47
51
  res.json(telegram.status());
48
52
  });
49
53
 
50
- app.post("/telegram/start", (_req, res) => {
54
+ api.post("/telegram/start", (_req, res) => {
51
55
  if (!telegram)
52
56
  return res.status(503).json({ error: "telegram plugin not loaded" });
53
57
  try {
@@ -58,7 +62,7 @@ export function register(app, { telegram }) {
58
62
  }
59
63
  });
60
64
 
61
- app.post("/telegram/stop", (_req, res) => {
65
+ api.post("/telegram/stop", (_req, res) => {
62
66
  if (!telegram)
63
67
  return res.status(503).json({ error: "telegram plugin not loaded" });
64
68
  try {
@@ -69,7 +73,7 @@ export function register(app, { telegram }) {
69
73
  }
70
74
  });
71
75
 
72
- app.post("/telegram/send", async (req, res) => {
76
+ api.post("/telegram/send", async (req, res) => {
73
77
  const { chat_id, text, channel } = req.body || {};
74
78
  if (!text) return res.status(400).json({ error: "text required" });
75
79
  if (!telegram)
@@ -82,7 +86,7 @@ export function register(app, { telegram }) {
82
86
  }
83
87
  });
84
88
 
85
- app.post("/telegram/send_photo", async (req, res) => {
89
+ api.post("/telegram/send_photo", async (req, res) => {
86
90
  const { chat_id, photo, caption, parse_mode, channel } = req.body || {};
87
91
  if (!photo)
88
92
  return res.status(400).json({ error: "photo required (path or url)" });
@@ -102,7 +106,7 @@ export function register(app, { telegram }) {
102
106
  }
103
107
  });
104
108
 
105
- app.post("/telegram/send_voice", async (req, res) => {
109
+ api.post("/telegram/send_voice", async (req, res) => {
106
110
  const { chat_id, audio, caption, duration, channel } = req.body || {};
107
111
  if (!audio) return res.status(400).json({ error: "audio required (path)" });
108
112
  if (!telegram)
@@ -121,7 +125,7 @@ export function register(app, { telegram }) {
121
125
  }
122
126
  });
123
127
 
124
- app.post("/telegram/send_audio", async (req, res) => {
128
+ api.post("/telegram/send_audio", async (req, res) => {
125
129
  const { chat_id, audio, caption, title, performer, channel } =
126
130
  req.body || {};
127
131
  if (!audio) return res.status(400).json({ error: "audio required (path)" });
@@ -145,7 +149,7 @@ export function register(app, { telegram }) {
145
149
  // ── Channel CRUD (config-only; caller must POST /admin/reload to apply) ──
146
150
  // We read fresh config from disk on each call so concurrent writes from the
147
151
  // CLI and the daemon don't clobber one another via stale closures.
148
- app.get("/telegram/channels", (_req, res) => {
152
+ api.get("/telegram/channels", (_req, res) => {
149
153
  try {
150
154
  const cfg = readConfig();
151
155
  res.json({ channels: listTelegramChannels(cfg).map(redactChannel) });
@@ -154,7 +158,7 @@ export function register(app, { telegram }) {
154
158
  }
155
159
  });
156
160
 
157
- app.post("/telegram/channels", (req, res) => {
161
+ api.post("/telegram/channels", (req, res) => {
158
162
  const body = req.body || {};
159
163
  if (!body.name || typeof body.name !== "string") {
160
164
  return res.status(400).json({ error: "name required" });
@@ -173,7 +177,7 @@ export function register(app, { telegram }) {
173
177
  }
174
178
  });
175
179
 
176
- app.patch("/telegram/channels/:name", (req, res) => {
180
+ api.patch("/telegram/channels/:name", (req, res) => {
177
181
  const { name } = req.params;
178
182
  const body = req.body || {};
179
183
  try {
@@ -202,7 +206,7 @@ export function register(app, { telegram }) {
202
206
  }
203
207
  });
204
208
 
205
- app.delete("/telegram/channels/:name", (req, res) => {
209
+ api.delete("/telegram/channels/:name", (req, res) => {
206
210
  const { name } = req.params;
207
211
  try {
208
212
  const cfg = readConfig();
@@ -219,7 +223,7 @@ export function register(app, { telegram }) {
219
223
  // channel. These edit ~/.apx/config.json directly; the running poller picks
220
224
  // changes up on its next inbound message (it re-reads config per message), so
221
225
  // no /admin/reload is required for contact/role edits.
222
- app.get("/telegram/contacts", (_req, res) => {
226
+ api.get("/telegram/contacts", (_req, res) => {
223
227
  try {
224
228
  const cfg = readConfig();
225
229
  res.json({
@@ -235,7 +239,7 @@ export function register(app, { telegram }) {
235
239
  }
236
240
  });
237
241
 
238
- app.patch("/telegram/contacts/:user_id", (req, res) => {
242
+ api.patch("/telegram/contacts/:user_id", (req, res) => {
239
243
  const { user_id } = req.params;
240
244
  const body = req.body || {};
241
245
  try {
@@ -254,7 +258,7 @@ export function register(app, { telegram }) {
254
258
  }
255
259
  });
256
260
 
257
- app.delete("/telegram/contacts/:user_id", (req, res) => {
261
+ api.delete("/telegram/contacts/:user_id", (req, res) => {
258
262
  const { user_id } = req.params;
259
263
  try {
260
264
  const cfg = readConfig();
@@ -267,7 +271,7 @@ export function register(app, { telegram }) {
267
271
  });
268
272
 
269
273
  // ── Roles (role → { tools }) ────────────────────────────────────────────
270
- app.get("/telegram/roles", (_req, res) => {
274
+ api.get("/telegram/roles", (_req, res) => {
271
275
  try {
272
276
  res.json({ roles: listRoles(readConfig()) });
273
277
  } catch (e) {
@@ -275,7 +279,7 @@ export function register(app, { telegram }) {
275
279
  }
276
280
  });
277
281
 
278
- app.put("/telegram/roles/:name", (req, res) => {
282
+ api.put("/telegram/roles/:name", (req, res) => {
279
283
  const { name } = req.params;
280
284
  const body = req.body || {};
281
285
  try {
@@ -288,7 +292,7 @@ export function register(app, { telegram }) {
288
292
  }
289
293
  });
290
294
 
291
- app.delete("/telegram/roles/:name", (req, res) => {
295
+ api.delete("/telegram/roles/:name", (req, res) => {
292
296
  const { name } = req.params;
293
297
  try {
294
298
  const { removed } = removeRole(readConfig(), name);
@@ -300,14 +304,47 @@ export function register(app, { telegram }) {
300
304
  });
301
305
 
302
306
  // Alias for proactive daemon-initiated pushes (routines, error handlers, …).
303
- app.post("/telegram/notify", async (req, res) => {
304
- const { chat_id, text, channel } = req.body || {};
307
+ //
308
+ // PUSH PATH 2 OF 4 this endpoint exists FOR unrequested messages, so the
309
+ // interruption budget applies by default. A caller delivering something the
310
+ // user is waiting for says so with `unsolicited: false`, and that choice is
311
+ // then visible in its own diff rather than hidden in this handler.
312
+ api.post("/telegram/notify", async (req, res) => {
313
+ const { chat_id, text, channel, kind, project_id, severity, unsolicited } = req.body || {};
305
314
  if (!text) return res.status(400).json({ error: "text required" });
306
315
  if (!telegram)
307
316
  return res.status(503).json({ error: "telegram plugin not loaded" });
317
+
318
+ const gate = canNudge(
319
+ {
320
+ kind: kind || "notify",
321
+ project_id: project_id ?? null,
322
+ severity: severity || "normal",
323
+ unsolicited: unsolicited !== false,
324
+ channel: "telegram",
325
+ },
326
+ readConfig(),
327
+ );
328
+ if (!gate.allowed) {
329
+ // 429, not 500: nothing failed. The caller is being asked to wait, and
330
+ // told for how long, so a routine can decide to try again rather than
331
+ // treating a working guardrail as an outage.
332
+ return res.status(429).json({
333
+ ok: false,
334
+ suppressed: true,
335
+ reason: gate.reason,
336
+ retry_after_ms: gate.retry_after_ms,
337
+ });
338
+ }
339
+
308
340
  try {
309
- const r = await telegram.send({ chat_id, text, channel });
310
- res.status(202).json({ ok: true, message_id: r.message_id, via: "notify" });
341
+ const r = await telegram.send({
342
+ chat_id, text, channel,
343
+ reply_markup: gate.unsolicited ? nudgeFeedbackKeyboard(gate.nudge_id) : undefined,
344
+ meta: { nudge_id: gate.unsolicited ? gate.nudge_id : undefined, nudge_kind: gate.kind },
345
+ });
346
+ recordNudge(gate, { chat_id, preview: text });
347
+ res.status(202).json({ ok: true, message_id: r.message_id, via: "notify", nudge_id: gate.nudge_id });
311
348
  } catch (e) {
312
349
  res.status(502).json({ error: e.message });
313
350
  }
@@ -11,11 +11,11 @@ import { buildRegistryRouter } from "#core/http-tools/registry.js";
11
11
  import { buildGlobRouter } from "#core/http-tools/glob.js";
12
12
  import { buildGrepRouter } from "#core/http-tools/grep.js";
13
13
 
14
- export function register(app, { express, projects, registries }) {
15
- app.use("/tools/fetch", buildFetchRouter(express));
16
- app.use("/tools/browser", buildBrowserRouter(express));
17
- app.use("/tools/search", buildSearchRouter(express));
18
- app.use("/tools/glob", buildGlobRouter(express));
19
- app.use("/tools/grep", buildGrepRouter(express));
20
- app.use("/tools", buildRegistryRouter(express, { projects, registries }));
14
+ export function register(api, { express, projects, registries }) {
15
+ api.use("/tools/fetch", buildFetchRouter(express));
16
+ api.use("/tools/browser", buildBrowserRouter(express));
17
+ api.use("/tools/search", buildSearchRouter(express));
18
+ api.use("/tools/glob", buildGlobRouter(express));
19
+ api.use("/tools/grep", buildGrepRouter(express));
20
+ api.use("/tools", buildRegistryRouter(express, { projects, registries }));
21
21
  }
@@ -12,9 +12,9 @@ import fs from "node:fs";
12
12
  import path from "node:path";
13
13
  import { resolveMemoryPath } from "./shared.js";
14
14
 
15
- export function register(app, { projects, registries, resolveTopProject }) {
15
+ export function register(api, { projects, registries, resolveTopProject }) {
16
16
  // ---- /memory ----
17
- app.get("/memory", (req, res) => {
17
+ api.get("/memory", (req, res) => {
18
18
  const p = resolveTopProject(req.query);
19
19
  if (!p) return res.status(404).json({ error: "no project registered" });
20
20
  const memPath = resolveMemoryPath(p);
@@ -22,7 +22,7 @@ export function register(app, { projects, registries, resolveTopProject }) {
22
22
  res.json({ project_id: p.id, path: memPath, body });
23
23
  });
24
24
 
25
- app.post("/memory", (req, res) => {
25
+ api.post("/memory", (req, res) => {
26
26
  const p = resolveTopProject(req.query);
27
27
  if (!p) return res.status(404).json({ error: "no project registered" });
28
28
  const { body } = req.body || {};
@@ -42,7 +42,7 @@ export function register(app, { projects, registries, resolveTopProject }) {
42
42
  });
43
43
 
44
44
  // ---- /files ----
45
- app.get("/files", (req, res) => {
45
+ api.get("/files", (req, res) => {
46
46
  const p = resolveTopProject(req.query);
47
47
  if (!p) return res.status(404).json({ error: "no project registered" });
48
48
  const rel = req.query.path;
@@ -90,7 +90,7 @@ export function register(app, { projects, registries, resolveTopProject }) {
90
90
  });
91
91
  });
92
92
 
93
- app.post("/files", (req, res) => {
93
+ api.post("/files", (req, res) => {
94
94
  const p = resolveTopProject(req.query);
95
95
  if (!p) return res.status(404).json({ error: "no project registered" });
96
96
  const { path: rel, content } = req.body || {};
@@ -111,13 +111,13 @@ export function register(app, { projects, registries, resolveTopProject }) {
111
111
  });
112
112
 
113
113
  // ---- /mcp ----
114
- app.get("/mcp", (req, res) => {
114
+ api.get("/mcp", (req, res) => {
115
115
  const p = resolveTopProject(req.query);
116
116
  if (!p) return res.status(404).json({ error: "no project registered" });
117
117
  res.json(registries.for(p).list());
118
118
  });
119
119
 
120
- app.post("/mcp/run", async (req, res) => {
120
+ api.post("/mcp/run", async (req, res) => {
121
121
  const { project: projectRef, name, tool, params } = req.body || {};
122
122
  if (!name) return res.status(400).json({ error: "name required" });
123
123
  if (!tool) return res.status(400).json({ error: "tool required" });
@@ -5,12 +5,12 @@
5
5
  // X-Provider auto | local | openai | custom (overrides config)
6
6
  //
7
7
  // Shared by overlay, telegram voice messages, and any external caller.
8
- export function register(app) {
8
+ export function register(api) {
9
9
  // GET /transcribe/providers — STT engine list + availability for the web
10
10
  // admin (mirror of /tts/providers). local = embedded faster-whisper;
11
11
  // openai = cloud Whisper; custom = any OpenAI-compatible server (mlx-audio
12
12
  // on Metal, a Radeon/NVIDIA box on the LAN, a remote endpoint).
13
- app.get("/transcribe/providers", async (_req, res) => {
13
+ api.get("/transcribe/providers", async (_req, res) => {
14
14
  try {
15
15
  const { readConfig } = await import("#core/config/index.js");
16
16
  const { listSttProviders } = await import("#core/voice/transcription.js");
@@ -23,7 +23,7 @@ export function register(app) {
23
23
  // GET /transcribe/hardware — detected machine + the recommended local backend
24
24
  // (mlx on Apple Silicon, faster-whisper cuda on NVIDIA, else CPU). Drives the
25
25
  // "engine adapts itself" UX in the web admin.
26
- app.get("/transcribe/hardware", async (_req, res) => {
26
+ api.get("/transcribe/hardware", async (_req, res) => {
27
27
  try {
28
28
  const { detectHardware, recommendStt } = await import("#core/voice/stt-hardware.js");
29
29
  const hw = detectHardware();
@@ -35,7 +35,7 @@ export function register(app) {
35
35
 
36
36
  // GET /transcribe/models?backend=faster|mlx — model catalog with on-disk
37
37
  // status (downloaded? size) for the model-manager UI.
38
- app.get("/transcribe/models", async (req, res) => {
38
+ api.get("/transcribe/models", async (req, res) => {
39
39
  try {
40
40
  const backend = String(req.query.backend || "faster");
41
41
  const { listSttModels } = await import("#core/voice/stt-models.js");
@@ -48,7 +48,7 @@ export function register(app) {
48
48
  // GET /transcribe/warmup — load the local whisper model (if needed) and reset
49
49
  // its idle watchdog. Callers (e.g. the desktop window) ping this while open so
50
50
  // the first real utterance doesn't pay the cold-load cost.
51
- app.get("/transcribe/warmup", async (_req, res) => {
51
+ api.get("/transcribe/warmup", async (_req, res) => {
52
52
  try {
53
53
  const { warmupWhisper } = await import("../whisper-server.js");
54
54
  res.json(await warmupWhisper());
@@ -57,7 +57,7 @@ export function register(app) {
57
57
  }
58
58
  });
59
59
 
60
- app.post("/transcribe/chunk", async (req, res) => {
60
+ api.post("/transcribe/chunk", async (req, res) => {
61
61
  const chunks = [];
62
62
  req.on("data", (c) => chunks.push(c));
63
63
  req.on("end", async () => {
@@ -11,8 +11,8 @@
11
11
  import { synthesize, listProviders } from "#core/voice/tts.js";
12
12
  import { readConfig } from "#core/config/index.js";
13
13
 
14
- export function register(app) {
15
- app.post("/tts/say", async (req, res) => {
14
+ export function register(api) {
15
+ api.post("/tts/say", async (req, res) => {
16
16
  try {
17
17
  const { text, voice, language, provider, format, style } = req.body || {};
18
18
  if (typeof text !== "string" || !text.trim()) {
@@ -33,7 +33,7 @@ export function register(app) {
33
33
  }
34
34
  });
35
35
 
36
- app.get("/tts/providers", async (_req, res) => {
36
+ api.get("/tts/providers", async (_req, res) => {
37
37
  try {
38
38
  const info = await listProviders(readConfig());
39
39
  res.json(info);
@@ -24,7 +24,10 @@ import {
24
24
  maskValue,
25
25
  } from "#core/vars/index.js";
26
26
 
27
- function normalizeScope(raw, { isBase }) {
27
+ // Var scope vocabulary: project | global. The default depends on whether the
28
+ // request targets the base store. See the note in api/mcps.js: these scope
29
+ // helpers share a shape but not a vocabulary.
30
+ function normalizeVarScope(raw, { isBase }) {
28
31
  if (!raw) return isBase ? "global" : "project";
29
32
  const s = String(raw).toLowerCase();
30
33
  if (s === "project" || s === "global") return s;
@@ -37,8 +40,8 @@ function maskAll(obj) {
37
40
  return out;
38
41
  }
39
42
 
40
- export function register(app, { project, registries }) {
41
- app.get("/projects/:pid/vars", (req, res) => {
43
+ export function register(api, { project, registries }) {
44
+ api.get("/projects/:pid/vars", (req, res) => {
42
45
  const p = project(req, res);
43
46
  if (!p) return;
44
47
  const reveal = req.query?.reveal === "1" || req.query?.reveal === "true";
@@ -54,7 +57,7 @@ export function register(app, { project, registries }) {
54
57
  });
55
58
  });
56
59
 
57
- app.get("/projects/:pid/vars/:name", (req, res) => {
60
+ api.get("/projects/:pid/vars/:name", (req, res) => {
58
61
  const p = project(req, res);
59
62
  if (!p) return;
60
63
  const name = req.params.name;
@@ -80,7 +83,7 @@ export function register(app, { project, registries }) {
80
83
  });
81
84
  });
82
85
 
83
- app.post("/projects/:pid/vars", (req, res) => {
86
+ api.post("/projects/:pid/vars", (req, res) => {
84
87
  const p = project(req, res);
85
88
  if (!p) return;
86
89
  const { name, value } = req.body || {};
@@ -91,7 +94,7 @@ export function register(app, { project, registries }) {
91
94
  return res.status(400).json({ error: "value required" });
92
95
  }
93
96
  const isBase = String(p.id) === "0";
94
- const scope = normalizeScope(req.body?.scope, { isBase });
97
+ const scope = normalizeVarScope(req.body?.scope, { isBase });
95
98
  if (scope === null) {
96
99
  return res
97
100
  .status(400)
@@ -111,11 +114,11 @@ export function register(app, { project, registries }) {
111
114
  res.status(201).json({ ok: true, name, scope });
112
115
  });
113
116
 
114
- app.delete("/projects/:pid/vars/:name", (req, res) => {
117
+ api.delete("/projects/:pid/vars/:name", (req, res) => {
115
118
  const p = project(req, res);
116
119
  if (!p) return;
117
120
  const isBase = String(p.id) === "0";
118
- const scope = normalizeScope(req.query?.scope, { isBase });
121
+ const scope = normalizeVarScope(req.query?.scope, { isBase });
119
122
  if (scope === null) {
120
123
  return res
121
124
  .status(400)
@@ -18,8 +18,10 @@
18
18
  //
19
19
  // Domain logic (channel context, suggestion parsing, audio decoding) lives in
20
20
  // core/. This file is just glue: parse request → call core → format response.
21
+ import { stripReasoning } from "#core/util/thinking.js";
21
22
  import fs from "node:fs";
22
23
  import path from "node:path";
24
+ import { TTS_TMP_DIR } from "#core/config/paths.js";
23
25
  import { readConfig } from "#core/config/index.js";
24
26
  import { synthesize } from "#core/voice/tts.js";
25
27
  import { stripEmotionTags } from "#core/voice/emotions.js";
@@ -31,18 +33,17 @@ import { extractSuggestions } from "#core/agent/suggestions.js";
31
33
  import { appendGlobalMessage } from "#core/stores/messages.js";
32
34
  import { appendErrorTrace, previewText } from "#core/logging.js";
33
35
 
34
- export function register(app, { projects, plugins, registries }) {
36
+ export function register(api, { projects, plugins, registries }) {
35
37
  // GET /voice/tts?path=<abs>
36
38
  //
37
39
  // Streams a TTS audio file back to the caller. Sandboxed to the
38
40
  // ~/.apx/tmp/tts directory so a client can't request arbitrary
39
41
  // filesystem paths through a manifest-leaked reply_audio_path.
40
- app.get("/voice/tts", async (req, res) => {
42
+ api.get("/voice/tts", async (req, res) => {
41
43
  const rawPath = String(req.query.path || "");
42
44
  if (!rawPath) return res.status(400).json({ error: "path required" });
43
45
  try {
44
- const os = await import("node:os");
45
- const ttsRoot = path.resolve(os.homedir(), ".apx", "tmp", "tts");
46
+ const ttsRoot = path.resolve(TTS_TMP_DIR);
46
47
  const resolved = path.resolve(rawPath);
47
48
  if (!resolved.startsWith(ttsRoot + path.sep)) {
48
49
  return res.status(403).json({ error: "path outside tts dir" });
@@ -65,7 +66,7 @@ export function register(app, { projects, plugins, registries }) {
65
66
  }
66
67
  });
67
68
 
68
- app.post("/voice/turn", async (req, res) => {
69
+ api.post("/voice/turn", async (req, res) => {
69
70
  const body = req.body || {};
70
71
  const cfg = readConfig();
71
72
  let userText = (body.text || "").trim();
@@ -103,7 +104,7 @@ export function register(app, { projects, plugins, registries }) {
103
104
  : [];
104
105
  const channel = body.channel || "voice";
105
106
  let suggestions = [];
106
- let toolsUsed = [];
107
+ const toolsUsed = [];
107
108
  // Attribution for the persisted turn (see the appendGlobalMessage below).
108
109
  let replyModel = null;
109
110
  let replyUsage = null;
@@ -128,7 +129,8 @@ export function register(app, { projects, plugins, registries }) {
128
129
  systemSuffix: channelCtx.systemSuffix,
129
130
  previousMessages,
130
131
  });
131
- const raw = (result?.text || "").trim();
132
+ // Spoken back to the user — never narrate raw planning.
133
+ const raw = stripReasoning(result?.text || "").answer.trim();
132
134
  replyModel = result?.model || null;
133
135
  replyUsage = result?.usage || null;
134
136
  replyName = result?.name || null;
@@ -2,9 +2,10 @@
2
2
  //
3
3
  // The web bundle is built into src/interfaces/web/dist. When that folder
4
4
  // exists, this module mounts it at `/` so users can open
5
- // http://127.0.0.1:7430 and get the UI. SPA fallback: anything that isn't an
6
- // /api/* / /projects/* / etc. route falls through to index.html so React
7
- // Router can resolve client-side paths like /p/1/tasks.
5
+ // http://127.0.0.1:7430 and get the UI. The root namespace belongs to the
6
+ // panel alone every data route lives under /api (see ./prefix.js) so the
7
+ // SPA fallback only has to step aside for that single prefix instead of a
8
+ // hand-maintained list that drifts away from the route registry.
8
9
  //
9
10
  // During development the user runs `vite dev` on :7431 with proxy → :7430,
10
11
  // so this mount is a no-op (dist/ doesn't exist yet).
@@ -12,29 +13,17 @@ import fs from "node:fs";
12
13
  import path from "node:path";
13
14
  import { fileURLToPath } from "node:url";
14
15
 
16
+ import { isApiPath } from "./prefix.js";
17
+
18
+ // Re-exported so the SPA/API seam keeps a single import site for callers that
19
+ // reason about "which side of the seam is this path on?" (tests, auth).
20
+ export { isApiPath };
21
+
15
22
  const __filename = fileURLToPath(import.meta.url);
16
23
  const __dirname = path.dirname(__filename);
17
24
  // host/daemon/api/web.js → ../../.. = src/ then interfaces/web/dist
18
25
  const WEB_DIST = path.resolve(__dirname, "..", "..", "..", "interfaces", "web", "dist");
19
26
 
20
- // Paths the panel is NOT allowed to swallow with its SPA fallback. These are
21
- // the real API surfaces; we keep the list flat so it doesn't drift away from
22
- // the actual route registry.
23
- const API_PREFIXES = [
24
- "/health", "/admin", "/projects", "/telegram", "/engines", "/runtimes",
25
- "/messages", "/sessions", "/tools", "/mcp", "/voice", "/tts", "/desktop", "/overlay",
26
- "/transcribe", "/run", "/files", "/memory", "/env", "/pair", "/deck",
27
- "/super-agent", "/identity", "/skills", "/profiles", "/inbox",
28
- "/tasks", "/agents", "/plugins", "/previews", "/embeddings",
29
- ];
30
-
31
- export function isApiPath(p) {
32
- for (const prefix of API_PREFIXES) {
33
- if (p === prefix || p.startsWith(prefix + "/")) return true;
34
- }
35
- return false;
36
- }
37
-
38
27
  // Client-side routes the SPA actually knows how to render. Must mirror the
39
28
  // <Routes> registry in src/interfaces/web/src/App.tsx. Anything else still
40
29
  // gets the SPA shell (so React Router shows the styled 404 page) but with an
@@ -51,15 +40,18 @@ export function isKnownSpaRoute(p) {
51
40
  return SPA_ROUTES.some((re) => re.test(p));
52
41
  }
53
42
 
54
- export function register(app, { express, token }) {
55
- // /admin/web-token: localhost-only endpoint that returns the daemon token
56
- // so the same-origin admin panel can authenticate every subsequent call.
57
- // Refuses if the request didn't come from loopback. Also refuses if the
58
- // request was tunneled in via Cloudflare/ngrok/etc. those connect from
59
- // a local agent so the socket IP IS loopback, but tunnel-specific headers
60
- // give them away. When tunneled, the SPA must instead receive the token
61
- // via URL fragment (#token=…) that the operator shares out-of-band.
62
- app.get("/admin/web-token", (req, res) => {
43
+ // /api/admin/web-token: localhost-only endpoint that returns the daemon token
44
+ // so the same-origin admin panel can authenticate every subsequent call.
45
+ // Refuses if the request didn't come from loopback. Also refuses if the
46
+ // request was tunneled in via Cloudflare/ngrok/etc. those connect from
47
+ // a local agent so the socket IP IS loopback, but tunnel-specific headers
48
+ // give them away. When tunneled, the SPA must instead receive the token
49
+ // via URL fragment (#token=…) that the operator shares out-of-band.
50
+ //
51
+ // Registered on the API router (not the static mount below) so the panel's
52
+ // bootstrap call is a normal /api/* request like every other.
53
+ export function registerWebToken(api, { token }) {
54
+ api.get("/admin/web-token", (req, res) => {
63
55
  const ra = req.ip || req.socket?.remoteAddress || "";
64
56
  const isLocal =
65
57
  ra === "127.0.0.1" ||
@@ -86,7 +78,11 @@ export function register(app, { express, token }) {
86
78
  }
87
79
  res.json({ token });
88
80
  });
81
+ }
89
82
 
83
+ // Static admin panel at the root. Mounted after `app.use("/api", api)` in
84
+ // api.js, so a stray /api/* request can never reach the SPA fallback.
85
+ export function register(app, { express }) {
90
86
  if (!fs.existsSync(WEB_DIST)) {
91
87
  // No build present. Expose a hint endpoint so users hitting the daemon
92
88
  // root know what to do.
@@ -112,8 +108,8 @@ export function register(app, { express, token }) {
112
108
  return;
113
109
  }
114
110
 
115
- // Serve static assets. NB: we mount AFTER the API routes are registered
116
- // in api.js so /projects, /admin/*, etc. always win over the catch-all.
111
+ // Serve static assets. NB: we mount AFTER the /api router is mounted in
112
+ // api.js so data routes always win over this catch-all.
117
113
  app.use(
118
114
  express.static(WEB_DIST, {
119
115
  index: false,