@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
@@ -1,6 +1,6 @@
1
1
  // APX Deck manifest — the data model the companion clients (deck, desktop
2
2
  // capsule) read on boot. Pure data + decoration; no HTTP or filesystem.
3
- // host/daemon/api/deck.js wraps this for the /deck/manifest endpoint.
3
+ // host/daemon/api/deck.js wraps this for the /api/deck/manifest endpoint.
4
4
 
5
5
  export const CORE_WIDGETS = [
6
6
  {
@@ -75,19 +75,19 @@ export const SAFE_ACTIONS = [
75
75
  id: "apx.copy_context",
76
76
  title: "Copiar contexto APX",
77
77
  risk: "safe",
78
- endpoint: "/projects/:pid/agents",
78
+ endpoint: "/api/projects/:pid/agents",
79
79
  },
80
80
  {
81
81
  id: "apx.voice_turn",
82
82
  title: "Hablar con APX",
83
83
  risk: "safe",
84
- endpoint: "/voice/turn",
84
+ endpoint: "/api/voice/turn",
85
85
  },
86
86
  {
87
87
  id: "apx.super_agent",
88
88
  title: "Pedir acción a APX",
89
89
  risk: "confirm",
90
- endpoint: "/projects/:pid/super-agent/chat",
90
+ endpoint: "/api/projects/:pid/super-agent/chat",
91
91
  },
92
92
  ];
93
93
 
@@ -131,7 +131,7 @@ export function decorateExternalWidgets(pluginStatus = {}, overrides = {}) {
131
131
  }
132
132
 
133
133
  /**
134
- * Build the full /deck/manifest response body.
134
+ * Build the full /api/deck/manifest response body.
135
135
  *
136
136
  * Inputs are *resolved* runtime values, not the live managers — caller is
137
137
  * responsible for catching errors in projects.list()/plugins.status() and
@@ -167,13 +167,13 @@ export function buildDeckManifest({
167
167
  projects: projectList,
168
168
  plugins: pluginStatus,
169
169
  endpoints: {
170
- health: "/health",
171
- projects: "/projects",
172
- plugins: "/plugins",
173
- voice_turn: "/voice/turn",
174
- transcribe_chunk: "/transcribe/chunk",
175
- super_agent_chat: "/projects/:pid/super-agent/chat",
176
- super_agent_stream: "/projects/:pid/super-agent/chat/stream",
170
+ health: "/api/health",
171
+ projects: "/api/projects",
172
+ plugins: "/api/plugins",
173
+ voice_turn: "/api/voice/turn",
174
+ transcribe_chunk: "/api/transcribe/chunk",
175
+ super_agent_chat: "/api/projects/:pid/super-agent/chat",
176
+ super_agent_stream: "/api/projects/:pid/super-agent/chat/stream",
177
177
  },
178
178
  },
179
179
  safety: {
@@ -1,6 +1,6 @@
1
1
  // Per-user "launch APX Desktop at login" wiring. Used by BOTH the CLI
2
2
  // (`apx desktop install` / `uninstall`) and the daemon's
3
- // /desktop/autostart endpoint so the web admin can toggle the same setting
3
+ // /api/desktop/autostart endpoint so the web admin can toggle the same setting
4
4
  // without shelling out.
5
5
  //
6
6
  // Per-user, never sudo, fully reversible. Three platforms supported:
@@ -16,6 +16,7 @@
16
16
  import fs from "node:fs";
17
17
  import os from "node:os";
18
18
  import path from "node:path";
19
+ import { DESKTOP_AUTOSTART_LOG_PATH } from "#core/config/paths.js";
19
20
  import { execFileSync } from "node:child_process";
20
21
  import { fileURLToPath } from "node:url";
21
22
  import { augmentedPath } from "#core/util/path-env.js";
@@ -28,7 +29,7 @@ export const MAC_PLIST_PATH = path.join(os.homedir(), "Library", "LaunchAg
28
29
  export const LINUX_DESKTOP_PATH = path.join(os.homedir(), ".config", "autostart", "apx-desktop.desktop");
29
30
  export const WIN_RUN_KEY = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
30
31
  export const WIN_RUN_NAME = "APXDesktop";
31
- const AUTOSTART_LOG_PATH = path.join(os.homedir(), ".apx", "desktop-autostart.log");
32
+ const AUTOSTART_LOG_PATH = DESKTOP_AUTOSTART_LOG_PATH;
32
33
 
33
34
  // ── runner resolution ────────────────────────────────────────────────────
34
35
 
@@ -1,5 +1,5 @@
1
1
  // Desktop (Electron floating window) process control — shared by the CLI
2
- // (`apx desktop start/stop/restart`) and the daemon's /desktop/{start,stop}
2
+ // (`apx desktop start/stop/restart`) and the daemon's /api/desktop/{start,stop}
3
3
  // HTTP endpoints, so both spawn/kill the window the exact same way.
4
4
  //
5
5
  // The window is a detached Electron process (it must survive the spawner so a
@@ -8,8 +8,8 @@
8
8
 
9
9
  "use strict";
10
10
  import fs from "node:fs";
11
- import os from "node:os";
12
11
  import path from "node:path";
12
+ import { DESKTOP_LOG_PATH, DESKTOP_PID_PATH } from "#core/config/paths.js";
13
13
  import { spawn, execFileSync } from "node:child_process";
14
14
  import { fileURLToPath } from "node:url";
15
15
 
@@ -19,8 +19,8 @@ const __dirname = path.dirname(__filename);
19
19
  // src/core/desktop/ → repo root is three levels up.
20
20
  const ROOT = path.resolve(__dirname, "..", "..", "..");
21
21
  export const DESKTOP_MAIN = path.resolve(__dirname, "..", "..", "interfaces", "desktop", "main.js");
22
- export const DESKTOP_PID = path.join(os.homedir(), ".apx", "desktop.pid");
23
- const DESKTOP_LOG = path.join(os.homedir(), ".apx", "desktop.log");
22
+ export const DESKTOP_PID = DESKTOP_PID_PATH;
23
+ const DESKTOP_LOG = DESKTOP_LOG_PATH;
24
24
 
25
25
  // ── PID file ────────────────────────────────────────────────────────────────
26
26
  export function readPid() {
@@ -6,7 +6,82 @@ import { randomUUID } from "node:crypto";
6
6
  const API_BASE = "https://generativelanguage.googleapis.com/v1beta/models";
7
7
 
8
8
  function getKey(config) {
9
- return config.api_key || process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY || "";
9
+ return getKeys(config)[0] || "";
10
+ }
11
+
12
+ /**
13
+ * Every key we may use, in order.
14
+ *
15
+ * WHY THIS IS A LIST. Gemini's free tier meters per KEY per MODEL per DAY, and
16
+ * the good models are metered tightly — 20 requests a day on gemini-3.5-flash.
17
+ * One key runs out mid-morning and the whole chain collapses to whatever is
18
+ * last, which in practice was a free OpenRouter router that answers with its
19
+ * raw chain of thought. Several keys against the same tier multiply the day's
20
+ * capacity by the number of keys, with no change in behaviour until one is
21
+ * exhausted.
22
+ *
23
+ * `api_key` stays the primary so nothing about the existing single-key config
24
+ * changes; `api_keys` is additive.
25
+ */
26
+ export function getKeys(config = {}) {
27
+ const raw = [
28
+ config.api_key,
29
+ ...(Array.isArray(config.api_keys) ? config.api_keys : []),
30
+ process.env.GEMINI_API_KEY,
31
+ process.env.GOOGLE_API_KEY,
32
+ ];
33
+ const seen = new Set();
34
+ const out = [];
35
+ for (const k of raw) {
36
+ const key = String(k || "").trim();
37
+ if (!key || seen.has(key)) continue;
38
+ seen.add(key);
39
+ out.push(key);
40
+ }
41
+ return out;
42
+ }
43
+
44
+ /**
45
+ * Keys known to be out of quota for a given model, until the daily reset.
46
+ *
47
+ * In memory on purpose: quotas reset on Google's clock, not ours, and a
48
+ * persisted "this key is dead" file would outlive the reset and permanently
49
+ * shrink the pool. Losing this on restart costs one wasted request per key.
50
+ */
51
+ const exhausted = new Map(); // `${model}\u0000${key}` -> epoch ms when it expires
52
+
53
+ function cooldownKey(model, key) {
54
+ return `${model}\u0000${key}`;
55
+ }
56
+
57
+ function isExhausted(model, key, now = Date.now()) {
58
+ const until = exhausted.get(cooldownKey(model, key));
59
+ if (!until) return false;
60
+ if (until <= now) {
61
+ exhausted.delete(cooldownKey(model, key));
62
+ return false;
63
+ }
64
+ return true;
65
+ }
66
+
67
+ /** Park a key until the next UTC midnight, which is when Google's RPD resets. */
68
+ function markExhausted(model, key, now = Date.now()) {
69
+ const reset = new Date(now);
70
+ reset.setUTCHours(24, 0, 0, 0);
71
+ exhausted.set(cooldownKey(model, key), reset.getTime());
72
+ }
73
+
74
+ /** Did this response mean "this key has no quota left", as opposed to a real error? */
75
+ function isQuotaError(status, json) {
76
+ if (status === 429) return true;
77
+ const msg = String(json?.error?.message || "");
78
+ const reason = String(json?.error?.status || "");
79
+ return reason === "RESOURCE_EXHAUSTED" || /quota|rate limit/i.test(msg);
80
+ }
81
+
82
+ /** Test-only: forget which keys are parked. */
83
+ export function _resetKeyCooldowns() {
84
+ exhausted.clear();
10
85
  }
11
86
 
12
87
  // Convert OpenAI-style tool schemas (`{ type: "function", function: { name,
@@ -27,59 +102,193 @@ function toGeminiTools(toolSchemas) {
27
102
  ];
28
103
  }
29
104
 
30
- // Map our message history into Gemini's `contents` array. Tool results land
31
- // as `role: "function"` parts with a `functionResponse`. Function calls
32
- // emitted by the model in earlier turns become `functionCall` parts under
33
- // `role: "model"`.
34
- function toGeminiContents(messages) {
105
+ function signatureOf(part) {
106
+ return part?.thoughtSignature || part?.thought_signature || null;
107
+ }
108
+
109
+ function callSignatureOf(tc) {
110
+ return tc?._thoughtSignature || tc?.thought_signature || null;
111
+ }
112
+
113
+ function isFunctionCallPart(p) {
114
+ return !!(p?.functionCall || p?.function_call);
115
+ }
116
+
117
+ // A raw part is replayable if it still holds content. Empty objects would be
118
+ // rejected by the API, and we never invent parts we didn't receive.
119
+ function isUsablePart(p) {
120
+ return !!p && typeof p === "object" && Object.keys(p).length > 0;
121
+ }
122
+
123
+ function callArgsOf(tc) {
124
+ const raw =
125
+ typeof tc.function?.arguments === "string"
126
+ ? safeParseJson(tc.function.arguments)
127
+ : tc.function?.arguments || tc.arguments || {};
128
+ return raw && typeof raw === "object" ? raw : {};
129
+ }
130
+
131
+ // Which models stamp a thoughtSignature on their own turns — and therefore
132
+ // demand it back on every functionCall part we replay. Declarative on purpose:
133
+ // covering a new family is one glob in this list, never a new branch in the
134
+ // code below.
135
+ //
136
+ // Patterns are shell-style globs matched against the bare model id (no
137
+ // provider prefix): `*` is any run of characters, `?` a single one.
138
+ // Verified live, one tool call per model — all return the signature:
139
+ // gemini-2.5-flash, gemini-3-flash-preview, gemini-3.1-flash-lite,
140
+ // gemini-3.6-flash. (gemini-2.0-* is retired by Google and never signed.)
141
+ //
142
+ // The 4.x/5.x entries are a forward default: the mechanism is a family trait,
143
+ // not a per-version quirk, so a new release is covered the day it ships.
144
+ // Anything still unlisted — a tuned endpoint, a family we didn't predict — is
145
+ // picked up at runtime by the evidence check in signaturesRequired().
146
+ export const THOUGHT_SIGNATURE_MODELS = [
147
+ "gemini-2.5*",
148
+ "gemini-3*",
149
+ "gemini-4*",
150
+ "gemini-5*",
151
+ ];
152
+
153
+ function globToRegExp(pattern) {
154
+ const escaped = String(pattern).replace(/[.+^${}()|[\]\\]/g, "\\$&");
155
+ return new RegExp(`^${escaped.replace(/\*/g, ".*").replace(/\?/g, ".")}$`, "i");
156
+ }
157
+
158
+ // Per-install override, no code change required:
159
+ // engines.gemini.thought_signature_models: ["gemini-3*", "my-tuned-model*"]
160
+ // A configured list REPLACES the built-in default — it is the whole answer to
161
+ // "which models use this mechanism", so an install can also opt out entirely
162
+ // with an explicit empty list.
163
+ export function modelUsesThoughtSignatures(model, config = {}) {
164
+ const configured = config?.thought_signature_models;
165
+ const patterns = Array.isArray(configured) ? configured : THOUGHT_SIGNATURE_MODELS;
166
+ const id = String(model || "");
167
+ return patterns.some((p) => globToRegExp(p).test(id));
168
+ }
169
+
170
+ function historyHasSignature(messages) {
171
+ for (const m of messages) {
172
+ if (Array.isArray(m?._geminiRawParts) && m._geminiRawParts.some(signatureOf)) return true;
173
+ if (Array.isArray(m?.tool_calls) && m.tool_calls.some(callSignatureOf)) return true;
174
+ }
175
+ return false;
176
+ }
177
+
178
+ function signaturesRequired(model, messages, config) {
179
+ if (modelUsesThoughtSignatures(model, config)) return true;
180
+ // Evidence beats the list: a model nobody declared, but which signed
181
+ // something earlier in THIS conversation, gets the same treatment.
182
+ return historyHasSignature(messages);
183
+ }
184
+
185
+ // Map our message history into Gemini's `contents` array. Tool results land as
186
+ // `functionResponse` parts on a `role: "user"` turn. Function calls emitted by
187
+ // the model in earlier turns become `functionCall` parts under `role: "model"`.
188
+ //
189
+ // Thinking-model history fidelity: when APX stores a Gemini-originated
190
+ // assistant turn it attaches `_geminiRawParts` — the verbatim `parts` array
191
+ // from the API response. We replay those raw parts here instead of
192
+ // reconstructing from `tool_calls`, which would lose the thought parts (and
193
+ // their thoughtSignature) that the signing families require in every
194
+ // subsequent turn. Falling back to reconstruction handles messages that came
195
+ // from a non-Gemini engine or were serialised before this field was added.
196
+ function toGeminiContents(messages, { model = "", config = {} } = {}) {
197
+ const requireSignatures = signaturesRequired(model, messages, config);
35
198
  const out = [];
199
+ // tool_call ids whose functionCall part we could not replay with a valid
200
+ // signature and had to narrate as text instead. Their tool results must be
201
+ // degraded to text too: Gemini rejects a functionResponse that answers a
202
+ // call it can no longer see in the history.
203
+ const degraded = new Set();
204
+
36
205
  for (const m of messages) {
37
206
  if (m.role === "tool") {
38
- out.push({
39
- role: "function",
40
- parts: [
41
- {
42
- functionResponse: {
43
- name: m.name || m.tool_name || "tool",
44
- response: { content: m.content },
45
- },
46
- },
47
- ],
48
- });
207
+ const name = m.name || m.tool_name || "tool";
208
+ const id = m.tool_call_id || m.id;
209
+ if (id && degraded.has(id)) {
210
+ out.push({
211
+ role: "user",
212
+ parts: [{ text: `[tool result: ${name}] ${asText(m.content)}` }],
213
+ });
214
+ continue;
215
+ }
216
+ // Tool results ride under role "user", NOT "function": the newer models
217
+ // (3.6/3.7) reject `role: "function"` outright —
218
+ // 400 Role 'function' is not supported. Please use a valid role: …
219
+ // — while every version accepts a user turn carrying functionResponse
220
+ // parts. Parallel results merge into a single turn, mirroring the one
221
+ // model turn that emitted the calls.
222
+ const part = { functionResponse: { name, response: { content: m.content } } };
223
+ const prev = out[out.length - 1];
224
+ if (prev && prev.role === "user" && prev.parts.every((p) => p.functionResponse)) {
225
+ prev.parts.push(part);
226
+ } else {
227
+ out.push({ role: "user", parts: [part] });
228
+ }
49
229
  continue;
50
230
  }
231
+
51
232
  if (m.role === "assistant" && Array.isArray(m.tool_calls) && m.tool_calls.length > 0) {
52
- out.push({
53
- role: "model",
54
- parts: m.tool_calls.map((tc) => {
55
- const part = {
56
- functionCall: {
57
- name: tc.function?.name || tc.name,
58
- args:
59
- typeof tc.function?.arguments === "string"
60
- ? safeParseJson(tc.function.arguments)
61
- : tc.function?.arguments || tc.arguments || {},
62
- },
63
- };
64
- // Gemini 3.x thinking models require us to echo back the
65
- // thoughtSignature that came attached to the original functionCall
66
- // part, or the API rejects the next turn with 400. We captured it
67
- // in the response parser; replay it verbatim when present.
68
- const sig = tc._thoughtSignature || tc.thought_signature;
69
- if (sig) part.thoughtSignature = sig;
70
- return part;
71
- }),
72
- });
233
+ // Preferred path: replay the raw parts as Gemini returned them. This
234
+ // preserves thought parts and their thoughtSignatures verbatim — the
235
+ // signature belongs to the PART, and rebuilding loses whichever part
236
+ // was carrying it.
237
+ const raw = Array.isArray(m._geminiRawParts) ? m._geminiRawParts.filter(isUsablePart) : null;
238
+ if (
239
+ raw &&
240
+ raw.length > 0 &&
241
+ raw.some(isFunctionCallPart) &&
242
+ (!requireSignatures || raw.some(signatureOf))
243
+ ) {
244
+ out.push({ role: "model", parts: raw });
245
+ continue;
246
+ }
247
+
248
+ // Fallback: rebuild from tool_calls. Used for calls that never came from
249
+ // a Gemini response pseudo-tool calls parsed out of plain text, or a
250
+ // turn inherited from another engine after a retry-chain model switch.
251
+ //
252
+ // The signature is per TURN, not per call: on parallel calls Gemini
253
+ // stamps only the first part and accepts the siblings bare. So one
254
+ // signature anywhere in the turn is enough to send it as real calls; a
255
+ // turn with none at all is what earns the 400.
256
+ const turnHasSignature = m.tool_calls.some(callSignatureOf);
257
+ const parts = [];
258
+ const text = typeof m.content === "string" ? m.content.trim() : "";
259
+ if (text) parts.push({ text });
260
+ for (const tc of m.tool_calls) {
261
+ const name = tc.function?.name || tc.name;
262
+ const sig = callSignatureOf(tc);
263
+ if (requireSignatures && !turnHasSignature) {
264
+ // Nothing to replay: sending this as a functionCall is a guaranteed
265
+ // 400. Narrate it instead so the model keeps the context without the
266
+ // API rejecting the turn.
267
+ if (tc.id) degraded.add(tc.id);
268
+ parts.push({ text: `[tool call: ${name}] ${JSON.stringify(callArgsOf(tc))}` });
269
+ continue;
270
+ }
271
+ const part = { functionCall: { name, args: callArgsOf(tc) } };
272
+ if (sig) part.thoughtSignature = sig;
273
+ parts.push(part);
274
+ }
275
+ if (parts.length === 0) parts.push({ text: "" });
276
+ out.push({ role: "model", parts });
73
277
  continue;
74
278
  }
279
+
75
280
  out.push({
76
281
  role: m.role === "assistant" ? "model" : "user",
77
- parts: [{ text: typeof m.content === "string" ? m.content : JSON.stringify(m.content) }],
282
+ parts: [{ text: asText(m.content) }],
78
283
  });
79
284
  }
80
285
  return out;
81
286
  }
82
287
 
288
+ function asText(v) {
289
+ return typeof v === "string" ? v : JSON.stringify(v);
290
+ }
291
+
83
292
  function safeParseJson(s) {
84
293
  try { return JSON.parse(s); } catch { return {}; }
85
294
  }
@@ -108,12 +317,12 @@ export default {
108
317
  config = {},
109
318
  signal,
110
319
  }) {
111
- const key = getKey(config);
112
- if (!key) throw new Error("gemini: no api_key (set GEMINI_API_KEY or engines.gemini.api_key)");
320
+ const keys = getKeys(config);
321
+ if (!keys.length) throw new Error("gemini: no api_key (set GEMINI_API_KEY or engines.gemini.api_key)");
113
322
  if (!model) throw new Error("gemini: model required");
114
323
 
115
324
  const body = {
116
- contents: toGeminiContents(messages),
325
+ contents: toGeminiContents(messages, { model, config }),
117
326
  generationConfig: { temperature, maxOutputTokens: maxTokens },
118
327
  };
119
328
  if (system) body.systemInstruction = { parts: [{ text: system }] };
@@ -130,26 +339,74 @@ export default {
130
339
  }
131
340
  }
132
341
 
133
- const url = `${API_BASE}/${encodeURIComponent(model)}:generateContent?key=${key}`;
134
- const res = await fetch(url, {
135
- method: "POST",
136
- headers: { "content-type": "application/json" },
137
- body: JSON.stringify(body),
138
- signal,
139
- });
140
- const json = await res.json();
141
- if (!res.ok) {
342
+ // Try each key in turn, skipping any already known to be out of quota for
343
+ // THIS model today. A key exhausted on gemini-3.5-flash usually still has
344
+ // its full allowance on the -lite tiers, so the cooldown is per model.
345
+ const fresh = keys.filter((k) => !isExhausted(model, k));
346
+ // All parked? Try them anyway rather than failing without asking — the
347
+ // cooldown is a guess about Google's clock, and being wrong should cost a
348
+ // request, not the turn.
349
+ const order = fresh.length ? fresh : keys;
350
+
351
+ let json;
352
+ let lastError = null;
353
+ for (let i = 0; i < order.length; i++) {
354
+ const key = order[i];
355
+ const url = `${API_BASE}/${encodeURIComponent(model)}:generateContent?key=${key}`;
356
+ const res = await fetch(url, {
357
+ method: "POST",
358
+ headers: { "content-type": "application/json" },
359
+ body: JSON.stringify(body),
360
+ signal,
361
+ });
362
+ const payload = await res.json();
363
+
364
+ if (res.ok) { json = payload; break; }
365
+
366
+ if (isQuotaError(res.status, payload)) {
367
+ markExhausted(model, key);
368
+ lastError = new Error(
369
+ `gemini ${res.status}: ${payload?.error?.message || "quota exhausted"}`
370
+ );
371
+ continue; // next key
372
+ }
373
+
374
+ // A real error — a bad request, a missing model, a revoked key. Rotating
375
+ // would just repeat it against every key and turn one clear failure into
376
+ // N slow ones.
142
377
  throw new Error(
143
- `gemini ${res.status}: ${json?.error?.message || JSON.stringify(json)}`
378
+ `gemini ${res.status}: ${payload?.error?.message || JSON.stringify(payload)}`
144
379
  );
145
380
  }
146
381
 
382
+ if (!json) {
383
+ throw lastError || new Error("gemini: every key is out of quota for this model");
384
+ }
385
+
147
386
  const parts = json.candidates?.[0]?.content?.parts || [];
148
- const text = parts.map((p) => p.text || "").join("");
387
+
388
+ // Thinking models (gemini-2.5-flash, gemini-2.5-pro, etc.) emit thought
389
+ // parts (p.thought === true) that must NOT be shown to the user — they are
390
+ // internal reasoning. Only non-thought text parts form the visible reply.
391
+ const text = parts.filter((p) => !p.thought).map((p) => p.text || "").join("");
392
+
149
393
  // Extract function calls and translate them into the OpenAI-shaped
150
394
  // tool_calls the run-agent loop expects.
395
+ //
396
+ // KEY FIX: On thinking models the thoughtSignature lives on the THOUGHT
397
+ // text part (p.thought === true), NOT on the functionCall part. We track
398
+ // the most recently seen signature as we walk the parts array, so the
399
+ // functionCall that follows a thought part picks it up correctly.
400
+ // Signatures that appear directly on a functionCall part are also captured
401
+ // (some non-thinking Gemini variants may do this).
151
402
  const toolCalls = [];
403
+ let pendingThoughtSig = null;
152
404
  for (const p of parts) {
405
+ // Collect thoughtSignature from wherever Gemini puts it: thought parts
406
+ // or (less commonly) directly on the functionCall part.
407
+ const partSig = p.thoughtSignature || p.thought_signature;
408
+ if (partSig) pendingThoughtSig = partSig;
409
+
153
410
  const fc = p.functionCall || p.function_call;
154
411
  if (fc?.name) {
155
412
  const tc = {
@@ -160,13 +417,13 @@ export default {
160
417
  arguments: typeof fc.args === "string" ? fc.args : JSON.stringify(fc.args || {}),
161
418
  },
162
419
  };
163
- // Thinking models (Gemini 3.x) attach a thoughtSignature to the part
164
- // alongside the functionCall. We must replay it on the next request
165
- // or the API 400s. Carry it on the tool_call so the next call to
166
- // toGeminiContents() can put it back. Underscore prefix marks it as
167
- // adapter-private metadata other engines should ignore.
168
- const sig = p.thoughtSignature || p.thought_signature;
169
- if (sig) tc._thoughtSignature = sig;
420
+ // Attach the signature we collected above. On the next request
421
+ // toGeminiContents() puts it back on the part so Gemini 2.5+ doesn't
422
+ // reject the turn with 400 "missing thought_signature".
423
+ if (pendingThoughtSig) {
424
+ tc._thoughtSignature = pendingThoughtSig;
425
+ pendingThoughtSig = null; // consumed; reset for potential next call
426
+ }
170
427
  toolCalls.push(tc);
171
428
  }
172
429
  }
@@ -174,6 +431,11 @@ export default {
174
431
  return {
175
432
  text,
176
433
  tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
434
+ // Raw parts stored so toGeminiContents() can replay the model turn
435
+ // faithfully (thought parts + functionCalls) on the next request.
436
+ // This is the most robust way to satisfy Gemini's thought-signature
437
+ // requirement without any reconstruction logic.
438
+ _geminiRawParts: parts.length > 0 ? parts : undefined,
177
439
  finish_reason: json.candidates?.[0]?.finishReason || null,
178
440
  usage: {
179
441
  input_tokens: json.usageMetadata?.promptTokenCount || 0,
@@ -1,6 +1,19 @@
1
1
  // Shared OpenAI-compatible chat adapter (OpenAI, Groq, OpenRouter, …).
2
2
  import { pingUrl } from "./_health.js";
3
3
 
4
+ // Adapters stash provider-private metadata on tool_calls under an underscore
5
+ // prefix (e.g. gemini's `_thoughtSignature`). Strict OpenAI-shaped APIs reject
6
+ // unknown properties, and a retry-chain switch can hand us a turn produced by
7
+ // another engine — so scrub those keys on the way out.
8
+ function stripPrivateFields(tc) {
9
+ if (!tc || typeof tc !== "object") return tc;
10
+ const out = {};
11
+ for (const [k, v] of Object.entries(tc)) {
12
+ if (!k.startsWith("_")) out[k] = v;
13
+ }
14
+ return out;
15
+ }
16
+
4
17
  export function createOpenAiCompatibleEngine({
5
18
  id,
6
19
  defaultBaseUrl,
@@ -84,7 +97,7 @@ export function createOpenAiCompatibleEngine({
84
97
  role: m.role,
85
98
  content: typeof m.content === "string" ? m.content : JSON.stringify(m.content),
86
99
  };
87
- if (m.tool_calls) entry.tool_calls = m.tool_calls;
100
+ if (m.tool_calls) entry.tool_calls = m.tool_calls.map(stripPrivateFields);
88
101
  if (m.tool_call_id) entry.tool_call_id = m.tool_call_id;
89
102
  // Some adapters expect `name` on tool messages; we map from tool_name
90
103
  // (what run-agent.js writes) to be safe.
@@ -127,7 +140,13 @@ export function createOpenAiCompatibleEngine({
127
140
  }
128
141
 
129
142
  const choice = json.choices?.[0];
130
- const text = choice?.message?.content || "";
143
+ // Reasoning models on OpenRouter/Groq return their chain of thought in a
144
+ // SEPARATE field (`reasoning`, or `reasoning_content` on some routers).
145
+ // It is not an answer and must never reach a channel — fold it into the
146
+ // <think> form APX already knows how to strip per channel.
147
+ const reasoning = choice?.message?.reasoning || choice?.message?.reasoning_content || "";
148
+ const answer = choice?.message?.content || "";
149
+ const text = reasoning ? `<think>${reasoning}</think>${answer}` : answer;
131
150
  const toolCalls = choice?.message?.tool_calls;
132
151
 
133
152
  return {
@@ -4,11 +4,11 @@
4
4
  // Who consumes this:
5
5
  // • CLI — `apx setup` builds its provider/model menus from here
6
6
  // (src/interfaces/cli/commands/setup.js).
7
- // • Web — the admin panel fetches it via `GET /engines/presets` and hydrates
7
+ // • Web — the admin panel fetches it via `GET /api/engines/presets` and hydrates
8
8
  // its provider forms (src/interfaces/web/.../providers/typeStyles.ts).
9
9
  //
10
10
  // This is the OFFLINE / no-key fallback list. When the user has an api_key
11
- // configured, the daemon's `POST /engines/models` returns the provider's LIVE
11
+ // configured, the daemon's `POST /api/engines/models` returns the provider's LIVE
12
12
  // catalog instead (see ./catalog.js). The model field is ALWAYS free-text, so
13
13
  // any id can be typed even if it is not listed here.
14
14
  //
@@ -9,7 +9,7 @@
9
9
  // browser_* tool is actually called. HTTP-only fetching lives in fetch.js
10
10
  // (no Chromium needed).
11
11
  //
12
- // Endpoints (mounted at /tools/browser by api.js):
12
+ // Endpoints (mounted at /api/tools/browser by api.js):
13
13
  // POST /navigate { url, launch_options?, allow_dangerous? }
14
14
  // POST /screenshot { selector?, full_page?, width?, height?, encoded? }
15
15
  // POST /click { selector }