@geminilight/mindos 0.6.61 → 0.6.65

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 (313) hide show
  1. package/_standalone/.antigravity/mcp_config.json +14 -0
  2. package/_standalone/.mindos-build-version +1 -1
  3. package/_standalone/.next/BUILD_ID +1 -1
  4. package/_standalone/.next/app-path-routes-manifest.json +23 -23
  5. package/_standalone/.next/build-manifest.json +2 -2
  6. package/_standalone/.next/cache/.previewinfo +1 -1
  7. package/_standalone/.next/cache/.rscinfo +1 -1
  8. package/_standalone/.next/cache/config.json +3 -3
  9. package/_standalone/.next/prerender-manifest.json +3 -3
  10. package/_standalone/.next/server/app/.well-known/agent-card.json/route_client-reference-manifest.js +1 -1
  11. package/_standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  12. package/_standalone/.next/server/app/_global-error.html +2 -2
  13. package/_standalone/.next/server/app/_global-error.rsc +1 -1
  14. package/_standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  15. package/_standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  16. package/_standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  17. package/_standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  18. package/_standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  19. package/_standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  20. package/_standalone/.next/server/app/_not-found/page.js +1 -1
  21. package/_standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  22. package/_standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  23. package/_standalone/.next/server/app/agents/[agentKey]/page.js +1 -1
  24. package/_standalone/.next/server/app/agents/[agentKey]/page.js.nft.json +1 -1
  25. package/_standalone/.next/server/app/agents/[agentKey]/page_client-reference-manifest.js +1 -1
  26. package/_standalone/.next/server/app/agents/page.js +1 -1
  27. package/_standalone/.next/server/app/agents/page.js.nft.json +1 -1
  28. package/_standalone/.next/server/app/agents/page_client-reference-manifest.js +1 -1
  29. package/_standalone/.next/server/app/api/a2a/agents/route_client-reference-manifest.js +1 -1
  30. package/_standalone/.next/server/app/api/a2a/delegations/route_client-reference-manifest.js +1 -1
  31. package/_standalone/.next/server/app/api/a2a/discover/route_client-reference-manifest.js +1 -1
  32. package/_standalone/.next/server/app/api/a2a/route_client-reference-manifest.js +1 -1
  33. package/_standalone/.next/server/app/api/acp/config/route_client-reference-manifest.js +1 -1
  34. package/_standalone/.next/server/app/api/acp/detect/route.js +1 -1
  35. package/_standalone/.next/server/app/api/acp/detect/route_client-reference-manifest.js +1 -1
  36. package/_standalone/.next/server/app/api/acp/install/route_client-reference-manifest.js +1 -1
  37. package/_standalone/.next/server/app/api/acp/registry/route.js +1 -1
  38. package/_standalone/.next/server/app/api/acp/registry/route_client-reference-manifest.js +1 -1
  39. package/_standalone/.next/server/app/api/acp/session/route_client-reference-manifest.js +1 -1
  40. package/_standalone/.next/server/app/api/agent-activity/route_client-reference-manifest.js +1 -1
  41. package/_standalone/.next/server/app/api/agents/copy-skill/route.js.nft.json +1 -1
  42. package/_standalone/.next/server/app/api/agents/copy-skill/route_client-reference-manifest.js +1 -1
  43. package/_standalone/.next/server/app/api/agents/custom/detect/route_client-reference-manifest.js +1 -1
  44. package/_standalone/.next/server/app/api/agents/custom/route_client-reference-manifest.js +1 -1
  45. package/_standalone/.next/server/app/api/ask/route.js +53 -47
  46. package/_standalone/.next/server/app/api/ask/route.js.nft.json +1 -1
  47. package/_standalone/.next/server/app/api/ask/route_client-reference-manifest.js +1 -1
  48. package/_standalone/.next/server/app/api/ask-sessions/route_client-reference-manifest.js +1 -1
  49. package/_standalone/.next/server/app/api/auth/route_client-reference-manifest.js +1 -1
  50. package/_standalone/.next/server/app/api/backlinks/route.js.nft.json +1 -1
  51. package/_standalone/.next/server/app/api/backlinks/route_client-reference-manifest.js +1 -1
  52. package/_standalone/.next/server/app/api/bootstrap/route.js.nft.json +1 -1
  53. package/_standalone/.next/server/app/api/bootstrap/route_client-reference-manifest.js +1 -1
  54. package/_standalone/.next/server/app/api/changes/route.js.nft.json +1 -1
  55. package/_standalone/.next/server/app/api/changes/route_client-reference-manifest.js +1 -1
  56. package/_standalone/.next/server/app/api/export/route.js.nft.json +1 -1
  57. package/_standalone/.next/server/app/api/export/route_client-reference-manifest.js +1 -1
  58. package/_standalone/.next/server/app/api/extract-pdf/route_client-reference-manifest.js +1 -1
  59. package/_standalone/.next/server/app/api/file/import/route.js +1 -1
  60. package/_standalone/.next/server/app/api/file/import/route.js.nft.json +1 -1
  61. package/_standalone/.next/server/app/api/file/import/route_client-reference-manifest.js +1 -1
  62. package/_standalone/.next/server/app/api/file/raw/route.js.nft.json +1 -1
  63. package/_standalone/.next/server/app/api/file/raw/route_client-reference-manifest.js +1 -1
  64. package/_standalone/.next/server/app/api/file/route.js.nft.json +1 -1
  65. package/_standalone/.next/server/app/api/file/route_client-reference-manifest.js +1 -1
  66. package/_standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  67. package/_standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  68. package/_standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  69. package/_standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  70. package/_standalone/.next/server/app/api/graph/route.js.nft.json +1 -1
  71. package/_standalone/.next/server/app/api/graph/route_client-reference-manifest.js +1 -1
  72. package/_standalone/.next/server/app/api/health/route_client-reference-manifest.js +1 -1
  73. package/_standalone/.next/server/app/api/inbox/route.js.nft.json +1 -1
  74. package/_standalone/.next/server/app/api/inbox/route_client-reference-manifest.js +1 -1
  75. package/_standalone/.next/server/app/api/init/route.js.nft.json +1 -1
  76. package/_standalone/.next/server/app/api/init/route_client-reference-manifest.js +1 -1
  77. package/_standalone/.next/server/app/api/mcp/agents/route.js +1 -1
  78. package/_standalone/.next/server/app/api/mcp/agents/route.js.nft.json +1 -1
  79. package/_standalone/.next/server/app/api/mcp/agents/route_client-reference-manifest.js +1 -1
  80. package/_standalone/.next/server/app/api/mcp/install/route_client-reference-manifest.js +1 -1
  81. package/_standalone/.next/server/app/api/mcp/install-skill/route_client-reference-manifest.js +1 -1
  82. package/_standalone/.next/server/app/api/mcp/restart/route_client-reference-manifest.js +1 -1
  83. package/_standalone/.next/server/app/api/mcp/status/route_client-reference-manifest.js +1 -1
  84. package/_standalone/.next/server/app/api/mcp/uninstall/route_client-reference-manifest.js +1 -1
  85. package/_standalone/.next/server/app/api/monitoring/route.js.nft.json +1 -1
  86. package/_standalone/.next/server/app/api/monitoring/route_client-reference-manifest.js +1 -1
  87. package/_standalone/.next/server/app/api/recent-files/route.js.nft.json +1 -1
  88. package/_standalone/.next/server/app/api/recent-files/route_client-reference-manifest.js +1 -1
  89. package/_standalone/.next/server/app/api/restart/route_client-reference-manifest.js +1 -1
  90. package/_standalone/.next/server/app/api/search/route.js.nft.json +1 -1
  91. package/_standalone/.next/server/app/api/search/route_client-reference-manifest.js +1 -1
  92. package/_standalone/.next/server/app/api/settings/list-models/route.js +1 -1
  93. package/_standalone/.next/server/app/api/settings/list-models/route_client-reference-manifest.js +1 -1
  94. package/_standalone/.next/server/app/api/settings/reset-token/route_client-reference-manifest.js +1 -1
  95. package/_standalone/.next/server/app/api/settings/route.js +1 -1
  96. package/_standalone/.next/server/app/api/settings/route.js.nft.json +1 -1
  97. package/_standalone/.next/server/app/api/settings/route_client-reference-manifest.js +1 -1
  98. package/_standalone/.next/server/app/api/settings/test-key/route.js +1 -1
  99. package/_standalone/.next/server/app/api/settings/test-key/route_client-reference-manifest.js +1 -1
  100. package/_standalone/.next/server/app/api/setup/check-path/route_client-reference-manifest.js +1 -1
  101. package/_standalone/.next/server/app/api/setup/check-port/route_client-reference-manifest.js +1 -1
  102. package/_standalone/.next/server/app/api/setup/generate-token/route_client-reference-manifest.js +1 -1
  103. package/_standalone/.next/server/app/api/setup/ls/route_client-reference-manifest.js +1 -1
  104. package/_standalone/.next/server/app/api/setup/route.js +1 -1
  105. package/_standalone/.next/server/app/api/setup/route_client-reference-manifest.js +1 -1
  106. package/_standalone/.next/server/app/api/skills/route.js +1 -1
  107. package/_standalone/.next/server/app/api/skills/route_client-reference-manifest.js +1 -1
  108. package/_standalone/.next/server/app/api/sync/route_client-reference-manifest.js +1 -1
  109. package/_standalone/.next/server/app/api/tree-version/route.js.nft.json +1 -1
  110. package/_standalone/.next/server/app/api/tree-version/route_client-reference-manifest.js +1 -1
  111. package/_standalone/.next/server/app/api/uninstall/route_client-reference-manifest.js +1 -1
  112. package/_standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  113. package/_standalone/.next/server/app/api/update-check/route_client-reference-manifest.js +1 -1
  114. package/_standalone/.next/server/app/api/update-status/route_client-reference-manifest.js +1 -1
  115. package/_standalone/.next/server/app/api/workflows/route.js.nft.json +1 -1
  116. package/_standalone/.next/server/app/api/workflows/route_client-reference-manifest.js +1 -1
  117. package/_standalone/.next/server/app/changes/page.js +1 -1
  118. package/_standalone/.next/server/app/changes/page.js.nft.json +1 -1
  119. package/_standalone/.next/server/app/changes/page_client-reference-manifest.js +1 -1
  120. package/_standalone/.next/server/app/echo/[segment]/page.js +2 -2
  121. package/_standalone/.next/server/app/echo/[segment]/page.js.nft.json +1 -1
  122. package/_standalone/.next/server/app/echo/[segment]/page_client-reference-manifest.js +1 -1
  123. package/_standalone/.next/server/app/echo/page.js +1 -1
  124. package/_standalone/.next/server/app/echo/page.js.nft.json +1 -1
  125. package/_standalone/.next/server/app/echo/page_client-reference-manifest.js +1 -1
  126. package/_standalone/.next/server/app/explore/page.js +1 -1
  127. package/_standalone/.next/server/app/explore/page.js.nft.json +1 -1
  128. package/_standalone/.next/server/app/explore/page_client-reference-manifest.js +1 -1
  129. package/_standalone/.next/server/app/help/page.js +1 -1
  130. package/_standalone/.next/server/app/help/page.js.nft.json +1 -1
  131. package/_standalone/.next/server/app/help/page_client-reference-manifest.js +1 -1
  132. package/_standalone/.next/server/app/inbox/history/page.js +1 -1
  133. package/_standalone/.next/server/app/inbox/history/page.js.nft.json +1 -1
  134. package/_standalone/.next/server/app/inbox/history/page_client-reference-manifest.js +1 -1
  135. package/_standalone/.next/server/app/login/page.js +1 -1
  136. package/_standalone/.next/server/app/login/page.js.nft.json +1 -1
  137. package/_standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
  138. package/_standalone/.next/server/app/page.js +1 -1
  139. package/_standalone/.next/server/app/page.js.nft.json +1 -1
  140. package/_standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  141. package/_standalone/.next/server/app/setup/page.js +2 -2
  142. package/_standalone/.next/server/app/setup/page.js.nft.json +1 -1
  143. package/_standalone/.next/server/app/setup/page_client-reference-manifest.js +1 -1
  144. package/_standalone/.next/server/app/trash/page.js +3 -3
  145. package/_standalone/.next/server/app/trash/page.js.nft.json +1 -1
  146. package/_standalone/.next/server/app/trash/page_client-reference-manifest.js +1 -1
  147. package/_standalone/.next/server/app/view/[...path]/page.js +2 -2
  148. package/_standalone/.next/server/app/view/[...path]/page.js.nft.json +1 -1
  149. package/_standalone/.next/server/app/view/[...path]/page_client-reference-manifest.js +1 -1
  150. package/_standalone/.next/server/app/wiki/page.js +1 -1
  151. package/_standalone/.next/server/app/wiki/page.js.nft.json +1 -1
  152. package/_standalone/.next/server/app/wiki/page_client-reference-manifest.js +1 -1
  153. package/_standalone/.next/server/app-paths-manifest.json +23 -23
  154. package/_standalone/.next/server/chunks/122.js +222 -0
  155. package/_standalone/.next/server/chunks/1550.js +1 -1
  156. package/_standalone/.next/server/chunks/1750.js +1 -1
  157. package/_standalone/.next/server/chunks/3113.js +52 -0
  158. package/_standalone/.next/server/chunks/6539.js +1 -1
  159. package/_standalone/.next/server/chunks/8388.js +3 -3
  160. package/_standalone/.next/server/chunks/953.js +3 -3
  161. package/_standalone/.next/server/pages/500.html +2 -2
  162. package/_standalone/.next/server/server-reference-manifest.js +1 -1
  163. package/_standalone/.next/server/server-reference-manifest.json +1 -1
  164. package/_standalone/.next/static/chunks/1001-99da82ec8d8c136f.js +1 -0
  165. package/_standalone/.next/static/chunks/1088-77544af0a50cb7a4.js +1 -0
  166. package/_standalone/.next/static/chunks/1467-87dde7eed498806f.js +1 -0
  167. package/_standalone/.next/static/chunks/5149-4d828886dda479fa.js +1 -0
  168. package/_standalone/.next/static/chunks/5581-c671163a2fe1b312.js +29 -0
  169. package/_standalone/.next/static/chunks/{7266-bb7be1128eccd48e.js → 5718-3837c3210a0e175f.js} +2 -2
  170. package/_standalone/.next/static/chunks/6636-53238eff89503f03.js +6 -0
  171. package/_standalone/.next/static/chunks/6757-1c1a89720fdda8f0.js +1 -0
  172. package/_standalone/.next/static/chunks/7129-20e9d2463a9da646.js +1 -0
  173. package/_standalone/.next/static/chunks/7294-cac25d97869afadc.js +1 -0
  174. package/_standalone/.next/static/chunks/8225-21e5cebc3731ddf0.js +1 -0
  175. package/_standalone/.next/static/chunks/8520-b51810e66293ceb8.js +22 -0
  176. package/_standalone/.next/static/chunks/9207-dc9c31b351a2ed78.js +1 -0
  177. package/_standalone/.next/static/chunks/app/agents/[agentKey]/page-2f5cf97e03dc1cc9.js +1 -0
  178. package/_standalone/.next/static/chunks/app/agents/page-50eac58d511dcc6e.js +1 -0
  179. package/_standalone/.next/static/chunks/app/echo/[segment]/page-2a00f4686adf3885.js +11 -0
  180. package/_standalone/.next/static/chunks/app/layout-2cb7a6602d2e5d5f.js +168 -0
  181. package/_standalone/.next/static/chunks/app/{page-6a1f8d21c12b829e.js → page-5ab911b2226f6ff7.js} +1 -1
  182. package/_standalone/.next/static/chunks/app/setup/page-907b7c57fad2292b.js +1 -0
  183. package/_standalone/.next/static/chunks/app/trash/page-11a511b065ea84c2.js +1 -0
  184. package/_standalone/.next/static/chunks/app/view/[...path]/page-26e47dd4c533a58c.js +12 -0
  185. package/_standalone/.next/static/chunks/app/wiki/page-dce495b9048022fb.js +1 -0
  186. package/_standalone/.next/static/css/67e7918f5ed7d147.css +1 -0
  187. package/_standalone/.next/trace +65 -65
  188. package/_standalone/__tests__/acp/registry.test.ts +30 -20
  189. package/_standalone/__tests__/api/ask-attachments.test.ts +194 -0
  190. package/_standalone/__tests__/api/mcp-install.test.ts +49 -2
  191. package/_standalone/__tests__/api/settings.test.ts +16 -12
  192. package/_standalone/__tests__/api/setup.test.ts +11 -9
  193. package/_standalone/__tests__/api/test-key.test.ts +0 -10
  194. package/_standalone/__tests__/components/UpdateToast.test.ts +344 -0
  195. package/_standalone/__tests__/core/context.test.ts +48 -426
  196. package/_standalone/__tests__/lib/pi-skills.test.ts +4 -4
  197. package/_standalone/__tests__/lib/settings-ai-client.test.ts +32 -12
  198. package/_standalone/__tests__/setup.ts +5 -5
  199. package/_standalone/app/globals.css +4 -4
  200. package/_standalone/components/ActivityBar.tsx +17 -6
  201. package/_standalone/components/Panel.tsx +24 -6
  202. package/_standalone/components/SidebarLayout.tsx +36 -8
  203. package/_standalone/components/agents/AgentsMcpSection.tsx +2 -2
  204. package/_standalone/components/agents/AgentsOverviewSection.tsx +5 -1
  205. package/_standalone/components/agents/AgentsPanelA2aTab.tsx +173 -113
  206. package/_standalone/components/agents/AgentsSkillsSection.tsx +2 -2
  207. package/_standalone/components/ask/AskContent.tsx +83 -44
  208. package/_standalone/components/ask/AskHeader.tsx +8 -1
  209. package/_standalone/components/ask/MessageList.tsx +37 -3
  210. package/_standalone/components/ask/ProviderModelCapsule.tsx +444 -174
  211. package/_standalone/components/home/InboxSection.tsx +25 -25
  212. package/_standalone/components/settings/AiTab.tsx +353 -298
  213. package/_standalone/components/settings/CustomProviderFields.tsx +121 -0
  214. package/_standalone/components/settings/CustomProvidersCard.tsx +154 -0
  215. package/_standalone/components/settings/KnowledgeTab.tsx +6 -20
  216. package/_standalone/components/settings/McpAgentInstall.tsx +7 -2
  217. package/_standalone/components/settings/Primitives.tsx +48 -104
  218. package/_standalone/components/settings/ProviderModal.tsx +87 -0
  219. package/_standalone/components/settings/SettingsContent.tsx +2 -5
  220. package/_standalone/components/settings/TestButton.tsx +64 -0
  221. package/_standalone/components/settings/types.ts +3 -9
  222. package/_standalone/components/settings/useCustomProviderForm.ts +132 -0
  223. package/_standalone/components/setup/StepAI.tsx +12 -5
  224. package/_standalone/components/shared/ModelInput.tsx +220 -0
  225. package/_standalone/components/shared/ProviderSelect.tsx +126 -36
  226. package/_standalone/hooks/useAskChat.ts +100 -13
  227. package/_standalone/hooks/useAskPanel.ts +17 -1
  228. package/_standalone/lib/settings-ai-client.ts +17 -8
  229. package/_standalone/tsconfig.tsbuildinfo +1 -1
  230. package/app/.antigravity/mcp_config.json +14 -0
  231. package/app/app/api/ask/route.ts +154 -45
  232. package/app/app/api/mcp/agents/route.ts +3 -3
  233. package/app/app/api/settings/list-models/route.ts +36 -9
  234. package/app/app/api/settings/route.ts +14 -42
  235. package/app/app/api/settings/test-key/route.ts +78 -2
  236. package/app/app/api/setup/route.ts +36 -18
  237. package/app/app/api/skills/route.ts +1 -1
  238. package/app/app/globals.css +4 -4
  239. package/app/app/layout.tsx +5 -3
  240. package/app/app/view/[...path]/page.tsx +5 -0
  241. package/app/components/ActivityBar.tsx +17 -6
  242. package/app/components/HomeContent.tsx +11 -0
  243. package/app/components/InboxView.tsx +656 -0
  244. package/app/components/Panel.tsx +24 -6
  245. package/app/components/SidebarLayout.tsx +36 -8
  246. package/app/components/UpdateToast.tsx +255 -0
  247. package/app/components/agents/AgentDetailContent.tsx +8 -8
  248. package/app/components/agents/AgentsMcpSection.tsx +2 -2
  249. package/app/components/agents/AgentsOverviewSection.tsx +5 -1
  250. package/app/components/agents/AgentsPanelA2aTab.tsx +173 -113
  251. package/app/components/agents/AgentsSkillsSection.tsx +2 -2
  252. package/app/components/ask/AskContent.tsx +83 -44
  253. package/app/components/ask/AskHeader.tsx +8 -1
  254. package/app/components/ask/MessageList.tsx +37 -3
  255. package/app/components/ask/ProviderModelCapsule.tsx +444 -174
  256. package/app/components/home/InboxSection.tsx +25 -25
  257. package/app/components/settings/AiTab.tsx +353 -298
  258. package/app/components/settings/CustomProviderFields.tsx +121 -0
  259. package/app/components/settings/CustomProvidersCard.tsx +154 -0
  260. package/app/components/settings/KnowledgeTab.tsx +6 -20
  261. package/app/components/settings/McpAgentInstall.tsx +7 -2
  262. package/app/components/settings/Primitives.tsx +48 -104
  263. package/app/components/settings/ProviderModal.tsx +87 -0
  264. package/app/components/settings/SettingsContent.tsx +2 -5
  265. package/app/components/settings/TestButton.tsx +64 -0
  266. package/app/components/settings/types.ts +3 -9
  267. package/app/components/settings/useCustomProviderForm.ts +132 -0
  268. package/app/components/setup/StepAI.tsx +12 -5
  269. package/app/components/shared/ModelInput.tsx +220 -0
  270. package/app/components/shared/ProviderSelect.tsx +126 -36
  271. package/app/hooks/useAskChat.ts +100 -13
  272. package/app/hooks/useAskPanel.ts +17 -1
  273. package/app/lib/acp/registry.ts +92 -10
  274. package/app/lib/agent/context.ts +65 -0
  275. package/app/lib/agent/providers.ts +25 -0
  276. package/app/lib/agent/tools.ts +1 -1
  277. package/app/lib/custom-endpoints.ts +160 -0
  278. package/app/lib/fs.ts +8 -1
  279. package/app/lib/i18n/modules/ai-chat.ts +6 -0
  280. package/app/lib/i18n/modules/knowledge.ts +16 -0
  281. package/app/lib/i18n/modules/onboarding.ts +4 -0
  282. package/app/lib/i18n/modules/settings.ts +88 -2
  283. package/app/lib/mcp-agents.ts +11 -0
  284. package/app/lib/pi-integration/skills.ts +16 -4
  285. package/app/lib/settings-ai-client.ts +17 -8
  286. package/app/lib/settings.ts +68 -72
  287. package/app/lib/types.ts +4 -0
  288. package/bin/lib/mcp-agents.js +11 -0
  289. package/bin/lib/mcp-install.js +71 -7
  290. package/package.json +1 -1
  291. package/_standalone/.next/server/chunks/530.js +0 -218
  292. package/_standalone/.next/server/chunks/8955.js +0 -52
  293. package/_standalone/.next/static/chunks/1369-7d0ac5d1564eed1e.js +0 -1
  294. package/_standalone/.next/static/chunks/3427-2e61a5df1f5e55fb.js +0 -1
  295. package/_standalone/.next/static/chunks/5581-0c700c20718bd916.js +0 -29
  296. package/_standalone/.next/static/chunks/6297-085daa21037d5f81.js +0 -1
  297. package/_standalone/.next/static/chunks/6636-9bbc90fb3b8731fe.js +0 -6
  298. package/_standalone/.next/static/chunks/7637-904b0a381dc3ec02.js +0 -1
  299. package/_standalone/.next/static/chunks/8520-76d1b05072178b43.js +0 -22
  300. package/_standalone/.next/static/chunks/8658-16ff58b75ae37fbb.js +0 -1
  301. package/_standalone/.next/static/chunks/9905-a19d379cb225246e.js +0 -1
  302. package/_standalone/.next/static/chunks/app/agents/[agentKey]/page-0ea3571c8fbae823.js +0 -1
  303. package/_standalone/.next/static/chunks/app/agents/page-66858acbcd1d4bf8.js +0 -1
  304. package/_standalone/.next/static/chunks/app/echo/[segment]/page-bf5c290fa3ccff09.js +0 -11
  305. package/_standalone/.next/static/chunks/app/layout-a5d5925b47e87cc3.js +0 -164
  306. package/_standalone/.next/static/chunks/app/setup/page-821714e7477be46c.js +0 -1
  307. package/_standalone/.next/static/chunks/app/trash/page-40bc7316806acd62.js +0 -1
  308. package/_standalone/.next/static/chunks/app/view/[...path]/page-6fbb14b8f322d0f0.js +0 -12
  309. package/_standalone/.next/static/chunks/app/wiki/page-ba36eccf4fe62cfe.js +0 -1
  310. package/_standalone/.next/static/css/b57c4eb3cc88308b.css +0 -1
  311. package/_standalone/lib/agent/context.ts +0 -403
  312. /package/_standalone/.next/static/{5GmVArEG8OX03azKICsGq → eIlwbGas1iRGonlPyEwj7}/_buildManifest.js +0 -0
  313. /package/_standalone/.next/static/{5GmVArEG8OX03azKICsGq → eIlwbGas1iRGonlPyEwj7}/_ssgManifest.js +0 -0
@@ -0,0 +1,14 @@
1
+ {
2
+ "mcpServers": {
3
+ "mindos": {
4
+ "type": "stdio",
5
+ "command": "mindos",
6
+ "args": [
7
+ "mcp"
8
+ ],
9
+ "env": {
10
+ "MCP_TRANSPORT": "stdio"
11
+ }
12
+ }
13
+ }
14
+ }
@@ -22,7 +22,9 @@ import { getFileContent, getMindRoot, collectAllFiles } from '@/lib/fs';
22
22
  import { getModelConfig, hasImages } from '@/lib/agent/model';
23
23
  import { isProviderId, type ProviderId, toPiProvider } from '@/lib/agent/providers';
24
24
  import { getRequestScopedTools, getOrganizeTools, getChatTools, WRITE_TOOLS, truncate } from '@/lib/agent/tools';
25
+ import { isCustomProviderId, findCustomProvider } from '@/lib/custom-endpoints';
25
26
  import { AGENT_SYSTEM_PROMPT, ORGANIZE_SYSTEM_PROMPT, CHAT_SYSTEM_PROMPT } from '@/lib/agent/prompt';
27
+ import { estimateStringTokens, getOllamaContextWindow } from '@/lib/agent/context';
26
28
  import type { AskModeApi } from '@/lib/types';
27
29
  import { toAgentMessages } from '@/lib/agent/to-agent-messages';
28
30
  import { logAgentOp } from '@/lib/agent/log';
@@ -38,6 +40,48 @@ import type { Message as FrontendMessage } from '@/lib/types';
38
40
 
39
41
  const MAX_DIR_FILES = 30;
40
42
 
43
+ /**
44
+ * Load attached and current files into context parts for the system prompt.
45
+ * Returns the context parts array and a list of file paths that failed to load.
46
+ * Deduplicates files and logs failures with the given mode label.
47
+ */
48
+ function loadAttachedFileContext(
49
+ attachedFiles: string[] | undefined,
50
+ currentFile: string | undefined,
51
+ mode: string,
52
+ ): { contextParts: string[]; failedFiles: string[] } {
53
+ const contextParts: string[] = [];
54
+ const failedFiles: string[] = [];
55
+ const seen = new Set<string>();
56
+
57
+ if (Array.isArray(attachedFiles) && attachedFiles.length > 0) {
58
+ for (const filePath of attachedFiles) {
59
+ if (seen.has(filePath)) continue;
60
+ seen.add(filePath);
61
+ try {
62
+ const content = truncate(getFileContent(filePath));
63
+ contextParts.push(`## Attached: ${filePath}\n\n${content}`);
64
+ } catch (err) {
65
+ console.warn(`[ask] ${mode}: failed to read attached file "${filePath}":`, err instanceof Error ? err.message : err);
66
+ failedFiles.push(filePath);
67
+ }
68
+ }
69
+ }
70
+
71
+ if (currentFile && !seen.has(currentFile)) {
72
+ seen.add(currentFile);
73
+ try {
74
+ const content = truncate(getFileContent(currentFile));
75
+ contextParts.push(`## Current file: ${currentFile}\n\n${content}`);
76
+ } catch (err) {
77
+ console.warn(`[ask] ${mode}: failed to read currentFile "${currentFile}":`, err instanceof Error ? err.message : err);
78
+ failedFiles.push(currentFile);
79
+ }
80
+ }
81
+
82
+ return { contextParts, failedFiles };
83
+ }
84
+
41
85
  /** Expand attachedFiles entries: directory paths (trailing /) become individual file paths. */
42
86
  function expandAttachedFiles(raw: string[]): string[] {
43
87
  const result: string[] = [];
@@ -706,6 +750,8 @@ export async function POST(req: NextRequest) {
706
750
  selectedAcpAgent?: { id: string; name: string } | null;
707
751
  /** Per-request provider override from the chat panel capsule */
708
752
  providerOverride?: string;
753
+ /** Per-request model override from the inline model picker */
754
+ modelOverride?: string;
709
755
  };
710
756
  try {
711
757
  body = await req.json();
@@ -719,6 +765,11 @@ export async function POST(req: NextRequest) {
719
765
  : body.mode === 'chat' ? 'chat'
720
766
  : 'agent';
721
767
 
768
+ // Diagnostic: log attached files so silent failures are visible
769
+ if (Array.isArray(attachedFiles) && attachedFiles.length > 0) {
770
+ console.log(`[ask] mode=${askMode} attachedFiles=${JSON.stringify(attachedFiles)} currentFile=${currentFile ?? 'none'}`);
771
+ }
772
+
722
773
  // Read agent config from settings
723
774
  const serverSettings = readSettings();
724
775
  const agentConfig = serverSettings.agent ?? {};
@@ -755,7 +806,7 @@ export async function POST(req: NextRequest) {
755
806
  let systemPrompt: string;
756
807
 
757
808
  if (askMode === 'organize') {
758
- // Organize mode: minimal prompt — only KB structure + uploaded files
809
+ // Organize mode: minimal prompt — only KB structure + attached/uploaded files
759
810
  const promptParts: string[] = [ORGANIZE_SYSTEM_PROMPT];
760
811
 
761
812
  promptParts.push(`---\n\nmind_root=${getMindRoot()}`);
@@ -766,6 +817,15 @@ export async function POST(req: NextRequest) {
766
817
  promptParts.push(`---\n\n## Knowledge Base Structure\n\n${bootstrapIndex.content}`);
767
818
  }
768
819
 
820
+ // Include attached KB files (@ mentions) — same pattern as chat/agent modes
821
+ const { contextParts, failedFiles } = loadAttachedFileContext(attachedFiles, currentFile, 'organize');
822
+ if (contextParts.length > 0) {
823
+ promptParts.push(`---\n\nThe user is currently viewing these files:\n\n${contextParts.join('\n\n---\n\n')}`);
824
+ }
825
+ if (failedFiles.length > 0) {
826
+ promptParts.push(`---\n\n⚠️ The following attached files could not be read: ${failedFiles.join(', ')}. Inform the user that these files were not loaded.`);
827
+ }
828
+
769
829
  if (uploadedParts.length > 0) {
770
830
  promptParts.push(
771
831
  `---\n\n## ⚠️ USER-UPLOADED FILES\n\n` +
@@ -791,28 +851,13 @@ export async function POST(req: NextRequest) {
791
851
  const now = new Date();
792
852
  promptParts.push(`---\n\n## Current Time Context\n- Current UTC Time: ${now.toISOString()}\n- System Local Time: ${new Intl.DateTimeFormat('en-US', { dateStyle: 'full', timeStyle: 'long' }).format(now)}`);
793
853
 
794
- const contextParts: string[] = [];
795
- const seen = new Set<string>();
796
- if (Array.isArray(attachedFiles) && attachedFiles.length > 0) {
797
- for (const filePath of attachedFiles!) {
798
- if (seen.has(filePath)) continue;
799
- seen.add(filePath);
800
- try {
801
- const content = truncate(getFileContent(filePath));
802
- contextParts.push(`## Attached: ${filePath}\n\n${content}`);
803
- } catch { /* ignore missing files */ }
804
- }
805
- }
806
- if (currentFile && !seen.has(currentFile)) {
807
- seen.add(currentFile);
808
- try {
809
- const content = truncate(getFileContent(currentFile));
810
- contextParts.push(`## Current file: ${currentFile}\n\n${content}`);
811
- } catch { /* ignore */ }
812
- }
854
+ const { contextParts, failedFiles } = loadAttachedFileContext(attachedFiles, currentFile, 'chat');
813
855
  if (contextParts.length > 0) {
814
856
  promptParts.push(`---\n\nThe user is currently viewing these files:\n\n${contextParts.join('\n\n---\n\n')}`);
815
857
  }
858
+ if (failedFiles.length > 0) {
859
+ promptParts.push(`---\n\n⚠️ The following attached files could not be read: ${failedFiles.join(', ')}. Inform the user that these files were not loaded.`);
860
+ }
816
861
 
817
862
  if (uploadedParts.length > 0) {
818
863
  promptParts.push(
@@ -924,28 +969,7 @@ export async function POST(req: NextRequest) {
924
969
  if (bootstrap.target_config_json?.ok) initContextBlocks.push(`## bootstrap_target_config_json\n\n${bootstrap.target_config_json.content}`);
925
970
 
926
971
  // Build initial context from attached/current files
927
- const contextParts: string[] = [];
928
- const seen = new Set<string>();
929
- const hasAttached = Array.isArray(attachedFiles) && attachedFiles.length > 0;
930
-
931
- if (hasAttached) {
932
- for (const filePath of attachedFiles!) {
933
- if (seen.has(filePath)) continue;
934
- seen.add(filePath);
935
- try {
936
- const content = truncate(getFileContent(filePath));
937
- contextParts.push(`## Attached: ${filePath}\n\n${content}`);
938
- } catch { /* ignore missing files */ }
939
- }
940
- }
941
-
942
- if (currentFile && !seen.has(currentFile)) {
943
- seen.add(currentFile);
944
- try {
945
- const content = truncate(getFileContent(currentFile));
946
- contextParts.push(`## Current file: ${currentFile}\n\n${content}`);
947
- } catch { /* ignore */ }
948
- }
972
+ const { contextParts, failedFiles } = loadAttachedFileContext(attachedFiles, currentFile, 'agent');
949
973
 
950
974
  const now = new Date();
951
975
  const timeContext = `## Current Time Context
@@ -970,6 +994,9 @@ export async function POST(req: NextRequest) {
970
994
  if (contextParts.length > 0) {
971
995
  promptParts.push(`---\n\nThe user is currently viewing these files:\n\n${contextParts.join('\n\n---\n\n')}`);
972
996
  }
997
+ if (failedFiles.length > 0) {
998
+ promptParts.push(`---\n\n⚠️ The following attached files could not be read: ${failedFiles.join(', ')}. Inform the user that these files were not loaded.`);
999
+ }
973
1000
 
974
1001
  if (uploadedParts.length > 0) {
975
1002
  promptParts.push(
@@ -984,15 +1011,97 @@ export async function POST(req: NextRequest) {
984
1011
  systemPrompt = promptParts.join('\n\n');
985
1012
  }
986
1013
 
1014
+ // Log system prompt size for diagnosing context truncation issues (e.g. Ollama)
1015
+ console.log(`[ask] mode=${askMode} systemPrompt=${systemPrompt.length} chars (~${Math.ceil(systemPrompt.length / 4)} tokens)`);
1016
+
987
1017
  try {
988
- const provOverride = body.providerOverride && isProviderId(body.providerOverride)
989
- ? body.providerOverride as ProviderId
990
- : undefined;
1018
+ let provOverride: ProviderId | undefined;
1019
+ let customProviderConfig: { apiKey: string; model: string; baseUrl: string } | undefined;
1020
+
1021
+ // Handle custom provider (cp_*) or built-in provider override
1022
+ if (body.providerOverride) {
1023
+ if (isCustomProviderId(body.providerOverride)) {
1024
+ const settings = readSettings();
1025
+ const customProvider = findCustomProvider(settings.ai.providers ?? [], body.providerOverride);
1026
+ if (!customProvider) {
1027
+ return apiError(ErrorCodes.INVALID_REQUEST, 'Custom provider not found', 400);
1028
+ }
1029
+ provOverride = customProvider.protocol;
1030
+ customProviderConfig = {
1031
+ apiKey: customProvider.apiKey,
1032
+ model: customProvider.model,
1033
+ baseUrl: customProvider.baseUrl,
1034
+ };
1035
+ } else if (isProviderId(body.providerOverride)) {
1036
+ provOverride = body.providerOverride as ProviderId;
1037
+ }
1038
+ }
1039
+
1040
+ // Per-request model override (from chat capsule model picker)
1041
+ const modelOverride = (body.modelOverride && typeof body.modelOverride === 'string')
1042
+ ? body.modelOverride.trim() : undefined;
1043
+
991
1044
  const { model, modelName, apiKey, provider, baseUrl } = getModelConfig({
992
1045
  provider: provOverride,
1046
+ apiKey: customProviderConfig?.apiKey,
1047
+ model: modelOverride ?? customProviderConfig?.model,
1048
+ baseUrl: customProviderConfig?.baseUrl,
993
1049
  hasImages: hasImages(messages),
994
1050
  });
995
1051
 
1052
+ // ── Ollama context window guard ──
1053
+ // Ollama silently truncates input that exceeds the model's actual context window.
1054
+ // Detect this and compact the system prompt to prevent attached files from being dropped.
1055
+ if (provider === 'ollama') {
1056
+ const ollamaBase = baseUrl || 'http://localhost:11434/v1';
1057
+ const actualCtx = await getOllamaContextWindow(ollamaBase, modelName);
1058
+ const promptTokens = estimateStringTokens(systemPrompt);
1059
+ // Reserve ~30% of context for conversation history + model output
1060
+ const maxPromptTokens = actualCtx ? Math.floor(actualCtx * 0.7) : undefined;
1061
+
1062
+ if (actualCtx) {
1063
+ console.log(`[ask] Ollama model="${modelName}" context=${actualCtx} promptTokens=${promptTokens} maxPromptTokens=${maxPromptTokens}`);
1064
+ }
1065
+
1066
+ if (maxPromptTokens && promptTokens > maxPromptTokens) {
1067
+ console.warn(`[ask] Ollama context overflow: prompt ${promptTokens} tokens > ${maxPromptTokens} max (${actualCtx} ctx). Compacting...`);
1068
+ // Compact by progressively stripping lower-priority sections from system prompt.
1069
+ // Priority order (keep first, strip last):
1070
+ // 1. Core system prompt (AGENT/CHAT/ORGANIZE base) — must keep
1071
+ // 2. Attached/current file content — user explicitly requested these
1072
+ // 3. KB structure (README.md) — important for navigation
1073
+ // 4. Time context — low priority
1074
+ // 5. SKILL.md + write-supplement — largest sections, can be stripped
1075
+ // 6. bootstrap INSTRUCTION/CONFIG — can be stripped for local models
1076
+
1077
+ // Strategy: strip sections between "---" delimiters from the end,
1078
+ // but preserve sections containing "Attached:" or "Current file:" or "USER-UPLOADED"
1079
+ const sections = systemPrompt.split('\n\n---\n\n');
1080
+ const preserved: string[] = [];
1081
+ let currentTokens = 0;
1082
+
1083
+ for (const section of sections) {
1084
+ const sectionTokens = estimateStringTokens(section);
1085
+ const isAttachment = section.includes('## Attached:') || section.includes('## Current file:') || section.includes('USER-UPLOADED');
1086
+ const isCore = preserved.length === 0; // first section = base system prompt
1087
+
1088
+ if (isCore || isAttachment) {
1089
+ // Always keep core prompt and user attachments
1090
+ preserved.push(section);
1091
+ currentTokens += sectionTokens;
1092
+ } else if (currentTokens + sectionTokens <= maxPromptTokens) {
1093
+ preserved.push(section);
1094
+ currentTokens += sectionTokens;
1095
+ } else {
1096
+ console.log(`[ask] Ollama compact: stripping section (${sectionTokens} tokens): ${section.slice(0, 80)}...`);
1097
+ }
1098
+ }
1099
+
1100
+ systemPrompt = preserved.join('\n\n---\n\n');
1101
+ console.log(`[ask] Ollama compacted: ${promptTokens} → ${estimateStringTokens(systemPrompt)} tokens`);
1102
+ }
1103
+ }
1104
+
996
1105
  // Convert frontend messages to AgentMessage[]
997
1106
  const agentMessages = toAgentMessages(messages);
998
1107
 
@@ -20,7 +20,7 @@ import { readSettings } from '@/lib/settings';
20
20
  import { scanSkillDirs } from '@/lib/pi-integration/skills';
21
21
  import { getMindRoot } from '@/lib/fs';
22
22
 
23
- function enrichMindOsAgent(agent: Record<string, unknown>) {
23
+ async function enrichMindOsAgent(agent: Record<string, unknown>) {
24
24
  agent.present = true;
25
25
  agent.installed = true;
26
26
  agent.scope = 'builtin';
@@ -35,7 +35,7 @@ function enrichMindOsAgent(agent: Record<string, unknown>) {
35
35
 
36
36
  try {
37
37
  const projectRoot = process.env.MINDOS_PROJECT_ROOT || path.resolve(process.cwd(), '..');
38
- const skills = scanSkillDirs({ projectRoot, mindRoot: getMindRoot() });
38
+ const skills = await scanSkillDirs({ projectRoot, mindRoot: getMindRoot() });
39
39
  const enabledSkills = skills.filter(s => s.enabled);
40
40
  agent.installedSkillNames = enabledSkills.map(s => s.name);
41
41
  agent.installedSkillCount = enabledSkills.length;
@@ -217,7 +217,7 @@ export async function GET() {
217
217
  });
218
218
 
219
219
  const mindos = agents.find(a => a.key === 'mindos');
220
- if (mindos) enrichMindOsAgent(mindos as unknown as Record<string, unknown>);
220
+ if (mindos) await enrichMindOsAgent(mindos as unknown as Record<string, unknown>);
221
221
 
222
222
  // Runtime verification: for agents marked as installed with HTTP endpoint,
223
223
  // verify endpoint is reachable (1s timeout to avoid blocking)
@@ -1,8 +1,9 @@
1
1
  export const dynamic = 'force-dynamic';
2
2
  import { NextRequest, NextResponse } from 'next/server';
3
3
  import { getModels as piGetModels } from '@mariozechner/pi-ai';
4
- import { effectiveAiConfig } from '@/lib/settings';
4
+ import { effectiveAiConfig, readSettings } from '@/lib/settings';
5
5
  import { type ProviderId, isProviderId, PROVIDER_PRESETS, toPiProvider, getDefaultBaseUrl } from '@/lib/agent/providers';
6
+ import { isProviderEntryId, findProvider } from '@/lib/custom-endpoints';
6
7
 
7
8
  const TIMEOUT = 10_000;
8
9
 
@@ -15,6 +16,31 @@ export async function POST(req: NextRequest) {
15
16
  baseUrl?: string;
16
17
  };
17
18
 
19
+ // Handle provider entry ID (p_*) — look up from unified providers list
20
+ if (provider && isProviderEntryId(provider)) {
21
+ const settings = readSettings();
22
+ const entry = findProvider(settings.ai.providers, provider);
23
+ if (!entry) {
24
+ return NextResponse.json({ ok: false, error: 'Provider not found' }, { status: 404 });
25
+ }
26
+
27
+ const ctrl = new AbortController();
28
+ const timer = setTimeout(() => ctrl.abort(), TIMEOUT);
29
+
30
+ try {
31
+ const models = await fetchModels(entry.protocol, apiKey || entry.apiKey, baseUrl || entry.baseUrl, ctrl.signal);
32
+ return NextResponse.json({ ok: true, models });
33
+ } catch (e: unknown) {
34
+ if (e instanceof Error && e.name === 'AbortError') {
35
+ return NextResponse.json({ ok: false, error: 'Request timed out' });
36
+ }
37
+ return NextResponse.json({ ok: false, error: e instanceof Error ? e.message : 'Network error' });
38
+ } finally {
39
+ clearTimeout(timer);
40
+ }
41
+ }
42
+
43
+ // Handle built-in protocol ID (openai, anthropic, etc.)
18
44
  if (!provider || !isProviderId(provider)) {
19
45
  return NextResponse.json({ ok: false, error: 'Invalid provider' }, { status: 400 });
20
46
  }
@@ -27,13 +53,15 @@ export async function POST(req: NextRequest) {
27
53
  return NextResponse.json({ ok: true, models });
28
54
  }
29
55
 
30
- const cfg = effectiveAiConfig(provider as ProviderId);
56
+ const cfg = effectiveAiConfig();
31
57
  let resolvedKey = apiKey || '';
32
- if (!resolvedKey || resolvedKey === '***set***') {
58
+ if (!resolvedKey) {
33
59
  resolvedKey = cfg.apiKey;
34
60
  }
35
61
 
36
- if (!resolvedKey) {
62
+ // Allow keyless requests when an explicit baseUrl is provided (local servers like Ollama)
63
+ const effectiveBaseUrl = baseUrl || cfg.baseUrl || '';
64
+ if (!resolvedKey && !effectiveBaseUrl) {
37
65
  return NextResponse.json({ ok: false, error: 'No API key configured' });
38
66
  }
39
67
 
@@ -41,7 +69,7 @@ export async function POST(req: NextRequest) {
41
69
  const timer = setTimeout(() => ctrl.abort(), TIMEOUT);
42
70
 
43
71
  try {
44
- const models = await fetchModels(provider as ProviderId, resolvedKey, baseUrl || cfg.baseUrl || '', ctrl.signal);
72
+ const models = await fetchModels(provider as ProviderId, resolvedKey, effectiveBaseUrl, ctrl.signal);
45
73
  return NextResponse.json({ ok: true, models });
46
74
  } catch (e: unknown) {
47
75
  if (e instanceof Error && e.name === 'AbortError') {
@@ -112,10 +140,9 @@ async function fetchAnthropicModels(apiKey: string, signal: AbortSignal): Promis
112
140
  async function fetchOpenAICompatModels(
113
141
  endpoint: string, apiKey: string, signal: AbortSignal,
114
142
  ): Promise<string[]> {
115
- const res = await fetch(endpoint, {
116
- headers: { 'Authorization': `Bearer ${apiKey}` },
117
- signal,
118
- });
143
+ const headers: Record<string, string> = {};
144
+ if (apiKey) headers['Authorization'] = `Bearer ${apiKey}`;
145
+ const res = await fetch(endpoint, { headers, signal });
119
146
 
120
147
  if (!res.ok) {
121
148
  const errBody = await res.text().catch(() => '');
@@ -2,7 +2,8 @@ export const dynamic = 'force-dynamic';
2
2
  import { NextRequest, NextResponse } from 'next/server';
3
3
  import { readSettings, writeSettings, ServerSettings } from '@/lib/settings';
4
4
  import { invalidateCache } from '@/lib/fs';
5
- import { PROVIDER_PRESETS, ALL_PROVIDER_IDS, getApiKeyEnvVar, getApiKeyFromEnv } from '@/lib/agent/providers';
5
+ import { ALL_PROVIDER_IDS, getApiKeyEnvVar, getApiKeyFromEnv } from '@/lib/agent/providers';
6
+ import { parseProviders } from '@/lib/custom-endpoints';
6
7
 
7
8
  function maskToken(token: string | undefined): string {
8
9
  if (!token) return '';
@@ -34,31 +35,19 @@ export async function GET() {
34
35
  }
35
36
  }
36
37
 
37
- // Mask API keys for all configured providers
38
- const maskedProviders: Record<string, { apiKey: string; model: string; baseUrl?: string }> = {};
39
- for (const [id, cfg] of Object.entries(settings.ai.providers)) {
40
- if (!cfg) continue;
41
- maskedProviders[id] = {
42
- apiKey: cfg.apiKey ? '***set***' : '',
43
- model: cfg.model ?? '',
44
- ...(cfg.baseUrl !== undefined ? { baseUrl: cfg.baseUrl } : {}),
45
- };
46
- }
47
-
48
- const masked = {
38
+ return NextResponse.json({
49
39
  ai: {
50
- provider: settings.ai.provider,
51
- providers: maskedProviders,
40
+ activeProvider: settings.ai.activeProvider,
41
+ providers: settings.ai.providers,
52
42
  },
53
43
  mindRoot: settings.mindRoot,
54
- webPassword: settings.webPassword ? '***set***' : '',
44
+ webPassword: settings.webPassword ?? '',
55
45
  authToken: maskToken(settings.authToken),
56
46
  mcpPort: settings.mcpPort ?? 8781,
57
47
  agent: settings.agent ?? {},
58
48
  envOverrides,
59
49
  envValues,
60
- };
61
- return NextResponse.json(masked);
50
+ });
62
51
  }
63
52
 
64
53
  export async function POST(req: NextRequest) {
@@ -66,28 +55,14 @@ export async function POST(req: NextRequest) {
66
55
  const body = await req.json() as Partial<ServerSettings>;
67
56
  const current = readSettings();
68
57
 
69
- // Merge providers dynamically, preserving masked keys ('***set***' = keep existing)
70
- const mergedProviders = { ...current.ai.providers };
71
- if (body.ai?.providers) {
72
- for (const [id, incoming] of Object.entries(body.ai.providers)) {
73
- if (!incoming) continue;
74
- const cur = mergedProviders[id as keyof typeof mergedProviders] ?? { apiKey: '', model: '' };
75
- mergedProviders[id as keyof typeof mergedProviders] = {
76
- ...cur,
77
- ...incoming,
78
- apiKey: incoming.apiKey === '***set***'
79
- ? (cur.apiKey ?? '')
80
- : (incoming.apiKey ?? cur.apiKey ?? ''),
81
- model: incoming.model ?? cur.model ?? '',
82
- };
83
- }
58
+ // Resolve AI config
59
+ const resolvedAi = { ...current.ai };
60
+ if (body.ai) {
61
+ if (body.ai.activeProvider !== undefined) resolvedAi.activeProvider = body.ai.activeProvider;
62
+ if (body.ai.providers !== undefined) resolvedAi.providers = parseProviders(body.ai.providers);
84
63
  }
85
64
 
86
- // Resolve webPassword: '***set***' means keep existing, '' means clear, anything else = new value
87
- const incomingWebPassword = body.webPassword;
88
- const resolvedWebPassword = incomingWebPassword === '***set***'
89
- ? current.webPassword
90
- : (incomingWebPassword ?? current.webPassword);
65
+ const resolvedWebPassword = body.webPassword ?? current.webPassword;
91
66
 
92
67
  // authToken is read-only via POST (use /api/settings/reset-token to regenerate)
93
68
  // but allow clearing it by passing empty string
@@ -109,10 +84,7 @@ export async function POST(req: NextRequest) {
109
84
  }
110
85
 
111
86
  const next: ServerSettings = {
112
- ai: {
113
- provider: body.ai?.provider ?? current.ai.provider,
114
- providers: mergedProviders,
115
- },
87
+ ai: resolvedAi,
116
88
  mindRoot: body.mindRoot ?? current.mindRoot,
117
89
  agent: body.agent ?? current.agent,
118
90
  webPassword: resolvedWebPassword,
@@ -4,6 +4,7 @@ import { complete } from '@mariozechner/pi-ai';
4
4
  import { effectiveAiConfig, readBaseUrlCompat, writeSettings, readSettings } from '@/lib/settings';
5
5
  import { getModelConfig } from '@/lib/agent/model';
6
6
  import { type ProviderId, isProviderId } from '@/lib/agent/providers';
7
+ import { isProviderEntryId, findProvider } from '@/lib/custom-endpoints';
7
8
 
8
9
  const TIMEOUT = 15_000;
9
10
 
@@ -39,13 +40,88 @@ function classifyPiAiError(err: unknown): { code: ErrorCode; error: string } {
39
40
  export async function POST(req: NextRequest) {
40
41
  try {
41
42
  const body = await req.json();
42
- const { provider, apiKey, model, baseUrl } = body as {
43
+ const { provider, apiKey, model, baseUrl, baseProviderId } = body as {
43
44
  provider?: string;
44
45
  apiKey?: string;
45
46
  model?: string;
46
47
  baseUrl?: string;
48
+ /** When set, run an inline test using only the supplied params (no settings fallback). */
49
+ baseProviderId?: string;
47
50
  };
48
51
 
52
+ // Inline test for unsaved custom providers — uses only the supplied params.
53
+ if (baseProviderId && isProviderId(baseProviderId)) {
54
+ if (!apiKey) {
55
+ return NextResponse.json({ ok: false, code: 'auth_error', error: 'No API key configured' });
56
+ }
57
+ if (!model) {
58
+ return NextResponse.json({ ok: false, code: 'unknown', error: 'Model is required' }, { status: 400 });
59
+ }
60
+ const start = Date.now();
61
+ const ctrl = new AbortController();
62
+ const timer = setTimeout(() => ctrl.abort(), TIMEOUT);
63
+ try {
64
+ const { model: piModel } = getModelConfig({
65
+ provider: baseProviderId as ProviderId,
66
+ apiKey,
67
+ model,
68
+ baseUrl: baseUrl || undefined,
69
+ });
70
+ await complete(piModel, {
71
+ messages: [{ role: 'user', content: 'hi', timestamp: Date.now() }],
72
+ }, {
73
+ apiKey,
74
+ signal: ctrl.signal,
75
+ });
76
+ return NextResponse.json({ ok: true, latency: Date.now() - start });
77
+ } catch (e) {
78
+ return NextResponse.json({ ok: false, ...classifyPiAiError(e) });
79
+ } finally {
80
+ clearTimeout(timer);
81
+ }
82
+ }
83
+
84
+ // Support provider entry IDs (p_*) — look up from unified providers list
85
+ if (provider && isProviderEntryId(provider)) {
86
+ const settings = readSettings();
87
+ const entry = findProvider(settings.ai.providers, provider);
88
+ if (!entry) {
89
+ return NextResponse.json(
90
+ { ok: false, code: 'unknown', error: 'Provider not found' },
91
+ { status: 400 },
92
+ );
93
+ }
94
+ const resolvedKey = apiKey || entry.apiKey;
95
+ const resolvedModel = model || entry.model;
96
+ const resolvedBaseUrl = baseUrl || entry.baseUrl;
97
+ if (!resolvedKey) {
98
+ return NextResponse.json({ ok: false, code: 'auth_error', error: 'No API key configured' });
99
+ }
100
+ const start = Date.now();
101
+ const ctrl = new AbortController();
102
+ const timer = setTimeout(() => ctrl.abort(), TIMEOUT);
103
+ try {
104
+ const { model: piModel } = getModelConfig({
105
+ provider: entry.protocol,
106
+ apiKey: resolvedKey,
107
+ model: resolvedModel || undefined,
108
+ baseUrl: resolvedBaseUrl || undefined,
109
+ });
110
+ await complete(piModel, {
111
+ messages: [{ role: 'user', content: 'hi', timestamp: Date.now() }],
112
+ }, {
113
+ apiKey: resolvedKey,
114
+ signal: ctrl.signal,
115
+ });
116
+ return NextResponse.json({ ok: true, latency: Date.now() - start });
117
+ } catch (e) {
118
+ return NextResponse.json({ ok: false, ...classifyPiAiError(e) });
119
+ } finally {
120
+ clearTimeout(timer);
121
+ }
122
+ }
123
+
124
+ // Legacy: support raw protocol IDs (openai, anthropic, etc.)
49
125
  if (!provider || !isProviderId(provider)) {
50
126
  return NextResponse.json(
51
127
  { ok: false, code: 'unknown', error: 'Invalid provider' },
@@ -55,7 +131,7 @@ export async function POST(req: NextRequest) {
55
131
 
56
132
  const cfg = effectiveAiConfig(provider as ProviderId);
57
133
  let resolvedKey = apiKey || '';
58
- if (!resolvedKey || resolvedKey === '***set***') {
134
+ if (!resolvedKey) {
59
135
  resolvedKey = cfg.apiKey;
60
136
  }
61
137