@agentprojectcontext/apx 1.77.3 → 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 (293) 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 +6 -6
  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-BptlbKjU.js → index-CBR_-QyA.js} +52 -52
  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 +3 -3
  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-BptlbKjU.js.map +0 -1
  283. package/src/skills/apc-context/SKILL.md +0 -159
  284. /package/src/{host/daemon → core}/runtimes/_spawn.js +0 -0
  285. /package/src/{host/daemon → core}/runtimes/aider.js +0 -0
  286. /package/src/{host/daemon → core}/runtimes/antigravity.js +0 -0
  287. /package/src/{host/daemon → core}/runtimes/claude-code.js +0 -0
  288. /package/src/{host/daemon → core}/runtimes/codex.js +0 -0
  289. /package/src/{host/daemon → core}/runtimes/cursor-agent.js +0 -0
  290. /package/src/{host/daemon → core}/runtimes/gemini-cli.js +0 -0
  291. /package/src/{host/daemon → core}/runtimes/index.js +0 -0
  292. /package/src/{host/daemon → core}/runtimes/opencode.js +0 -0
  293. /package/src/{host/daemon → core}/runtimes/qwen-code.js +0 -0
@@ -25,8 +25,12 @@ import {
25
25
  getPluginService,
26
26
  reconcilePluginMcp,
27
27
  } from "#core/integrations/index.js";
28
+ import { asyncRoute } from "./shared.js";
28
29
 
29
- function normalizeScope(raw) {
30
+ // Integration scope vocabulary: project | global, accepting "default" for
31
+ // global and both "shared" and "runtime" for project. See the note in
32
+ // api/mcps.js — do not merge these three helpers.
33
+ function normalizeIntegrationScope(raw) {
30
34
  if (!raw) return "project";
31
35
  const s = String(raw).toLowerCase();
32
36
  if (s === "global" || s === "default") return "global";
@@ -44,7 +48,7 @@ function storagePathForScope(scope, p, projects) {
44
48
  return p.storagePath || null;
45
49
  }
46
50
 
47
- export function register(app, { projects, project, registries }) {
51
+ export function register(api, { projects, project, registries }) {
48
52
  // Keep a plugin's optional auto-registered MCP server (svc.mcpServer hook) in
49
53
  // lockstep with its stored state. Best-effort: a failure here must not break
50
54
  // the configure/validate/deactivate/delete response. `storagePath` is the one
@@ -62,10 +66,10 @@ export function register(app, { projects, project, registries }) {
62
66
  }
63
67
 
64
68
  // List stored integrations in the chosen scope (secrets redacted).
65
- app.get("/projects/:pid/integrations", (req, res) => {
69
+ api.get("/projects/:pid/integrations", (req, res) => {
66
70
  const p = project(req, res);
67
71
  if (!p) return;
68
- const scope = normalizeScope(req.query?.scope);
72
+ const scope = normalizeIntegrationScope(req.query?.scope);
69
73
  if (scope === null) return res.status(400).json({ error: `unknown scope "${req.query?.scope}"` });
70
74
  const storagePath = storagePathForScope(scope, p, projects);
71
75
  if (!storagePath) return res.status(400).json({ error: "project has no storage path" });
@@ -75,7 +79,7 @@ export function register(app, { projects, project, registries }) {
75
79
 
76
80
  // The full plugin roster with each plugin's resolved status for this project
77
81
  // (project record wins over the default/global one).
78
- app.get("/projects/:pid/integrations/catalog", (req, res) => {
82
+ api.get("/projects/:pid/integrations/catalog", (req, res) => {
79
83
  const p = project(req, res);
80
84
  if (!p) return;
81
85
  const catalog = listCatalog().map((entry) => {
@@ -97,12 +101,12 @@ export function register(app, { projects, project, registries }) {
97
101
  });
98
102
 
99
103
  // Status for a single plugin in the chosen scope.
100
- app.get("/projects/:pid/integrations/:slug", (req, res) => {
104
+ api.get("/projects/:pid/integrations/:slug", (req, res) => {
101
105
  const p = project(req, res);
102
106
  if (!p) return;
103
107
  const svc = getPluginService(req.params.slug);
104
108
  if (!svc) return res.status(404).json({ error: `unknown plugin "${req.params.slug}"` });
105
- const scope = normalizeScope(req.query?.scope);
109
+ const scope = normalizeIntegrationScope(req.query?.scope);
106
110
  if (scope === null) return res.status(400).json({ error: `unknown scope "${req.query?.scope}"` });
107
111
  const storagePath = storagePathForScope(scope, p, projects);
108
112
  if (!storagePath) return res.status(400).json({ error: "project has no storage path" });
@@ -111,12 +115,12 @@ export function register(app, { projects, project, registries }) {
111
115
  });
112
116
 
113
117
  // Save credentials / config. Creates the record if missing.
114
- app.post("/projects/:pid/integrations/:slug/configure", (req, res) => {
118
+ api.post("/projects/:pid/integrations/:slug/configure", (req, res) => {
115
119
  const p = project(req, res);
116
120
  if (!p) return;
117
121
  const svc = getPluginService(req.params.slug);
118
122
  if (!svc) return res.status(404).json({ error: `unknown plugin "${req.params.slug}"` });
119
- const scope = normalizeScope(req.query?.scope);
123
+ const scope = normalizeIntegrationScope(req.query?.scope);
120
124
  if (scope === null) return res.status(400).json({ error: `unknown scope "${req.query?.scope}"` });
121
125
  const storagePath = storagePathForScope(scope, p, projects);
122
126
  if (!storagePath) return res.status(400).json({ error: "project has no storage path" });
@@ -132,12 +136,12 @@ export function register(app, { projects, project, registries }) {
132
136
  });
133
137
 
134
138
  // Verify the stored credentials against the provider, then persist the result.
135
- app.post("/projects/:pid/integrations/:slug/validate", async (req, res) => {
139
+ api.post("/projects/:pid/integrations/:slug/validate", asyncRoute(async (req, res) => {
136
140
  const p = project(req, res);
137
141
  if (!p) return;
138
142
  const svc = getPluginService(req.params.slug);
139
143
  if (!svc) return res.status(404).json({ error: `unknown plugin "${req.params.slug}"` });
140
- const scope = normalizeScope(req.query?.scope);
144
+ const scope = normalizeIntegrationScope(req.query?.scope);
141
145
  if (scope === null) return res.status(400).json({ error: `unknown scope "${req.query?.scope}"` });
142
146
  const storagePath = storagePathForScope(scope, p, projects);
143
147
  if (!storagePath) return res.status(400).json({ error: "project has no storage path" });
@@ -153,15 +157,15 @@ export function register(app, { projects, project, registries }) {
153
157
  } catch (e) {
154
158
  res.status(400).json({ error: e.message });
155
159
  }
156
- });
160
+ }));
157
161
 
158
162
  // Disable a plugin without deleting its stored credentials.
159
- app.post("/projects/:pid/integrations/:slug/deactivate", (req, res) => {
163
+ api.post("/projects/:pid/integrations/:slug/deactivate", (req, res) => {
160
164
  const p = project(req, res);
161
165
  if (!p) return;
162
166
  const svc = getPluginService(req.params.slug);
163
167
  if (!svc) return res.status(404).json({ error: `unknown plugin "${req.params.slug}"` });
164
- const scope = normalizeScope(req.query?.scope);
168
+ const scope = normalizeIntegrationScope(req.query?.scope);
165
169
  if (scope === null) return res.status(400).json({ error: `unknown scope "${req.query?.scope}"` });
166
170
  const storagePath = storagePathForScope(scope, p, projects);
167
171
  if (!storagePath) return res.status(400).json({ error: "project has no storage path" });
@@ -174,7 +178,7 @@ export function register(app, { projects, project, registries }) {
174
178
  });
175
179
 
176
180
  // Plugin-specific read action (e.g. Asana → list workspaces for the token).
177
- app.post("/projects/:pid/integrations/:slug/action/:action", async (req, res) => {
181
+ api.post("/projects/:pid/integrations/:slug/action/:action", asyncRoute(async (req, res) => {
178
182
  const p = project(req, res);
179
183
  if (!p) return;
180
184
  const svc = getPluginService(req.params.slug);
@@ -183,7 +187,7 @@ export function register(app, { projects, project, registries }) {
183
187
  if (typeof fn !== "function") {
184
188
  return res.status(404).json({ error: `unknown action "${req.params.action}"` });
185
189
  }
186
- const scope = normalizeScope(req.query?.scope);
190
+ const scope = normalizeIntegrationScope(req.query?.scope);
187
191
  if (scope === null) return res.status(400).json({ error: `unknown scope "${req.query?.scope}"` });
188
192
  const storagePath = storagePathForScope(scope, p, projects);
189
193
  if (!storagePath) return res.status(400).json({ error: "project has no storage path" });
@@ -197,13 +201,13 @@ export function register(app, { projects, project, registries }) {
197
201
  } catch (e) {
198
202
  res.status(400).json({ error: e.message });
199
203
  }
200
- });
204
+ }));
201
205
 
202
206
  // Remove a stored integration entirely.
203
- app.delete("/projects/:pid/integrations/:slug", (req, res) => {
207
+ api.delete("/projects/:pid/integrations/:slug", (req, res) => {
204
208
  const p = project(req, res);
205
209
  if (!p) return;
206
- const scope = normalizeScope(req.query?.scope);
210
+ const scope = normalizeIntegrationScope(req.query?.scope);
207
211
  if (scope === null) return res.status(400).json({ error: `unknown scope "${req.query?.scope}"` });
208
212
  const storagePath = storagePathForScope(scope, p, projects);
209
213
  if (!storagePath) return res.status(400).json({ error: "project has no storage path" });
@@ -45,7 +45,11 @@ function writeScope(scope, projectEntry, json) {
45
45
  return writeApfMcps(projectEntry.path, json);
46
46
  }
47
47
 
48
- function normalizeScope(raw) {
48
+ // MCP scope vocabulary: shared | runtime | global (with "apc" as a friendly
49
+ // alias for shared). NOT interchangeable with the var or integration scopes —
50
+ // each subsystem has its own set and its own default, and collapsing them into
51
+ // one helper would silently reroute writes to the wrong store.
52
+ function normalizeMcpScope(raw) {
49
53
  if (!raw) return "shared";
50
54
  const s = String(raw).toLowerCase();
51
55
  if (s === "apc") return "shared"; // friendly alias
@@ -55,17 +59,17 @@ function normalizeScope(raw) {
55
59
  return s;
56
60
  }
57
61
 
58
- export function register(app, { projects, registries, project }) {
59
- app.get("/projects/:pid/mcps", (req, res) => {
62
+ export function register(api, { projects, registries, project }) {
63
+ api.get("/projects/:pid/mcps", (req, res) => {
60
64
  const p = project(req, res);
61
65
  if (!p) return;
62
66
  res.json(registries.for(p).list());
63
67
  });
64
68
 
65
- app.post("/projects/:pid/mcps", (req, res) => {
69
+ api.post("/projects/:pid/mcps", (req, res) => {
66
70
  const p = project(req, res);
67
71
  if (!p) return;
68
- const scope = normalizeScope(req.query?.scope);
72
+ const scope = normalizeMcpScope(req.query?.scope);
69
73
  if (scope === null) {
70
74
  return res.status(400).json({ error: `unknown scope "${req.query?.scope}" (use shared|runtime|global)` });
71
75
  }
@@ -102,10 +106,10 @@ export function register(app, { projects, registries, project }) {
102
106
  res.status(201).json(entry);
103
107
  });
104
108
 
105
- app.delete("/projects/:pid/mcps/:name", (req, res) => {
109
+ api.delete("/projects/:pid/mcps/:name", (req, res) => {
106
110
  const p = project(req, res);
107
111
  if (!p) return;
108
- const scope = normalizeScope(req.query?.scope);
112
+ const scope = normalizeMcpScope(req.query?.scope);
109
113
  if (scope === null) {
110
114
  return res.status(400).json({ error: `unknown scope "${req.query?.scope}" (use shared|runtime|global)` });
111
115
  }
@@ -155,7 +159,7 @@ export function register(app, { projects, registries, project }) {
155
159
  res.status(204).end();
156
160
  });
157
161
 
158
- app.get("/projects/:pid/mcps/check", (req, res) => {
162
+ api.get("/projects/:pid/mcps/check", (req, res) => {
159
163
  const p = project(req, res);
160
164
  if (!p) return;
161
165
  const reg = registries.for(p);
@@ -194,7 +198,7 @@ export function register(app, { projects, registries, project }) {
194
198
  // Full tool catalog — tools/list with input schemas, all pages merged.
195
199
  // This is what `apx mcp tools` renders; /test below stays as the
196
200
  // lightweight smoke check for the web UI card.
197
- app.get("/projects/:pid/mcps/:name/tools", async (req, res) => {
201
+ api.get("/projects/:pid/mcps/:name/tools", async (req, res) => {
198
202
  const p = project(req, res);
199
203
  if (!p) return;
200
204
  try {
@@ -205,7 +209,7 @@ export function register(app, { projects, registries, project }) {
205
209
  }
206
210
  });
207
211
 
208
- app.post("/projects/:pid/mcps/:name/call", async (req, res) => {
212
+ api.post("/projects/:pid/mcps/:name/call", async (req, res) => {
209
213
  const p = project(req, res);
210
214
  if (!p) return;
211
215
  const { tool, params } = req.body || {};
@@ -221,7 +225,7 @@ export function register(app, { projects, registries, project }) {
221
225
  // Smoke test — calls tools/list and reports either the tool catalog or a
222
226
  // clean error message. Used by the "Test" button in the MCP card so the
223
227
  // user can sanity-check a freshly-saved MCP without firing a real tool.
224
- app.post("/projects/:pid/mcps/:name/test", async (req, res) => {
228
+ api.post("/projects/:pid/mcps/:name/test", async (req, res) => {
225
229
  const p = project(req, res);
226
230
  if (!p) return;
227
231
  try {
@@ -242,7 +246,7 @@ export function register(app, { projects, registries, project }) {
242
246
 
243
247
  // In-memory log buffer for one MCP — stderr tail (stdio) or fetch summary
244
248
  // (http) plus a ring of recent events.
245
- app.get("/projects/:pid/mcps/:name/logs", (req, res) => {
249
+ api.get("/projects/:pid/mcps/:name/logs", (req, res) => {
246
250
  const p = project(req, res);
247
251
  if (!p) return;
248
252
  const logs = registries.for(p).getLogs(req.params.name);
@@ -11,8 +11,8 @@ import {
11
11
  searchProjectMessages,
12
12
  } from "#core/stores/messages.js";
13
13
 
14
- export function register(app, { project }) {
15
- app.get("/projects/:pid/messages", (req, res) => {
14
+ export function register(api, { project }) {
15
+ api.get("/projects/:pid/messages", (req, res) => {
16
16
  const p = project(req, res);
17
17
  if (!p) return;
18
18
  const { agent, channel, since, limit = "100" } = req.query;
@@ -25,7 +25,7 @@ export function register(app, { project }) {
25
25
  res.json(rows);
26
26
  });
27
27
 
28
- app.post("/projects/:pid/messages", (req, res) => {
28
+ api.post("/projects/:pid/messages", (req, res) => {
29
29
  const p = project(req, res);
30
30
  if (!p) return;
31
31
  const {
@@ -55,7 +55,7 @@ export function register(app, { project }) {
55
55
  res.status(201).json({ ok: true, ts: r.ts });
56
56
  });
57
57
 
58
- app.get("/projects/:pid/messages/search", (req, res) => {
58
+ api.get("/projects/:pid/messages/search", (req, res) => {
59
59
  const p = project(req, res);
60
60
  if (!p) return;
61
61
  const { q, limit = "50" } = req.query;
@@ -70,7 +70,7 @@ export function register(app, { project }) {
70
70
  });
71
71
 
72
72
  // Cross-project channels (telegram, direct, …)
73
- app.get("/messages/global", (req, res) => {
73
+ api.get("/messages/global", (req, res) => {
74
74
  const { channel, limit = "100", since } = req.query;
75
75
  const lim = Math.min(parseInt(limit, 10) || 100, 1000);
76
76
  const rows = readGlobalMessages({
@@ -19,14 +19,14 @@ import {
19
19
  removeRole,
20
20
  } from "#core/stores/organization.js";
21
21
 
22
- export function register(app, { project }) {
23
- app.get("/projects/:pid/organization", (req, res) => {
22
+ export function register(api, { project }) {
23
+ api.get("/projects/:pid/organization", (req, res) => {
24
24
  const p = project(req, res);
25
25
  if (!p) return;
26
26
  res.json(readOrganization(p.path));
27
27
  });
28
28
 
29
- app.post("/projects/:pid/organization/areas", (req, res) => {
29
+ api.post("/projects/:pid/organization/areas", (req, res) => {
30
30
  const p = project(req, res);
31
31
  if (!p) return;
32
32
  try {
@@ -36,7 +36,7 @@ export function register(app, { project }) {
36
36
  }
37
37
  });
38
38
 
39
- app.patch("/projects/:pid/organization/areas/:slug", (req, res) => {
39
+ api.patch("/projects/:pid/organization/areas/:slug", (req, res) => {
40
40
  const p = project(req, res);
41
41
  if (!p) return;
42
42
  try {
@@ -48,7 +48,7 @@ export function register(app, { project }) {
48
48
  }
49
49
  });
50
50
 
51
- app.delete("/projects/:pid/organization/areas/:slug", (req, res) => {
51
+ api.delete("/projects/:pid/organization/areas/:slug", (req, res) => {
52
52
  const p = project(req, res);
53
53
  if (!p) return;
54
54
  if (!removeArea(p.path, req.params.slug))
@@ -56,7 +56,7 @@ export function register(app, { project }) {
56
56
  res.json({ ok: true });
57
57
  });
58
58
 
59
- app.post("/projects/:pid/organization/roles", (req, res) => {
59
+ api.post("/projects/:pid/organization/roles", (req, res) => {
60
60
  const p = project(req, res);
61
61
  if (!p) return;
62
62
  try {
@@ -66,7 +66,7 @@ export function register(app, { project }) {
66
66
  }
67
67
  });
68
68
 
69
- app.patch("/projects/:pid/organization/roles/:slug", (req, res) => {
69
+ api.patch("/projects/:pid/organization/roles/:slug", (req, res) => {
70
70
  const p = project(req, res);
71
71
  if (!p) return;
72
72
  try {
@@ -78,7 +78,7 @@ export function register(app, { project }) {
78
78
  }
79
79
  });
80
80
 
81
- app.delete("/projects/:pid/organization/roles/:slug", (req, res) => {
81
+ api.delete("/projects/:pid/organization/roles/:slug", (req, res) => {
82
82
  const p = project(req, res);
83
83
  if (!p) return;
84
84
  if (!removeRole(p.path, req.params.slug))
@@ -64,7 +64,7 @@ function purgeExpired(now = Date.now()) {
64
64
  }
65
65
  }
66
66
 
67
- export function register(app, ctx) {
67
+ export function register(api, ctx) {
68
68
  const { tokenStore, config } = ctx;
69
69
  if (!tokenStore) {
70
70
  // Pairing requires the multi-token store; if running with legacy
@@ -75,7 +75,7 @@ export function register(app, ctx) {
75
75
 
76
76
  // ── POST /pair/init ───────────────────────────────────────────────
77
77
  // Localhost-only. Returns { pairing_id, expires_at }. No body needed.
78
- app.post("/pair/init", (req, res) => {
78
+ api.post("/pair/init", (req, res) => {
79
79
  if (!isLocalhost(req)) {
80
80
  return res.status(403).json({ error: "pair/init: localhost only" });
81
81
  }
@@ -107,7 +107,7 @@ export function register(app, ctx) {
107
107
  // ── POST /pair/confirm ────────────────────────────────────────────
108
108
  // App side. Body: { pairing_id, label?, fingerprint? }. Returns the
109
109
  // new client token. Nonce is one-shot.
110
- app.post("/pair/confirm", (req, res) => {
110
+ api.post("/pair/confirm", (req, res) => {
111
111
  purgeExpired();
112
112
  const { pairing_id, label, fingerprint, kind } = req.body || {};
113
113
  if (!pairing_id || typeof pairing_id !== "string") {
@@ -149,7 +149,7 @@ export function register(app, ctx) {
149
149
  // ── GET /pair/status/:pairing_id ──────────────────────────────────
150
150
  // For the CLI to poll while showing the QR. Returns pending|confirmed
151
151
  // |expired. Never leaks the token.
152
- app.get("/pair/status/:pairing_id", (req, res) => {
152
+ api.get("/pair/status/:pairing_id", (req, res) => {
153
153
  purgeExpired();
154
154
  const s = sessions.get(req.params.pairing_id);
155
155
  if (!s) return res.json({ status: "unknown" });
@@ -171,13 +171,13 @@ export function register(app, ctx) {
171
171
  // ── GET /pair/list ────────────────────────────────────────────────
172
172
  // Authenticated (auth middleware exempts /pair/*, so we re-check by
173
173
  // hand here — only the master or an existing client can list peers).
174
- app.get("/pair/list", (req, res) => {
174
+ api.get("/pair/list", (req, res) => {
175
175
  if (!checkBearer(req, tokenStore)) return res.status(401).json({ error: "unauthorized" });
176
176
  res.json({ clients: tokenStore.list() });
177
177
  });
178
178
 
179
179
  // ── DELETE /pair/revoke/:id ───────────────────────────────────────
180
- app.delete("/pair/revoke/:id", (req, res) => {
180
+ api.delete("/pair/revoke/:id", (req, res) => {
181
181
  if (!checkBearer(req, tokenStore)) return res.status(401).json({ error: "unauthorized" });
182
182
  const ok = tokenStore.revoke(req.params.id);
183
183
  if (!ok) return res.status(404).json({ error: "no such client" });
@@ -1,13 +1,13 @@
1
1
  // Daemon plugin status surface.
2
2
  // GET /plugins status of every loaded plugin
3
3
  // GET /plugins/:id/status single plugin
4
- export function register(app, { plugins }) {
5
- app.get("/plugins", (_req, res) => {
4
+ export function register(api, { plugins }) {
5
+ api.get("/plugins", (_req, res) => {
6
6
  if (!plugins) return res.json({});
7
7
  res.json(plugins.status());
8
8
  });
9
9
 
10
- app.get("/plugins/:id/status", (req, res) => {
10
+ api.get("/plugins/:id/status", (req, res) => {
11
11
  if (!plugins) return res.status(404).end();
12
12
  const inst = plugins.get(req.params.id);
13
13
  if (!inst)
@@ -0,0 +1,20 @@
1
+ // The single mount point for every daemon data route.
2
+ //
3
+ // Before this existed, routes lived at the root and three separate hand-written
4
+ // prefix lists had to agree on which of them were "API" (the SPA fallback in
5
+ // web.js, the auth allowlist in shared.js, and the vite dev proxy). They drifted.
6
+ // Now the answer is structural: a path is an API path iff it starts with /api.
7
+ //
8
+ // Kept in its own module so both the daemon and the route modules can import it
9
+ // without pulling in express or any route registration.
10
+ export const API_PREFIX = "/api";
11
+
12
+ /** True for /api and anything below it. */
13
+ export function isApiPath(p) {
14
+ return p === API_PREFIX || p.startsWith(API_PREFIX + "/");
15
+ }
16
+
17
+ /** Prefix a root-relative route path with the API mount point. */
18
+ export function apiPath(p) {
19
+ return `${API_PREFIX}${p.startsWith("/") ? p : `/${p}`}`;
20
+ }
@@ -82,8 +82,8 @@ function detail(id, { preview = true } = {}) {
82
82
  };
83
83
  }
84
84
 
85
- export function register(app) {
86
- app.get("/profiles", (_req, res) => {
85
+ export function register(api) {
86
+ api.get("/profiles", (_req, res) => {
87
87
  try {
88
88
  const cfg = readConfig();
89
89
  res.json({
@@ -96,7 +96,7 @@ export function register(app) {
96
96
  });
97
97
 
98
98
  // Registered before /profiles/:id so "doctor" isn't swallowed as an id.
99
- app.get("/profiles/doctor", (req, res) => {
99
+ api.get("/profiles/doctor", (req, res) => {
100
100
  try {
101
101
  res.json(profileDoctor(req.query?.id || null));
102
102
  } catch (e) {
@@ -104,7 +104,7 @@ export function register(app) {
104
104
  }
105
105
  });
106
106
 
107
- app.get("/profiles/:id", (req, res) => {
107
+ api.get("/profiles/:id", (req, res) => {
108
108
  try {
109
109
  const out = detail(req.params.id, { preview: req.query?.preview !== "0" });
110
110
  if (!out) return res.status(404).json({ error: `profile "${req.params.id}" not found` });
@@ -114,7 +114,7 @@ export function register(app) {
114
114
  }
115
115
  });
116
116
 
117
- app.post("/profiles/install", (req, res) => {
117
+ api.post("/profiles/install", (req, res) => {
118
118
  try {
119
119
  const { source, force } = req.body || {};
120
120
  if (!source) return res.status(400).json({ error: "body needs { source }" });
@@ -131,7 +131,7 @@ export function register(app) {
131
131
  }
132
132
  });
133
133
 
134
- app.post("/profiles/use", (req, res) => {
134
+ api.post("/profiles/use", (req, res) => {
135
135
  try {
136
136
  const { id, force } = req.body || {};
137
137
  if (!id) return res.status(400).json({ error: "body needs { id }" });
@@ -148,7 +148,7 @@ export function register(app) {
148
148
  }
149
149
  });
150
150
 
151
- app.post("/profiles/off", (_req, res) => {
151
+ api.post("/profiles/off", (_req, res) => {
152
152
  try {
153
153
  res.json({ ok: true, ...offProfile() });
154
154
  } catch (e) {
@@ -156,7 +156,7 @@ export function register(app) {
156
156
  }
157
157
  });
158
158
 
159
- app.patch("/profiles/config", (req, res) => {
159
+ api.patch("/profiles/config", (req, res) => {
160
160
  try {
161
161
  const { values, id } = req.body || {};
162
162
  if (!values || typeof values !== "object") {
@@ -169,7 +169,7 @@ export function register(app) {
169
169
  }
170
170
  });
171
171
 
172
- app.delete("/profiles/:id", (req, res) => {
172
+ api.delete("/profiles/:id", (req, res) => {
173
173
  try {
174
174
  res.json({ ok: true, ...uninstallProfile(req.params.id) });
175
175
  } catch (e) {
@@ -3,10 +3,10 @@
3
3
  // POST /projects register a project by path
4
4
  // DELETE /projects/:id unregister
5
5
  // POST /projects/:id/rebuild force a context rebuild from disk
6
- export function register(app, { projects, registries, addProjectGlobally }) {
7
- app.get("/projects", (_req, res) => res.json(projects.list()));
6
+ export function register(api, { projects, registries, addProjectGlobally }) {
7
+ api.get("/projects", (_req, res) => res.json(projects.list()));
8
8
 
9
- app.post("/projects", (req, res) => {
9
+ api.post("/projects", (req, res) => {
10
10
  const { path: p } = req.body || {};
11
11
  if (!p) return res.status(400).json({ error: "path required" });
12
12
  try {
@@ -19,12 +19,12 @@ export function register(app, { projects, registries, addProjectGlobally }) {
19
19
  }
20
20
  });
21
21
 
22
- app.delete("/projects/:id", (req, res) => {
22
+ api.delete("/projects/:id", (req, res) => {
23
23
  const ok = projects.unregister(req.params.id);
24
24
  res.status(ok ? 204 : 404).end();
25
25
  });
26
26
 
27
- app.post("/projects/:id/rebuild", (req, res) => {
27
+ api.post("/projects/:id/rebuild", (req, res) => {
28
28
  try {
29
29
  const result = projects.rebuild(req.params.id);
30
30
  res.json({ ok: true, ...result });
@@ -17,14 +17,14 @@ import {
17
17
  runRoutineNow,
18
18
  } from "#core/routines/index.js";
19
19
 
20
- export function register(app, { projects, registries, plugins, project, config }) {
21
- app.get("/projects/:pid/routines", (req, res) => {
20
+ export function register(api, { projects, registries, plugins, project, config }) {
21
+ api.get("/projects/:pid/routines", (req, res) => {
22
22
  const p = project(req, res);
23
23
  if (!p) return;
24
24
  res.json(listRoutines(p.storagePath));
25
25
  });
26
26
 
27
- app.get("/projects/:pid/routines/:name", (req, res) => {
27
+ api.get("/projects/:pid/routines/:name", (req, res) => {
28
28
  const p = project(req, res);
29
29
  if (!p) return;
30
30
  const r = getRoutine(p.storagePath, req.params.name);
@@ -32,7 +32,7 @@ export function register(app, { projects, registries, plugins, project, config }
32
32
  res.json(r);
33
33
  });
34
34
 
35
- app.post("/projects/:pid/routines", (req, res) => {
35
+ api.post("/projects/:pid/routines", (req, res) => {
36
36
  const p = project(req, res);
37
37
  if (!p) return;
38
38
  try {
@@ -45,28 +45,28 @@ export function register(app, { projects, registries, plugins, project, config }
45
45
  }
46
46
  });
47
47
 
48
- app.delete("/projects/:pid/routines/:name", (req, res) => {
48
+ api.delete("/projects/:pid/routines/:name", (req, res) => {
49
49
  const p = project(req, res);
50
50
  if (!p) return;
51
51
  const ok = deleteRoutine(p.storagePath, req.params.name);
52
52
  res.status(ok ? 204 : 404).end();
53
53
  });
54
54
 
55
- app.post("/projects/:pid/routines/:name/enable", (req, res) => {
55
+ api.post("/projects/:pid/routines/:name/enable", (req, res) => {
56
56
  const p = project(req, res);
57
57
  if (!p) return;
58
58
  setRoutineEnabled(p.storagePath, req.params.name, true);
59
59
  res.json({ ok: true });
60
60
  });
61
61
 
62
- app.post("/projects/:pid/routines/:name/disable", (req, res) => {
62
+ api.post("/projects/:pid/routines/:name/disable", (req, res) => {
63
63
  const p = project(req, res);
64
64
  if (!p) return;
65
65
  setRoutineEnabled(p.storagePath, req.params.name, false);
66
66
  res.json({ ok: true });
67
67
  });
68
68
 
69
- app.post("/projects/:pid/routines/:name/run", async (req, res) => {
69
+ api.post("/projects/:pid/routines/:name/run", async (req, res) => {
70
70
  const p = project(req, res);
71
71
  if (!p) return;
72
72
  const r = getRoutine(p.storagePath, req.params.name);
@@ -4,8 +4,8 @@
4
4
  import path from "node:path";
5
5
  import { execFile } from "node:child_process";
6
6
 
7
- export function register(app, { projects }) {
8
- app.post("/run", (req, res) => {
7
+ export function register(api, { projects }) {
8
+ api.post("/run", (req, res) => {
9
9
  const {
10
10
  cmd,
11
11
  cwd: cwdOverride,
@@ -12,7 +12,7 @@ import { apcProjectFile, apcAgentsDir } from "#core/apc/paths.js";
12
12
  import { readSessionFrontmatter } from "#core/stores/sessions.js";
13
13
  import { buildAgentSystem } from "#core/agent/build-agent-system.js";
14
14
  import { CHANNELS } from "#core/constants/channels.js";
15
- import { getRuntime, RUNTIME_IDS } from "../runtimes/index.js";
15
+ import { getRuntime, RUNTIME_IDS } from "#core/runtimes/index.js";
16
16
  import { detectAll } from "#core/runtimes/detect.js";
17
17
  import { buildRuntimeBridgeHint as buildApfHint } from "#core/agent/runtime-bridge.js";
18
18
  import {
@@ -22,17 +22,17 @@ import {
22
22
  } from "#core/stores/runtime-sessions.js";
23
23
  import { runSuperAgent, isSuperAgentEnabled } from "#core/agent/super-agent.js";
24
24
 
25
- export function register(app, { projects, registries, plugins, project, config }) {
26
- app.get("/runtimes", (_req, res) =>
25
+ export function register(api, { projects, registries, plugins, project, config }) {
26
+ api.get("/runtimes", (_req, res) =>
27
27
  res.json({ runtimes: RUNTIME_IDS })
28
28
  );
29
29
 
30
- app.get("/env/detect", async (_req, res) => {
30
+ api.get("/env/detect", async (_req, res) => {
31
31
  const detected = await detectAll();
32
32
  res.json(detected);
33
33
  });
34
34
 
35
- app.post("/projects/:pid/agents/:slug/runtime", async (req, res) => {
35
+ api.post("/projects/:pid/agents/:slug/runtime", async (req, res) => {
36
36
  const p = project(req, res);
37
37
  if (!p) return;
38
38
  const { runtime, prompt, timeoutMs } = req.body || {};
@@ -146,7 +146,7 @@ export function register(app, { projects, registries, plugins, project, config }
146
146
  });
147
147
 
148
148
  // ---- Session resume — reads APC session file + (optionally) external transcript ----
149
- app.get("/projects/:pid/sessions/:id/resume", async (req, res) => {
149
+ api.get("/projects/:pid/sessions/:id/resume", async (req, res) => {
150
150
  const p = project(req, res);
151
151
  if (!p) return;
152
152
  const { id } = req.params;