@mastra/factory 0.0.0 → 0.1.0-alpha.3

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 (287) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/LICENSE.md +30 -0
  3. package/dist/auth.d.ts +181 -0
  4. package/dist/auth.d.ts.map +1 -0
  5. package/dist/auth.js +370 -0
  6. package/dist/auth.js.map +1 -0
  7. package/dist/capabilities/connection.d.ts +9 -0
  8. package/dist/capabilities/connection.d.ts.map +1 -0
  9. package/dist/capabilities/connection.js +1 -0
  10. package/dist/capabilities/connection.js.map +1 -0
  11. package/dist/capabilities/intake.d.ts +93 -0
  12. package/dist/capabilities/intake.d.ts.map +1 -0
  13. package/dist/capabilities/intake.js +1 -0
  14. package/dist/capabilities/intake.js.map +1 -0
  15. package/dist/capabilities/version-control.d.ts +239 -0
  16. package/dist/capabilities/version-control.d.ts.map +1 -0
  17. package/dist/capabilities/version-control.js +1 -0
  18. package/dist/capabilities/version-control.js.map +1 -0
  19. package/dist/factory.d.ts +145 -0
  20. package/dist/factory.d.ts.map +1 -0
  21. package/dist/factory.js +38245 -0
  22. package/dist/factory.js.map +1 -0
  23. package/dist/index.d.ts +3 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +38245 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/integrations/base.d.ts +197 -0
  28. package/dist/integrations/base.d.ts.map +1 -0
  29. package/dist/integrations/base.js +1 -0
  30. package/dist/integrations/base.js.map +1 -0
  31. package/dist/integrations/github/config.d.ts +60 -0
  32. package/dist/integrations/github/config.d.ts.map +1 -0
  33. package/dist/integrations/github/config.js +28 -0
  34. package/dist/integrations/github/config.js.map +1 -0
  35. package/dist/integrations/github/factory-session.d.ts +12 -0
  36. package/dist/integrations/github/factory-session.d.ts.map +1 -0
  37. package/dist/integrations/github/factory-session.js +41 -0
  38. package/dist/integrations/github/factory-session.js.map +1 -0
  39. package/dist/integrations/github/integration.d.ts +191 -0
  40. package/dist/integrations/github/integration.d.ts.map +1 -0
  41. package/dist/integrations/github/integration.js +3001 -0
  42. package/dist/integrations/github/integration.js.map +1 -0
  43. package/dist/integrations/github/issue-triage.d.ts +27 -0
  44. package/dist/integrations/github/issue-triage.d.ts.map +1 -0
  45. package/dist/integrations/github/issue-triage.js +82 -0
  46. package/dist/integrations/github/issue-triage.js.map +1 -0
  47. package/dist/integrations/github/project-lock.d.ts +78 -0
  48. package/dist/integrations/github/project-lock.d.ts.map +1 -0
  49. package/dist/integrations/github/project-lock.js +70 -0
  50. package/dist/integrations/github/project-lock.js.map +1 -0
  51. package/dist/integrations/github/provenance.d.ts +27 -0
  52. package/dist/integrations/github/provenance.d.ts.map +1 -0
  53. package/dist/integrations/github/provenance.js +104 -0
  54. package/dist/integrations/github/provenance.js.map +1 -0
  55. package/dist/integrations/github/routes.d.ts +72 -0
  56. package/dist/integrations/github/routes.d.ts.map +1 -0
  57. package/dist/integrations/github/routes.js +1834 -0
  58. package/dist/integrations/github/routes.js.map +1 -0
  59. package/dist/integrations/github/sandbox.d.ts +268 -0
  60. package/dist/integrations/github/sandbox.d.ts.map +1 -0
  61. package/dist/integrations/github/sandbox.js +381 -0
  62. package/dist/integrations/github/sandbox.js.map +1 -0
  63. package/dist/integrations/github/session-subscriptions.d.ts +23 -0
  64. package/dist/integrations/github/session-subscriptions.d.ts.map +1 -0
  65. package/dist/integrations/github/session-subscriptions.js +190 -0
  66. package/dist/integrations/github/session-subscriptions.js.map +1 -0
  67. package/dist/integrations/github/subscriptions.d.ts +54 -0
  68. package/dist/integrations/github/subscriptions.d.ts.map +1 -0
  69. package/dist/integrations/github/subscriptions.js +70 -0
  70. package/dist/integrations/github/subscriptions.js.map +1 -0
  71. package/dist/integrations/github/webhook.d.ts +87 -0
  72. package/dist/integrations/github/webhook.d.ts.map +1 -0
  73. package/dist/integrations/github/webhook.js +381 -0
  74. package/dist/integrations/github/webhook.js.map +1 -0
  75. package/dist/integrations/linear/agent-tools.d.ts +34 -0
  76. package/dist/integrations/linear/agent-tools.d.ts.map +1 -0
  77. package/dist/integrations/linear/agent-tools.js +108 -0
  78. package/dist/integrations/linear/agent-tools.js.map +1 -0
  79. package/dist/integrations/linear/integration.d.ts +233 -0
  80. package/dist/integrations/linear/integration.d.ts.map +1 -0
  81. package/dist/integrations/linear/integration.js +962 -0
  82. package/dist/integrations/linear/integration.js.map +1 -0
  83. package/dist/integrations/linear/routes.d.ts +61 -0
  84. package/dist/integrations/linear/routes.d.ts.map +1 -0
  85. package/dist/integrations/linear/routes.js +242 -0
  86. package/dist/integrations/linear/routes.js.map +1 -0
  87. package/dist/integrations/linear/storage.d.ts +65 -0
  88. package/dist/integrations/linear/storage.d.ts.map +1 -0
  89. package/dist/integrations/linear/storage.js +1 -0
  90. package/dist/integrations/linear/storage.js.map +1 -0
  91. package/dist/integrations/platform/api-client.d.ts +24 -0
  92. package/dist/integrations/platform/api-client.d.ts.map +1 -0
  93. package/dist/integrations/platform/api-client.js +153 -0
  94. package/dist/integrations/platform/api-client.js.map +1 -0
  95. package/dist/integrations/platform/github/event-worker.d.ts +44 -0
  96. package/dist/integrations/platform/github/event-worker.d.ts.map +1 -0
  97. package/dist/integrations/platform/github/event-worker.js +585 -0
  98. package/dist/integrations/platform/github/event-worker.js.map +1 -0
  99. package/dist/integrations/platform/github/integration.d.ts +36 -0
  100. package/dist/integrations/platform/github/integration.d.ts.map +1 -0
  101. package/dist/integrations/platform/github/integration.js +3253 -0
  102. package/dist/integrations/platform/github/integration.js.map +1 -0
  103. package/dist/integrations/platform/linear/integration.d.ts +46 -0
  104. package/dist/integrations/platform/linear/integration.d.ts.map +1 -0
  105. package/dist/integrations/platform/linear/integration.js +891 -0
  106. package/dist/integrations/platform/linear/integration.js.map +1 -0
  107. package/dist/integrations/workos/integration.d.ts +39 -0
  108. package/dist/integrations/workos/integration.d.ts.map +1 -0
  109. package/dist/integrations/workos/integration.js +96 -0
  110. package/dist/integrations/workos/integration.js.map +1 -0
  111. package/dist/routes/config.d.ts +220 -0
  112. package/dist/routes/config.d.ts.map +1 -0
  113. package/dist/routes/config.js +770 -0
  114. package/dist/routes/config.js.map +1 -0
  115. package/dist/routes/fs.d.ts +105 -0
  116. package/dist/routes/fs.d.ts.map +1 -0
  117. package/dist/routes/fs.js +294 -0
  118. package/dist/routes/fs.js.map +1 -0
  119. package/dist/routes/intake.d.ts +23 -0
  120. package/dist/routes/intake.d.ts.map +1 -0
  121. package/dist/routes/intake.js +179 -0
  122. package/dist/routes/intake.js.map +1 -0
  123. package/dist/routes/oauth.d.ts +43 -0
  124. package/dist/routes/oauth.d.ts.map +1 -0
  125. package/dist/routes/oauth.js +25058 -0
  126. package/dist/routes/oauth.js.map +1 -0
  127. package/dist/routes/projects.d.ts +19 -0
  128. package/dist/routes/projects.d.ts.map +1 -0
  129. package/dist/routes/projects.js +395 -0
  130. package/dist/routes/projects.js.map +1 -0
  131. package/dist/routes/provider-credentials.d.ts +81 -0
  132. package/dist/routes/provider-credentials.d.ts.map +1 -0
  133. package/dist/routes/provider-credentials.js +68 -0
  134. package/dist/routes/provider-credentials.js.map +1 -0
  135. package/dist/routes/route.d.ts +48 -0
  136. package/dist/routes/route.d.ts.map +1 -0
  137. package/dist/routes/route.js +11 -0
  138. package/dist/routes/route.js.map +1 -0
  139. package/dist/routes/skills.d.ts +30 -0
  140. package/dist/routes/skills.d.ts.map +1 -0
  141. package/dist/routes/skills.js +181 -0
  142. package/dist/routes/skills.js.map +1 -0
  143. package/dist/routes/surface.d.ts +83 -0
  144. package/dist/routes/surface.d.ts.map +1 -0
  145. package/dist/routes/surface.js +28519 -0
  146. package/dist/routes/surface.js.map +1 -0
  147. package/dist/routes/tenant-credentials.d.ts +74 -0
  148. package/dist/routes/tenant-credentials.d.ts.map +1 -0
  149. package/dist/routes/tenant-credentials.js +181 -0
  150. package/dist/routes/tenant-credentials.js.map +1 -0
  151. package/dist/routes/work-items.d.ts +40 -0
  152. package/dist/routes/work-items.d.ts.map +1 -0
  153. package/dist/routes/work-items.js +902 -0
  154. package/dist/routes/work-items.js.map +1 -0
  155. package/dist/rules/binding-context.d.ts +5 -0
  156. package/dist/rules/binding-context.d.ts.map +1 -0
  157. package/dist/rules/binding-context.js +40 -0
  158. package/dist/rules/binding-context.js.map +1 -0
  159. package/dist/rules/defaults.d.ts +9 -0
  160. package/dist/rules/defaults.d.ts.map +1 -0
  161. package/dist/rules/defaults.js +334 -0
  162. package/dist/rules/defaults.js.map +1 -0
  163. package/dist/rules/dispatcher.d.ts +40 -0
  164. package/dist/rules/dispatcher.d.ts.map +1 -0
  165. package/dist/rules/dispatcher.js +695 -0
  166. package/dist/rules/dispatcher.js.map +1 -0
  167. package/dist/rules/github-service.d.ts +29 -0
  168. package/dist/rules/github-service.d.ts.map +1 -0
  169. package/dist/rules/github-service.js +474 -0
  170. package/dist/rules/github-service.js.map +1 -0
  171. package/dist/rules/index.d.ts +7 -0
  172. package/dist/rules/index.d.ts.map +1 -0
  173. package/dist/rules/index.js +583 -0
  174. package/dist/rules/index.js.map +1 -0
  175. package/dist/rules/linear-service.d.ts +27 -0
  176. package/dist/rules/linear-service.d.ts.map +1 -0
  177. package/dist/rules/linear-service.js +343 -0
  178. package/dist/rules/linear-service.js.map +1 -0
  179. package/dist/rules/processor.d.ts +54 -0
  180. package/dist/rules/processor.d.ts.map +1 -0
  181. package/dist/rules/processor.js +629 -0
  182. package/dist/rules/processor.js.map +1 -0
  183. package/dist/rules/resolve.d.ts +17 -0
  184. package/dist/rules/resolve.d.ts.map +1 -0
  185. package/dist/rules/resolve.js +27 -0
  186. package/dist/rules/resolve.js.map +1 -0
  187. package/dist/rules/start-coordinator.d.ts +58 -0
  188. package/dist/rules/start-coordinator.d.ts.map +1 -0
  189. package/dist/rules/start-coordinator.js +136 -0
  190. package/dist/rules/start-coordinator.js.map +1 -0
  191. package/dist/rules/tools.d.ts +10 -0
  192. package/dist/rules/tools.d.ts.map +1 -0
  193. package/dist/rules/tools.js +93 -0
  194. package/dist/rules/tools.js.map +1 -0
  195. package/dist/rules/transition-service.d.ts +32 -0
  196. package/dist/rules/transition-service.d.ts.map +1 -0
  197. package/dist/rules/transition-service.js +474 -0
  198. package/dist/rules/transition-service.js.map +1 -0
  199. package/dist/rules/types.d.ts +225 -0
  200. package/dist/rules/types.d.ts.map +1 -0
  201. package/dist/rules/types.js +33 -0
  202. package/dist/rules/types.js.map +1 -0
  203. package/dist/rules/validation.d.ts +11 -0
  204. package/dist/rules/validation.d.ts.map +1 -0
  205. package/dist/rules/validation.js +310 -0
  206. package/dist/rules/validation.js.map +1 -0
  207. package/dist/sandbox/fleet.d.ts +208 -0
  208. package/dist/sandbox/fleet.d.ts.map +1 -0
  209. package/dist/sandbox/fleet.js +259 -0
  210. package/dist/sandbox/fleet.js.map +1 -0
  211. package/dist/sandbox/reattach.d.ts +3 -0
  212. package/dist/sandbox/reattach.d.ts.map +1 -0
  213. package/dist/sandbox/reattach.js +9 -0
  214. package/dist/sandbox/reattach.js.map +1 -0
  215. package/dist/server-error.d.ts +12 -0
  216. package/dist/server-error.d.ts.map +1 -0
  217. package/dist/server-error.js +20 -0
  218. package/dist/server-error.js.map +1 -0
  219. package/dist/skills/service.d.ts +48 -0
  220. package/dist/skills/service.d.ts.map +1 -0
  221. package/dist/skills/service.js +45 -0
  222. package/dist/skills/service.js.map +1 -0
  223. package/dist/spa-static.d.ts +34 -0
  224. package/dist/spa-static.d.ts.map +1 -0
  225. package/dist/spa-static.js +73 -0
  226. package/dist/spa-static.js.map +1 -0
  227. package/dist/state-signing.d.ts +48 -0
  228. package/dist/state-signing.d.ts.map +1 -0
  229. package/dist/state-signing.js +48 -0
  230. package/dist/state-signing.js.map +1 -0
  231. package/dist/storage/domains/audit/agent-audit.d.ts +27 -0
  232. package/dist/storage/domains/audit/agent-audit.d.ts.map +1 -0
  233. package/dist/storage/domains/audit/agent-audit.js +65 -0
  234. package/dist/storage/domains/audit/agent-audit.js.map +1 -0
  235. package/dist/storage/domains/audit/base.d.ts +140 -0
  236. package/dist/storage/domains/audit/base.d.ts.map +1 -0
  237. package/dist/storage/domains/audit/base.js +127 -0
  238. package/dist/storage/domains/audit/base.js.map +1 -0
  239. package/dist/storage/domains/audit/domain.d.ts +69 -0
  240. package/dist/storage/domains/audit/domain.d.ts.map +1 -0
  241. package/dist/storage/domains/audit/domain.js +165 -0
  242. package/dist/storage/domains/audit/domain.js.map +1 -0
  243. package/dist/storage/domains/credentials/base.d.ts +134 -0
  244. package/dist/storage/domains/credentials/base.d.ts.map +1 -0
  245. package/dist/storage/domains/credentials/base.js +257 -0
  246. package/dist/storage/domains/credentials/base.js.map +1 -0
  247. package/dist/storage/domains/intake/base.d.ts +38 -0
  248. package/dist/storage/domains/intake/base.d.ts.map +1 -0
  249. package/dist/storage/domains/intake/base.js +62 -0
  250. package/dist/storage/domains/intake/base.js.map +1 -0
  251. package/dist/storage/domains/integrations/base.d.ts +126 -0
  252. package/dist/storage/domains/integrations/base.d.ts.map +1 -0
  253. package/dist/storage/domains/integrations/base.js +252 -0
  254. package/dist/storage/domains/integrations/base.js.map +1 -0
  255. package/dist/storage/domains/model-packs/base.d.ts +49 -0
  256. package/dist/storage/domains/model-packs/base.d.ts.map +1 -0
  257. package/dist/storage/domains/model-packs/base.js +95 -0
  258. package/dist/storage/domains/model-packs/base.js.map +1 -0
  259. package/dist/storage/domains/projects/base.d.ts +55 -0
  260. package/dist/storage/domains/projects/base.d.ts.map +1 -0
  261. package/dist/storage/domains/projects/base.js +99 -0
  262. package/dist/storage/domains/projects/base.js.map +1 -0
  263. package/dist/storage/domains/queue-health/base.d.ts +52 -0
  264. package/dist/storage/domains/queue-health/base.d.ts.map +1 -0
  265. package/dist/storage/domains/queue-health/base.js +87 -0
  266. package/dist/storage/domains/queue-health/base.js.map +1 -0
  267. package/dist/storage/domains/source-control/base.d.ts +288 -0
  268. package/dist/storage/domains/source-control/base.d.ts.map +1 -0
  269. package/dist/storage/domains/source-control/base.js +722 -0
  270. package/dist/storage/domains/source-control/base.js.map +1 -0
  271. package/dist/storage/domains/source-control/inmemory.d.ts +158 -0
  272. package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -0
  273. package/dist/storage/domains/source-control/inmemory.js +348 -0
  274. package/dist/storage/domains/source-control/inmemory.js.map +1 -0
  275. package/dist/storage/domains/work-items/base.d.ts +412 -0
  276. package/dist/storage/domains/work-items/base.d.ts.map +1 -0
  277. package/dist/storage/domains/work-items/base.js +1174 -0
  278. package/dist/storage/domains/work-items/base.js.map +1 -0
  279. package/dist/storage/domains/work-items/metrics.d.ts +101 -0
  280. package/dist/storage/domains/work-items/metrics.d.ts.map +1 -0
  281. package/dist/storage/domains/work-items/metrics.js +203 -0
  282. package/dist/storage/domains/work-items/metrics.js.map +1 -0
  283. package/dist/workspace.d.ts +20 -0
  284. package/dist/workspace.d.ts.map +1 -0
  285. package/dist/workspace.js +364 -0
  286. package/dist/workspace.js.map +1 -0
  287. package/package.json +19 -19
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/routes/config.ts","../../src/routes/provider-credentials.ts","../../src/routes/route.ts"],"sourcesContent":["import type { AuthStorage } from '@mastra/code-sdk/auth/storage';\nimport { removeCustomPackFromSettings } from '@mastra/code-sdk/onboarding/custom-packs';\nimport {\n removeCustomProviderFromSettings,\n upsertCustomProviderInSettings,\n} from '@mastra/code-sdk/onboarding/custom-providers';\nimport { applyOmRoleOverride, persistOmObserveAttachments } from '@mastra/code-sdk/onboarding/om-settings';\nimport { getAvailableModePacks } from '@mastra/code-sdk/onboarding/packs';\nimport type { ModePack, ProviderAccess, ProviderAccessLevel } from '@mastra/code-sdk/onboarding/packs';\nimport {\n getCustomProviderId,\n loadSettings,\n saveSettings,\n THREAD_ACTIVE_MODEL_PACK_ID_KEY,\n} from '@mastra/code-sdk/onboarding/settings';\nimport type { CustomProviderSetting } from '@mastra/code-sdk/onboarding/settings';\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\n\nimport type { Context } from 'hono';\nimport type {\n CredentialRecord,\n LoginSessionKind,\n ModelCredentialsStorage,\n} from '../storage/domains/credentials/base.js';\nimport type { ModelPackRecord, ModelPacksStorage } from '../storage/domains/model-packs/base.js';\nimport {\n getAuthProviderId,\n listTenantCredentialsForRequest,\n resolveCredentialContext,\n tenantOrgId,\n WEB_OAUTH_FLOW_KINDS,\n} from './provider-credentials.js';\nimport { Route } from './route.js';\nimport type { RouteAuth, RouteDependencies } from './route.js';\n\n/** Widen a route-local Hono context to the plain `Context` the auth helpers take. */\nfunction loose(c: unknown): Context {\n return c as Context;\n}\n\n/**\n * Server-side configuration routes for the web app.\n *\n * The browser has no access to the credential store or the model catalog, so\n * the web settings panel asks the server — which owns both — to list providers\n * and manage API keys. This mirrors the TUI's `/api-keys` command, exposing the\n * same `AuthStorage`-backed key management over HTTP.\n *\n * Keys are never returned to the client; only their presence and source.\n */\n\n/**\n * Where a provider's active credential comes from, as seen by the caller.\n * Local mode reports `oauth`/`stored` (server-global `auth.json`); tenant mode\n * reports the scoped variants (`oauth-user`/`stored-user`/`stored-org`).\n */\nexport type ProviderCredentialSource =\n 'oauth' | 'stored' | 'env' | 'none' | 'oauth-user' | 'stored-user' | 'stored-org';\n\n/** A model provider with the current source of its credentials. */\nexport interface ProviderInfo {\n provider: string;\n /** Env var the provider's key is read from, if any. */\n envVar?: string;\n /** Where the active credential comes from. */\n source: ProviderCredentialSource;\n /** Web OAuth sign-in capability, when the provider supports it. */\n oauth?: { supported: true; modes: LoginSessionKind[] };\n}\n\n/** Minimal session surface a pack activation touches. */\ninterface PackSession {\n mode: { get: () => string };\n model: { switch: (args: { modelId: string }) => Promise<void> };\n subagents: { model: { set: (args: { modelId: string; agentType: string }) => Promise<void> } };\n thread: {\n getId: () => string | null;\n setSetting: (args: { key: string; value: unknown }) => Promise<void>;\n list: () => Promise<Array<{ id: string; metadata?: Record<string, unknown> }>>;\n };\n}\n\n/** One observational-memory role's read/switch surface. */\ninterface OMRole {\n modelId: () => string | undefined;\n threshold: () => number | undefined;\n switchModel: (args: { modelId: string }) => Promise<void>;\n}\n\n/**\n * Session-state fields the OM config routes write. The index signatures mirror\n * `MastraCodeState` so the concrete `Session.state.set(Partial<MastraCodeState>)`\n * stays assignable to this minimal surface (contravariant parameter check).\n */\ninterface OMStateWrites {\n [key: string]: unknown;\n [key: `subagentModelId_${string}`]: string | undefined;\n observationThreshold?: number;\n reflectionThreshold?: number;\n observeAttachments?: 'auto' | boolean;\n}\n\n/** Minimal session surface the OM config routes touch. */\nexport interface OMSession extends PackSession {\n state: {\n get: () => Record<string, unknown> | undefined;\n set: (updates: OMStateWrites) => Promise<void> | void;\n };\n om: { observer: OMRole; reflector: OMRole };\n}\n\n/** Minimal controller surface this module needs (model catalog + modes + sessions). */\ninterface ModelCatalog {\n listAvailableModels: () => Promise<\n Array<{ id?: string; modelName?: string; provider: string; hasApiKey: boolean; apiKeyEnvVar?: string }>\n >;\n listModes?: () => Array<{ id: string; defaultModelId?: string }>;\n getSessionByResource?: (resourceId: string, scope?: string) => Promise<OMSession | undefined>;\n}\n\n/**\n * Build a deduplicated, sorted list of providers from the model catalog,\n * annotated with where each provider's credential currently comes from.\n * Mirrors the TUI's `/api-keys` provider list.\n *\n * When `tenantCredentials` is given (deployed mode), sources reflect the\n * *caller's* tenant rows with user > org precedence and the server-global\n * `authStorage` is ignored; otherwise the local `auth.json` view is reported.\n */\nexport async function listProviders({\n controller,\n authStorage,\n tenantCredentials,\n}: {\n controller: ModelCatalog;\n authStorage?: AuthStorage;\n tenantCredentials?: CredentialRecord[];\n}): Promise<ProviderInfo[]> {\n const models = await controller.listAvailableModels();\n const seen = new Map<string, ProviderInfo>();\n\n for (const model of models) {\n if (seen.has(model.provider)) continue;\n\n const authProviderId = getAuthProviderId(model.provider);\n let source: ProviderInfo['source'] = 'none';\n if (tenantCredentials) {\n const userRec = tenantCredentials.find(r => r.scope === 'user' && r.provider === authProviderId);\n const orgRec = tenantCredentials.find(r => r.scope === 'org' && r.provider === authProviderId);\n if (userRec?.credential.type === 'oauth') {\n source = 'oauth-user';\n } else if (userRec?.credential.type === 'api_key') {\n source = 'stored-user';\n } else if (orgRec?.credential.type === 'api_key') {\n source = 'stored-org';\n }\n } else if (authStorage?.isLoggedIn(authProviderId)) {\n source = 'oauth';\n } else if (authStorage?.hasStoredApiKey(model.provider)) {\n source = 'stored';\n } else if (model.apiKeyEnvVar && process.env[model.apiKeyEnvVar]) {\n source = 'env';\n } else if (model.hasApiKey) {\n source = 'env';\n }\n\n const flowKind = WEB_OAUTH_FLOW_KINDS[model.provider];\n seen.set(model.provider, {\n provider: model.provider,\n envVar: model.apiKeyEnvVar,\n source,\n ...(flowKind ? { oauth: { supported: true as const, modes: [flowKind] } } : {}),\n });\n }\n\n return Array.from(seen.values()).sort((a, b) => a.provider.localeCompare(b.provider));\n}\n\n/** A user-defined OpenAI-compatible provider, with key presence (never the key). */\nexport interface CustomProviderInfo {\n id: string;\n name: string;\n url: string;\n hasApiKey: boolean;\n models: string[];\n}\n\n/** Read the saved custom providers from global settings (keys redacted). */\nexport function listCustomProviders(): CustomProviderInfo[] {\n const settings = loadSettings();\n return settings.customProviders.map(p => ({\n id: getCustomProviderId(p.name),\n name: p.name,\n url: p.url,\n hasApiKey: Boolean(p.apiKey),\n models: p.models,\n }));\n}\n\n/** Validate + coerce a request body into a CustomProviderSetting. */\nfunction parseCustomProviderBody(body: unknown): CustomProviderSetting | { error: string } {\n if (!body || typeof body !== 'object') return { error: 'Invalid JSON body' };\n const b = body as Record<string, unknown>;\n const name = typeof b.name === 'string' ? b.name.trim() : '';\n if (!name) return { error: 'Missing required field: name' };\n const url = typeof b.url === 'string' ? b.url.trim() : '';\n if (!url) return { error: 'Missing required field: url' };\n try {\n const parsed = new URL(url);\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n return { error: 'url must be an http(s) URL' };\n }\n } catch {\n return { error: 'url must be a valid URL' };\n }\n const apiKey = typeof b.apiKey === 'string' && b.apiKey.trim() ? b.apiKey.trim() : undefined;\n const models = Array.isArray(b.models)\n ? b.models.filter((m): m is string => typeof m === 'string' && m.trim().length > 0).map(m => m.trim())\n : [];\n return { name, url, apiKey, models };\n}\n\n// ── Model packs ──────────────────────────────────────────────────────────\n\n/** A model pack as surfaced to the web client, with an `active` flag. */\nexport interface ModelPackInfo extends ModePack {\n custom: boolean;\n active: boolean;\n}\n\n/**\n * Compute which providers the user can reach, mirroring the TUI's\n * `/models-pack` access derivation: OAuth/api-key from the credential store for\n * the named providers, plus any other provider that has a usable key.\n */\nexport async function buildProviderAccess({\n controller,\n authStorage,\n tenantCredentials,\n}: {\n controller: ModelCatalog;\n authStorage?: AuthStorage;\n tenantCredentials?: CredentialRecord[];\n}): Promise<ProviderAccess> {\n const models = await controller.listAvailableModels();\n const hasModelKey = (provider: string) => models.some(m => m.provider === provider && m.hasApiKey);\n const accessLevel = (provider: string): ProviderAccessLevel => {\n const authProviderId = getAuthProviderId(provider);\n if (tenantCredentials) {\n const userRec = tenantCredentials.find(r => r.scope === 'user' && r.provider === authProviderId);\n const orgRec = tenantCredentials.find(r => r.scope === 'org' && r.provider === authProviderId);\n const credential = userRec?.credential ?? orgRec?.credential;\n if (credential?.type === 'oauth') return 'oauth';\n if (credential?.type === 'api_key' && credential.key.trim().length > 0) return 'apikey';\n return false;\n }\n\n const oauthCredential = authStorage?.get(authProviderId);\n if (oauthCredential?.type === 'oauth') return 'oauth';\n if (authStorage?.hasStoredApiKey(provider)) return 'apikey';\n const directCredential = authStorage?.get(provider);\n if (directCredential?.type === 'api_key' && directCredential.key.trim().length > 0) return 'apikey';\n return hasModelKey(provider) ? 'apikey' : false;\n };\n const access: ProviderAccess = {\n anthropic: accessLevel('anthropic'),\n openai: accessLevel('openai'),\n cerebras: accessLevel('cerebras'),\n google: accessLevel('google'),\n deepseek: accessLevel('deepseek'),\n 'github-copilot': accessLevel('github-copilot'),\n };\n const seen = new Set(Object.keys(access));\n for (const m of models) {\n if (!seen.has(m.provider)) {\n access[m.provider] = accessLevel(m.provider);\n seen.add(m.provider);\n }\n }\n return access;\n}\n\nfunction canUseModelProvider(access: ProviderAccess, provider: string): boolean {\n return Boolean(access[provider]);\n}\n\n/**\n * Where a request's custom model packs live. Local mode (no auth adapter)\n * keeps the file-backed `settings.json` store — unchanged TUI behavior. Tenant\n * mode (auth adapter active) uses the `model-packs` factory storage domain,\n * scoped per org. Mirrors `resolveCredentialContext`.\n */\nexport type PackContext =\n { mode: 'local' } | { mode: 'tenant'; storage: ModelPacksStorage; orgId: string; userId: string };\n\n/** The tenant model-packs domain, when registered and ready (fail-soft). */\nasync function getTenantModelPacksStorage(\n modelPacks: ModelPacksStorage | undefined,\n): Promise<ModelPacksStorage | undefined> {\n if (!modelPacks) return undefined;\n try {\n await modelPacks.ensureReady();\n } catch {\n return undefined;\n }\n return modelPacks;\n}\n\n/** Resolve the pack context for a request, or a ready-to-return error response. */\nasync function resolvePackContext({\n c,\n auth,\n modelPacks,\n}: {\n c: Context;\n auth: RouteAuth;\n modelPacks?: ModelPacksStorage;\n}): Promise<PackContext | { response: Response }> {\n await auth.ensureUser(c);\n const tenant = auth.tenant(c);\n if (!tenant) {\n if (auth.enabled()) return { response: c.json({ error: 'unauthorized' }, 401) };\n return { mode: 'local' };\n }\n const storage = await getTenantModelPacksStorage(modelPacks);\n if (!storage) {\n return {\n response: c.json(\n {\n error: 'model_packs_unavailable',\n message: 'Model pack storage is unavailable — the app database is not configured or failed to start.',\n },\n 503,\n ),\n };\n }\n return { mode: 'tenant', storage, orgId: tenantOrgId(tenant), userId: tenant.userId };\n}\n\n/** DB row → the `ModePack` shape the packs list and activation flow consume. */\nfunction recordToModePack(record: ModelPackRecord): ModePack {\n return { id: `custom:${record.id}`, name: record.name, description: 'Saved custom pack', models: record.models };\n}\n\n/**\n * List available model packs (built-in, gated by provider access, plus saved\n * custom packs from the request's pack context). Drops the synthetic\n * \"New Custom\" placeholder — the web client has its own create flow. `active`\n * is set from the given session's thread when a resourceId is supplied.\n */\nexport async function listModelPacks({\n controller,\n authStorage,\n tenantCredentials,\n packContext,\n activePackId,\n}: {\n controller: ModelCatalog;\n authStorage?: AuthStorage;\n tenantCredentials?: CredentialRecord[];\n packContext: PackContext;\n activePackId?: string | null;\n}): Promise<ModelPackInfo[]> {\n const access = await buildProviderAccess({ controller, authStorage, tenantCredentials });\n const packs =\n packContext.mode === 'local'\n ? getAvailableModePacks(access, loadSettings().customModelPacks)\n : [\n ...getAvailableModePacks(access),\n ...(await packContext.storage.list({ orgId: packContext.orgId })).map(recordToModePack),\n ];\n return packs\n .filter(p => p.id !== 'custom') // synthetic \"choose each model\" placeholder\n .map(p => ({\n ...p,\n custom: p.id.startsWith('custom:'),\n active: activePackId != null && p.id === activePackId,\n }));\n}\n\n/** Resolve the active pack id for a session by reading its current thread. */\nasync function resolveActivePackId(session: PackSession | undefined): Promise<string | null> {\n if (!session) return null;\n const threadId = session.thread.getId();\n if (!threadId) return null;\n const thread = (await session.thread.list()).find(t => t.id === threadId);\n const value = thread?.metadata?.[THREAD_ACTIVE_MODEL_PACK_ID_KEY];\n return typeof value === 'string' ? value : null;\n}\n\n/**\n * Apply a pack to a session: seed each mode's default model, switch the current\n * mode's model, set per-subagent models, and tag the thread with the active\n * pack id. Mirrors the TUI `applyPack` orchestration.\n */\nasync function applyPackToSession({\n controller,\n session,\n pack,\n}: {\n controller: ModelCatalog;\n session: PackSession;\n pack: ModePack;\n}): Promise<void> {\n const modes = controller.listModes?.() ?? [];\n const packModels = pack.models as Record<string, string>;\n\n for (const mode of modes) {\n const modelId = packModels[mode.id];\n if (modelId) {\n mode.defaultModelId = modelId;\n await session.thread.setSetting({ key: `modeModelId_${mode.id}`, value: modelId });\n }\n }\n\n const currentModeModel = packModels[session.mode.get()];\n if (currentModeModel) {\n await session.model.switch({ modelId: currentModeModel });\n }\n\n const subagentModeMap: Record<string, string> = { explore: 'fast', plan: 'plan', execute: 'build' };\n for (const [agentType, modeId] of Object.entries(subagentModeMap)) {\n const saModelId = packModels[modeId];\n if (saModelId) {\n await session.subagents.model.set({ modelId: saModelId, agentType });\n }\n }\n\n await session.thread.setSetting({ key: THREAD_ACTIVE_MODEL_PACK_ID_KEY, value: pack.id });\n}\n\n// ── Observational memory ────────────────────────────────────────────────────\n// Mirrors the TUI `/om` command. Observer/reflector model + threshold reads come\n// from the session (state, falling back to omConfig defaults); writes go to both\n// the session (state + thread setting, via the same session methods the TUI uses)\n// and GlobalSettings (settings.json), so the choice survives restarts and stays\n// in sync with the terminal.\n\n/** Default thresholds mirror the TUI `/om` fallbacks. */\nconst DEFAULT_OBSERVATION_THRESHOLD = 30_000;\nconst DEFAULT_REFLECTION_THRESHOLD = 40_000;\n\n/** Read the current OM config from a session. */\nexport interface OMConfigInfo {\n observerModelId: string;\n reflectorModelId: string;\n observationThreshold: number;\n reflectionThreshold: number;\n observeAttachments: 'auto' | boolean;\n}\n\nexport function readOMConfig(session: OMSession): OMConfigInfo {\n const state = session.state.get() ?? {};\n const observeAttachments = state.observeAttachments;\n return {\n observerModelId: session.om.observer.modelId() ?? '',\n reflectorModelId: session.om.reflector.modelId() ?? '',\n observationThreshold: session.om.observer.threshold() ?? DEFAULT_OBSERVATION_THRESHOLD,\n reflectionThreshold: session.om.reflector.threshold() ?? DEFAULT_REFLECTION_THRESHOLD,\n observeAttachments: observeAttachments === true || observeAttachments === false ? observeAttachments : 'auto',\n };\n}\n\n/** Persist an OM threshold to GlobalSettings (settings.json), mirroring `/om`. */\nfunction persistOmThreshold({ role, value }: { role: 'observation' | 'reflection'; value: number }): void {\n const settings = loadSettings();\n if (role === 'observation') settings.models.omObservationThreshold = value;\n else settings.models.omReflectionThreshold = value;\n saveSettings(settings);\n}\n\n/** Persist an OM role model override to GlobalSettings, snapshotting the other role. */\nfunction persistOmRoleOverride({\n role,\n modelId,\n otherRoleCurrentModelId,\n}: {\n role: 'observer' | 'reflector';\n modelId: string;\n otherRoleCurrentModelId: string | null;\n}): void {\n const settings = loadSettings();\n applyOmRoleOverride(settings, role, modelId, otherRoleCurrentModelId);\n saveSettings(settings);\n}\n\n/** Dependencies injected into {@link ConfigRoutes}. */\nexport interface ConfigRoutesDeps extends RouteDependencies {\n controller: ModelCatalog;\n authStorage?: AuthStorage;\n /** Tenant credential domain handle; absent in local (no-DB) mode. */\n modelCredentials?: ModelCredentialsStorage;\n /** Tenant model-packs domain handle; absent in local (no-DB) mode. */\n modelPacks?: ModelPacksStorage;\n /** Notifies the host after tenant credentials change so caches can be dropped. */\n onCredentialsChanged?: (tenant: { orgId: string; userId?: string }) => void;\n}\n\n/**\n * The web config routes as Mastra `apiRoutes`:\n * - `GET /web/config/providers` — list providers + key source\n * - `PUT /web/config/providers/:provider/key` — set/update a provider's API key\n * - `DELETE /web/config/providers/:provider/key` — remove a stored API key\n * - `GET /web/config/models` — list available models (credentialed providers)\n * - `GET /web/config/custom-providers` — list custom OpenAI-compatible providers\n * - `POST /web/config/custom-providers` — create/update a custom provider\n * - `DELETE /web/config/custom-providers/:id` — remove a custom provider\n * - `GET /web/config/om` — read OM models/thresholds/observe-attachments\n * - `PUT /web/config/om/:role/model` — switch observer/reflector model\n * - `PUT /web/config/om/thresholds` — set observation/reflection thresholds\n * - `PUT /web/config/om/observe-attachments` — set observe-attachments (auto/on/off)\n */\nexport class ConfigRoutes extends Route<ConfigRoutesDeps> {\n routes(): ApiRoute[] {\n const options = this.deps;\n const { controller, authStorage, auth } = options;\n const onCredentialsChanged = options.onCredentialsChanged ?? (() => {});\n\n return [\n registerApiRoute('/web/config/providers', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n try {\n // Tenant mode lists the caller's rows and never exposes the\n // server-global auth.json; local mode is unchanged.\n const tenantCredentials = await listTenantCredentialsForRequest({\n c: loose(c),\n auth,\n credentials: options.modelCredentials,\n });\n return c.json({\n providers: await listProviders({\n controller,\n authStorage: tenantCredentials ? undefined : authStorage,\n tenantCredentials,\n }),\n });\n } catch (error) {\n return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);\n }\n },\n }),\n\n registerApiRoute('/web/config/providers/:provider/key', {\n method: 'PUT',\n requiresAuth: false,\n handler: async c => {\n const ctx = await resolveCredentialContext({ c: loose(c), auth, credentials: options.modelCredentials });\n if ('response' in ctx) return ctx.response;\n\n const provider = c.req.param('provider');\n let body: { key?: unknown; envVar?: unknown; scope?: unknown };\n try {\n body = await c.req.json();\n } catch {\n return c.json({ error: 'Invalid JSON body' }, 400);\n }\n const key = typeof body.key === 'string' ? body.key.trim() : '';\n if (!key) return c.json({ error: 'Missing required field: key' }, 400);\n const envVar = typeof body.envVar === 'string' ? body.envVar : undefined;\n const scope = body.scope === 'org' ? 'org' : 'user';\n try {\n if (ctx.mode === 'tenant') {\n if (scope === 'org' && !(await auth.isOrganizationAdmin(loose(c), ctx.orgId))) {\n return c.json({ error: 'organization_admin_required' }, 403);\n }\n const tenant = scope === 'org' ? { orgId: ctx.orgId } : { orgId: ctx.orgId, userId: ctx.userId };\n // envVar is intentionally ignored: tenant credentials are resolved\n // per-request, never written into process.env.\n await ctx.storage.setCredential(tenant, getAuthProviderId(provider), { type: 'api_key', key });\n onCredentialsChanged(tenant);\n const records = await ctx.storage.listCredentials(ctx.orgId, ctx.userId);\n const providers = await listProviders({ controller, tenantCredentials: records });\n return c.json({ ok: true, provider: providers.find(p => p.provider === provider) });\n }\n if (!authStorage) return c.json({ error: 'Credential storage is not available' }, 503);\n // Local mode is single-user: scope is meaningless and ignored.\n authStorage.setStoredApiKey(provider, key, envVar);\n const providers = await listProviders({ controller, authStorage });\n return c.json({ ok: true, provider: providers.find(p => p.provider === provider) });\n } catch (error) {\n return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);\n }\n },\n }),\n\n registerApiRoute('/web/config/providers/:provider/key', {\n method: 'DELETE',\n requiresAuth: false,\n handler: async c => {\n const ctx = await resolveCredentialContext({ c: loose(c), auth, credentials: options.modelCredentials });\n if ('response' in ctx) return ctx.response;\n\n const provider = c.req.param('provider');\n const scope = c.req.query('scope') === 'org' ? 'org' : 'user';\n try {\n if (ctx.mode === 'tenant') {\n if (scope === 'org' && !(await auth.isOrganizationAdmin(loose(c), ctx.orgId))) {\n return c.json({ error: 'organization_admin_required' }, 403);\n }\n const tenant = scope === 'org' ? { orgId: ctx.orgId } : { orgId: ctx.orgId, userId: ctx.userId };\n await ctx.storage.removeCredential(tenant, getAuthProviderId(provider));\n onCredentialsChanged(tenant);\n const records = await ctx.storage.listCredentials(ctx.orgId, ctx.userId);\n const providers = await listProviders({ controller, tenantCredentials: records });\n return c.json({ ok: true, provider: providers.find(p => p.provider === provider) });\n }\n if (!authStorage) return c.json({ error: 'Credential storage is not available' }, 503);\n authStorage.remove(`apikey:${provider}`);\n const providers = await listProviders({ controller, authStorage });\n return c.json({ ok: true, provider: providers.find(p => p.provider === provider) });\n } catch (error) {\n return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);\n }\n },\n }),\n\n // ── Custom providers (OpenAI-compatible endpoints) ──────────────────────\n // Mirrors the TUI's /custom-providers command. Backed by GlobalSettings\n // (settings.json), not session state — these are user-global definitions.\n\n registerApiRoute('/web/config/custom-providers', {\n method: 'GET',\n requiresAuth: false,\n handler: c => {\n try {\n return c.json({ providers: listCustomProviders() });\n } catch (error) {\n return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);\n }\n },\n }),\n\n registerApiRoute('/web/config/custom-providers', {\n method: 'POST',\n requiresAuth: false,\n handler: async c => {\n let body: unknown;\n try {\n body = await c.req.json();\n } catch {\n return c.json({ error: 'Invalid JSON body' }, 400);\n }\n const parsed = parseCustomProviderBody(body);\n if ('error' in parsed) return c.json({ error: parsed.error }, 400);\n // `previousId` lets a rename remove the old entry as well as any name clash.\n const previousId =\n body && typeof body === 'object' && typeof (body as Record<string, unknown>).previousId === 'string'\n ? ((body as Record<string, unknown>).previousId as string)\n : undefined;\n try {\n const settings = loadSettings();\n upsertCustomProviderInSettings(settings, parsed, previousId);\n saveSettings(settings);\n const id = getCustomProviderId(parsed.name);\n return c.json({ ok: true, provider: listCustomProviders().find(p => p.id === id) });\n } catch (error) {\n return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);\n }\n },\n }),\n\n registerApiRoute('/web/config/custom-providers/:id', {\n method: 'DELETE',\n requiresAuth: false,\n handler: c => {\n const id = c.req.param('id');\n try {\n const settings = loadSettings();\n removeCustomProviderFromSettings(settings, id);\n saveSettings(settings);\n return c.json({ ok: true });\n } catch (error) {\n return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);\n }\n },\n }),\n\n // ── Available models ────────────────────────────────────────────────────\n // Session-independent model catalog for settings pickers (Factory default\n // model, pack editors). Only models whose provider has a credential are\n // returned — the same filter the session-scoped hook applies client-side.\n\n registerApiRoute('/web/config/models', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n try {\n const tenantCredentials = await listTenantCredentialsForRequest({\n c: loose(c),\n auth,\n credentials: options.modelCredentials,\n });\n const [models, access] = await Promise.all([\n controller.listAvailableModels(),\n buildProviderAccess({\n controller,\n authStorage: tenantCredentials ? undefined : authStorage,\n tenantCredentials,\n }),\n ]);\n return c.json({\n models: models\n .filter(m => canUseModelProvider(access, m.provider) && typeof m.id === 'string')\n .map(m => ({ id: m.id, provider: m.provider, modelName: m.modelName, hasApiKey: true }))\n .sort((a, b) =>\n a.provider === b.provider ? a.id!.localeCompare(b.id!) : a.provider.localeCompare(b.provider),\n ),\n });\n } catch (error) {\n return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);\n }\n },\n }),\n\n // ── Model packs ─────────────────────────────────────────────────────────\n // Mirrors the TUI's /models-pack command. Custom-pack CRUD is DB-backed in\n // tenant mode (model-packs storage domain, org-scoped) and settings.json in\n // local mode; activation is session-scoped and resolves the session from\n // the controller registry by resourceId.\n\n registerApiRoute('/web/config/model-packs', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const packContext = await resolvePackContext({ c: loose(c), auth, modelPacks: options.modelPacks });\n if ('response' in packContext) return packContext.response;\n const resourceId = c.req.query('resourceId');\n const scope = c.req.query('scope') || undefined;\n try {\n const session = resourceId ? await controller.getSessionByResource?.(resourceId, scope) : undefined;\n const activePackId = await resolveActivePackId(session);\n const tenantCredentials = await listTenantCredentialsForRequest({\n c: loose(c),\n auth,\n credentials: options.modelCredentials,\n });\n return c.json({\n packs: await listModelPacks({\n controller,\n authStorage: tenantCredentials ? undefined : authStorage,\n tenantCredentials,\n packContext,\n activePackId,\n }),\n activePackId,\n });\n } catch (error) {\n return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);\n }\n },\n }),\n\n registerApiRoute('/web/config/model-packs', {\n method: 'POST',\n requiresAuth: false,\n handler: async c => {\n const packContext = await resolvePackContext({ c: loose(c), auth, modelPacks: options.modelPacks });\n if ('response' in packContext) return packContext.response;\n let body: { name?: unknown; models?: unknown };\n try {\n body = await c.req.json();\n } catch {\n return c.json({ error: 'Invalid JSON body' }, 400);\n }\n const name = typeof body.name === 'string' ? body.name.trim() : '';\n if (!name) return c.json({ error: 'Missing required field: name' }, 400);\n const m = (body.models ?? {}) as Record<string, unknown>;\n const build = typeof m.build === 'string' ? m.build.trim() : '';\n const plan = typeof m.plan === 'string' ? m.plan.trim() : '';\n const fast = typeof m.fast === 'string' ? m.fast.trim() : '';\n if (!build || !plan || !fast) {\n return c.json({ error: 'models.build, models.plan and models.fast are required' }, 400);\n }\n try {\n if (packContext.mode === 'tenant') {\n const record = await packContext.storage.upsert({\n orgId: packContext.orgId,\n userId: packContext.userId,\n input: { name, models: { build, plan, fast } },\n });\n return c.json({ ok: true, pack: recordToModePack(record) });\n }\n const settings = loadSettings();\n const entry = { name, models: { build, plan, fast }, createdAt: new Date().toISOString() };\n const idx = settings.customModelPacks.findIndex(p => p.name === name);\n if (idx >= 0) settings.customModelPacks[idx] = entry;\n else settings.customModelPacks.push(entry);\n saveSettings(settings);\n return c.json({ ok: true, pack: { id: `custom:${name}`, name, models: { build, plan, fast } } });\n } catch (error) {\n return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);\n }\n },\n }),\n\n registerApiRoute('/web/config/model-packs/:id', {\n method: 'DELETE',\n requiresAuth: false,\n handler: async c => {\n const packContext = await resolvePackContext({ c: loose(c), auth, modelPacks: options.modelPacks });\n if ('response' in packContext) return packContext.response;\n const id = decodeURIComponent(c.req.param('id'));\n try {\n if (packContext.mode === 'tenant') {\n const recordId = id.startsWith('custom:') ? id.slice('custom:'.length) : id;\n const deleted = await packContext.storage.delete({ orgId: packContext.orgId, id: recordId });\n return deleted ? c.json({ ok: true }) : c.json({ error: `Unknown pack \"${id}\"` }, 404);\n }\n const settings = loadSettings();\n removeCustomPackFromSettings(settings, id);\n saveSettings(settings);\n return c.json({ ok: true });\n } catch (error) {\n return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);\n }\n },\n }),\n\n registerApiRoute('/web/config/model-packs/:id/activate', {\n method: 'POST',\n requiresAuth: false,\n handler: async c => {\n const packContext = await resolvePackContext({ c: loose(c), auth, modelPacks: options.modelPacks });\n if ('response' in packContext) return packContext.response;\n const id = decodeURIComponent(c.req.param('id'));\n let body: { resourceId?: unknown; scope?: unknown };\n try {\n body = await c.req.json();\n } catch {\n return c.json({ error: 'Invalid JSON body' }, 400);\n }\n const resourceId = typeof body.resourceId === 'string' ? body.resourceId : '';\n const scope = typeof body.scope === 'string' && body.scope ? body.scope : undefined;\n if (!resourceId) return c.json({ error: 'Missing required field: resourceId' }, 400);\n try {\n const session = await controller.getSessionByResource?.(resourceId, scope);\n if (!session) return c.json({ error: `No session for resourceId \"${resourceId}\"` }, 404);\n const tenantCredentials = await listTenantCredentialsForRequest({\n c: loose(c),\n auth,\n credentials: options.modelCredentials,\n });\n const packs = await listModelPacks({\n controller,\n authStorage: tenantCredentials ? undefined : authStorage,\n tenantCredentials,\n packContext,\n });\n const pack = packs.find(p => p.id === id);\n if (!pack) return c.json({ error: `Unknown pack \"${id}\"` }, 404);\n await applyPackToSession({ controller, session, pack });\n return c.json({ ok: true, activePackId: pack.id });\n } catch (error) {\n return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);\n }\n },\n }),\n\n // ── Observational memory ──────────────────────────────────────────────────\n // Mirrors the TUI's /om command. All five knobs are session-scoped (resolved\n // from the session, persisted to its state + thread setting) plus written to\n // GlobalSettings so the choice survives restarts and stays in sync with the TUI.\n\n registerApiRoute('/web/config/om', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const resourceId = c.req.query('resourceId');\n const scope = c.req.query('scope') || undefined;\n if (!resourceId) return c.json({ error: 'Missing required query param: resourceId' }, 400);\n try {\n const session = await controller.getSessionByResource?.(resourceId, scope);\n if (!session) return c.json({ error: `No session for resourceId \"${resourceId}\"` }, 404);\n return c.json({ config: readOMConfig(session) });\n } catch (error) {\n return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);\n }\n },\n }),\n\n registerApiRoute('/web/config/om/:role/model', {\n method: 'PUT',\n requiresAuth: false,\n handler: async c => {\n const role = c.req.param('role');\n if (role !== 'observer' && role !== 'reflector') {\n return c.json({ error: `Unknown OM role \"${role}\"` }, 400);\n }\n let body: { resourceId?: unknown; modelId?: unknown; scope?: unknown };\n try {\n body = await c.req.json();\n } catch {\n return c.json({ error: 'Invalid JSON body' }, 400);\n }\n const resourceId = typeof body.resourceId === 'string' ? body.resourceId : '';\n const scope = typeof body.scope === 'string' && body.scope ? body.scope : undefined;\n const modelId = typeof body.modelId === 'string' ? body.modelId.trim() : '';\n if (!resourceId) return c.json({ error: 'Missing required field: resourceId' }, 400);\n if (!modelId) return c.json({ error: 'Missing required field: modelId' }, 400);\n try {\n const session = await controller.getSessionByResource?.(resourceId, scope);\n if (!session) return c.json({ error: `No session for resourceId \"${resourceId}\"` }, 404);\n const otherRole = role === 'observer' ? session.om.reflector : session.om.observer;\n const otherRoleCurrentModelId = otherRole.modelId() ?? null;\n await session.om[role].switchModel({ modelId });\n persistOmRoleOverride({ role, modelId, otherRoleCurrentModelId });\n return c.json({ ok: true, config: readOMConfig(session) });\n } catch (error) {\n return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);\n }\n },\n }),\n\n registerApiRoute('/web/config/om/thresholds', {\n method: 'PUT',\n requiresAuth: false,\n handler: async c => {\n let body: {\n resourceId?: unknown;\n observationThreshold?: unknown;\n reflectionThreshold?: unknown;\n scope?: unknown;\n };\n try {\n body = await c.req.json();\n } catch {\n return c.json({ error: 'Invalid JSON body' }, 400);\n }\n const resourceId = typeof body.resourceId === 'string' ? body.resourceId : '';\n const scope = typeof body.scope === 'string' && body.scope ? body.scope : undefined;\n if (!resourceId) return c.json({ error: 'Missing required field: resourceId' }, 400);\n const observation =\n typeof body.observationThreshold === 'number' && body.observationThreshold > 0\n ? Math.round(body.observationThreshold)\n : undefined;\n const reflection =\n typeof body.reflectionThreshold === 'number' && body.reflectionThreshold > 0\n ? Math.round(body.reflectionThreshold)\n : undefined;\n if (observation === undefined && reflection === undefined) {\n return c.json({ error: 'Provide observationThreshold and/or reflectionThreshold (positive numbers)' }, 400);\n }\n try {\n const session = await controller.getSessionByResource?.(resourceId, scope);\n if (!session) return c.json({ error: `No session for resourceId \"${resourceId}\"` }, 404);\n if (observation !== undefined) {\n await session.state.set({ observationThreshold: observation });\n await session.thread.setSetting({ key: 'observationThreshold', value: observation });\n persistOmThreshold({ role: 'observation', value: observation });\n }\n if (reflection !== undefined) {\n await session.state.set({ reflectionThreshold: reflection });\n await session.thread.setSetting({ key: 'reflectionThreshold', value: reflection });\n persistOmThreshold({ role: 'reflection', value: reflection });\n }\n return c.json({ ok: true, config: readOMConfig(session) });\n } catch (error) {\n return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);\n }\n },\n }),\n\n registerApiRoute('/web/config/om/observe-attachments', {\n method: 'PUT',\n requiresAuth: false,\n handler: async c => {\n let body: { resourceId?: unknown; value?: unknown; scope?: unknown };\n try {\n body = await c.req.json();\n } catch {\n return c.json({ error: 'Invalid JSON body' }, 400);\n }\n const resourceId = typeof body.resourceId === 'string' ? body.resourceId : '';\n const scope = typeof body.scope === 'string' && body.scope ? body.scope : undefined;\n if (!resourceId) return c.json({ error: 'Missing required field: resourceId' }, 400);\n const raw = body.value;\n const value: 'auto' | boolean = raw === 'auto' || raw === true || raw === false ? raw : 'auto';\n if (raw !== 'auto' && raw !== true && raw !== false) {\n return c.json({ error: \"value must be 'auto', true, or false\" }, 400);\n }\n try {\n const session = await controller.getSessionByResource?.(resourceId, scope);\n if (!session) return c.json({ error: `No session for resourceId \"${resourceId}\"` }, 404);\n await session.state.set({ observeAttachments: value });\n await session.thread.setSetting({ key: 'observeAttachments', value });\n persistOmObserveAttachments(value);\n return c.json({ ok: true, config: readOMConfig(session) });\n } catch (error) {\n return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);\n }\n },\n }),\n ];\n }\n}\n","/**\n * Per-request model-provider credential context.\n *\n * The provider config/OAuth routes serve two deployment shapes:\n *\n * - **Local** (no web auth adapter): credentials live in the server-global\n * file-backed `AuthStorage` (`auth.json`) — unchanged TUI behavior.\n * - **Tenant** (auth adapter active): credentials live in the\n * `model-credentials` factory storage domain, scoped per `(orgId, userId)`\n * with optional org-wide rows (`userId` absent).\n *\n * `resolveCredentialContext` decides which shape a request is in: an\n * authenticated user always gets the tenant path; an unauthenticated request\n * is rejected when auth is enabled and falls back to local otherwise.\n */\n\nimport type { Context } from 'hono';\n\nimport type {\n CredentialRecord,\n LoginSessionKind,\n ModelCredentialsStorage,\n} from '../storage/domains/credentials/base.js';\nimport type { RouteAuth } from './route.js';\n\n/**\n * OAuth credentials are stored under the auth provider id, which differs from\n * the catalog provider id for OpenAI (stored as `openai-codex`). Tenant rows\n * use the same keying so one lookup serves both OAuth tokens and API keys.\n */\nexport function getAuthProviderId(provider: string): string {\n return provider === 'openai' ? 'openai-codex' : provider;\n}\n\n/**\n * Providers that support a browser-driven OAuth sign-in flow, keyed by\n * *catalog* provider id (what the routes and settings UI use). All flows need\n * no inbound connection to the server: Anthropic uses paste-code PKCE, the\n * rest use RFC 8628-style device codes.\n */\nexport const WEB_OAUTH_FLOW_KINDS: Readonly<Record<string, LoginSessionKind>> = {\n anthropic: 'paste-code',\n openai: 'device-code',\n 'github-copilot': 'device-code',\n xai: 'device-code',\n};\n\nexport type CredentialContext =\n { mode: 'local' } | { mode: 'tenant'; storage: ModelCredentialsStorage; orgId: string; userId: string };\n\n/**\n * The tenant credentials domain, when registered and ready. `undefined` means\n * the factory never ran (no handle threaded in) or the domain's init failed\n * (fail-soft — callers report the feature unavailable instead of crashing).\n */\nexport async function getTenantCredentialsStorage(\n credentials: ModelCredentialsStorage | undefined,\n): Promise<ModelCredentialsStorage | undefined> {\n if (!credentials) return undefined;\n try {\n await credentials.ensureReady();\n } catch {\n return undefined;\n }\n return credentials;\n}\n\n/**\n * Org key for tenant rows. Personal accounts get an org bootstrapped by the\n * auth adapter (`ensureOrg`); if one still isn't present, scope rows under a\n * per-user synthetic org so credentials never become server-global.\n */\nexport function tenantOrgId(tenant: { orgId?: string; userId: string }): string {\n return tenant.orgId ?? `user:${tenant.userId}`;\n}\n\n/**\n * Resolve the credential context for a request, or a ready-to-return error\n * response. Mutating credential routes call this and hard-fail (401/503)\n * when the tenant path is required but unavailable.\n */\nexport async function resolveCredentialContext({\n c,\n auth,\n credentials,\n}: {\n c: Context;\n auth: RouteAuth;\n /** Tenant credential domain handle; absent in local (no-DB) mode. */\n credentials?: ModelCredentialsStorage;\n}): Promise<CredentialContext | { response: Response }> {\n await auth.ensureUser(c);\n const tenant = auth.tenant(c);\n if (!tenant) {\n // When an auth provider is active, credential operations always require a\n // signed-in caller — otherwise one anonymous request could write keys for\n // everyone. Without a provider this is a single-user local server.\n if (auth.enabled()) return { response: c.json({ error: 'unauthorized' }, 401) };\n return { mode: 'local' };\n }\n const storage = await getTenantCredentialsStorage(credentials);\n if (!storage) {\n return {\n response: c.json(\n {\n error: 'credentials_unavailable',\n message: 'Tenant credential storage is unavailable — the app database is not configured or failed to start.',\n },\n 503,\n ),\n };\n }\n return { mode: 'tenant', storage, orgId: tenantOrgId(tenant), userId: tenant.userId };\n}\n\n/**\n * List the caller's tenant credential records for provider listing, or\n * `undefined` in local mode (caller should fall back to `AuthStorage`).\n * Read-only and degradation-friendly: an authenticated caller with the domain\n * unavailable gets an empty list (sources show `env`/`none`) instead of a 503\n * so the settings page still renders.\n */\nexport async function listTenantCredentialsForRequest({\n c,\n auth,\n credentials,\n}: {\n c: Context;\n auth: RouteAuth;\n /** Tenant credential domain handle; absent in local (no-DB) mode. */\n credentials?: ModelCredentialsStorage;\n}): Promise<CredentialRecord[] | undefined> {\n await auth.ensureUser(c);\n const tenant = auth.tenant(c);\n if (!tenant) return auth.enabled() ? [] : undefined;\n const storage = await getTenantCredentialsStorage(credentials);\n if (!storage) return [];\n return storage.listCredentials(tenantOrgId(tenant), tenant.userId);\n}\n","/**\n * Base class for factory route modules.\n *\n * Route modules build Mastra `apiRoutes` from injected dependencies instead of\n * reaching into host globals. The host server (e.g. `mastracode/web`) supplies\n * the auth seam and storage domain handles at construction time, so the routes\n * stay portable and testable with fakes.\n */\n\nimport type { ApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\n/**\n * The auth surface factory routes need, implemented by the host server.\n *\n * Local (no-auth) deployments implement this with a stub where `enabled()`\n * returns `false` and `tenant()` returns `undefined` — routes then take their\n * single-user local paths.\n */\nexport interface RouteAuth {\n /** Whether an auth provider is active (tenant mode). */\n enabled(): boolean;\n /**\n * Resolve (and cache) the signed-in user for the request, if any. Must be\n * called before `tenant()` so the request context is populated.\n */\n ensureUser(c: Context): Promise<unknown>;\n /** Tenant identity for the request, when signed in. */\n tenant(c: Context): { orgId?: string; userId: string } | undefined;\n /** Fail-closed check that the caller administers the given organization. */\n isOrganizationAdmin(c: Context, organizationId: string): Promise<boolean>;\n}\n\n/** Dependencies shared by every factory route module. */\nexport interface RouteDependencies {\n auth: RouteAuth;\n}\n\n/**\n * A route module: constructed once at boot with its dependencies, then asked\n * for the `ApiRoute[]` it serves.\n */\nexport abstract class Route<TDeps extends RouteDependencies = RouteDependencies> {\n protected readonly deps: TDeps;\n\n constructor(deps: TDeps) {\n this.deps = deps;\n }\n\n /** Build the Mastra `apiRoutes` served by this module. */\n abstract routes(): ApiRoute[];\n}\n"],"mappings":"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","names":["providers"]}
@@ -0,0 +1,105 @@
1
+ import type { ApiRoute } from '@mastra/core/server';
2
+ /**
3
+ * Server-side directory browser for the web project picker.
4
+ *
5
+ * The browser cannot read absolute filesystem paths (the File System Access API
6
+ * only exposes a directory *name*), so the picker must ask the server — which
7
+ * does have filesystem access — to enumerate directories. The result is real
8
+ * absolute paths the user can select without typing.
9
+ *
10
+ * All access is confined to a configured `root` (default: the user's home
11
+ * directory). Requests that try to escape the root via `..` or symlinks are
12
+ * clamped back to the root.
13
+ */
14
+ export interface DirectoryEntry {
15
+ name: string;
16
+ /** Absolute path to the entry. */
17
+ path: string;
18
+ }
19
+ export interface DirectoryListing {
20
+ /** The allowed root; clients cannot browse above this. */
21
+ root: string;
22
+ /** The absolute path that was listed. */
23
+ path: string;
24
+ /** Parent directory path, or null when `path` is the root. */
25
+ parent: string | null;
26
+ /** Subdirectories of `path` (directories only, sorted, hidden excluded). */
27
+ entries: DirectoryEntry[];
28
+ }
29
+ export interface WorkspaceRenderedEntry {
30
+ name: string;
31
+ /** Path relative to the configured rendered root. */
32
+ path: string;
33
+ type: 'file' | 'directory';
34
+ size: number;
35
+ updatedAt: string;
36
+ }
37
+ export interface WorkspaceRenderedListing {
38
+ /** The confined workspace/project root. */
39
+ workspacePath: string;
40
+ /** Configured workspace-relative rendered root, e.g. `.artifacts`. */
41
+ root: string;
42
+ /** The confined absolute path for the rendered root. */
43
+ rootPath: string;
44
+ entries: WorkspaceRenderedEntry[];
45
+ }
46
+ export interface WorkspaceFile {
47
+ /** The confined workspace/project root. */
48
+ workspacePath: string;
49
+ /** Workspace-relative file path. */
50
+ path: string;
51
+ name: string;
52
+ size: number;
53
+ updatedAt: string;
54
+ contentType: 'text' | 'unsupported';
55
+ content?: string;
56
+ truncated?: boolean;
57
+ }
58
+ export type ArtifactEntry = WorkspaceRenderedEntry;
59
+ export interface ArtifactListing {
60
+ /** The confined workspace/project root. */
61
+ rootPath: string;
62
+ /** The workspace artifact directory. */
63
+ artifactsPath: string;
64
+ entries: ArtifactEntry[];
65
+ }
66
+ /** Resolve the browsable root, defaulting to the user's home directory. */
67
+ export declare function resolveFsRoot(root?: string): string;
68
+ /**
69
+ * List the directories inside `requestedPath`, confined to `root`. An absent or
70
+ * out-of-root path is clamped to the root, so the worst a malicious client can
71
+ * do is browse within the allowed root.
72
+ */
73
+ export declare function listDirectory(root: string, requestedPath?: string): Promise<DirectoryListing>;
74
+ export declare function listWorkspaceRenderedPath(root: string, workspacePath: string, renderedRoot: string): Promise<WorkspaceRenderedListing>;
75
+ export declare function readWorkspaceFile(root: string, workspacePath: string, path: string): Promise<WorkspaceFile>;
76
+ export declare function listArtifacts(root: string, workspacePath: string): Promise<ArtifactListing>;
77
+ export interface ResolvedCodebase {
78
+ /**
79
+ * The resourceId the TUI would use for this path — derived identically so a
80
+ * project opened in the terminal and in the web app resolve to the SAME
81
+ * session (and therefore the same threads).
82
+ */
83
+ resourceId: string;
84
+ name: string;
85
+ rootPath: string;
86
+ gitUrl?: string;
87
+ gitBranch?: string;
88
+ }
89
+ /**
90
+ * Resolve a project path to the same resourceId the TUI uses. Mirrors
91
+ * `createMastraCode`: detect the project, then apply any resourceId override
92
+ * (MASTRA_RESOURCE_ID env var or `.mastracode/database.json`). This is the
93
+ * shared continuity point — start in the TUI, continue on the web, same path
94
+ * → same resourceId → same session.
95
+ */
96
+ export declare function resolveCodebase(projectPath: string): ResolvedCodebase;
97
+ /**
98
+ * Build the web filesystem routes as Mastra `apiRoutes`:
99
+ * - `GET /web/fs/list?path=...` — browse directories (confined to root)
100
+ * - `GET /web/codebase/resolve?path=...` — TUI-compatible codebase resourceId
101
+ */
102
+ export declare function buildFsRoutes(options?: {
103
+ root?: string;
104
+ }): ApiRoute[];
105
+ //# sourceMappingURL=fs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../src/routes/fs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,294 @@
1
+ // src/routes/fs.ts
2
+ import { lstat, open, readdir, realpath, stat } from "fs/promises";
3
+ import { homedir } from "os";
4
+ import { isAbsolute, join, resolve, sep } from "path";
5
+ import { detectProject, getResourceIdOverride } from "@mastra/code-sdk/utils/project";
6
+ import { registerApiRoute } from "@mastra/core/server";
7
+ var MAX_TEXT_FILE_BYTES = 512 * 1024;
8
+ var TEXT_DECODER = new TextDecoder("utf-8", { fatal: true });
9
+ var APPROVED_RENDERED_ROOTS = /* @__PURE__ */ new Set([".artifacts"]);
10
+ function resolveFsRoot(root) {
11
+ return resolve(root && root.trim() ? root : homedir());
12
+ }
13
+ function isWithinRoot(candidate, root) {
14
+ if (candidate === root) return true;
15
+ const rootWithSep = root.endsWith(sep) ? root : root + sep;
16
+ return candidate.startsWith(rootWithSep);
17
+ }
18
+ async function realOrResolved(path) {
19
+ try {
20
+ return await realpath(path);
21
+ } catch {
22
+ return path;
23
+ }
24
+ }
25
+ async function realPathWithinRoot(candidate, root) {
26
+ try {
27
+ const real = await realpath(candidate);
28
+ return isWithinRoot(real, root) ? real : null;
29
+ } catch {
30
+ return null;
31
+ }
32
+ }
33
+ function assertRelativePath(path, label) {
34
+ const trimmed = path.trim();
35
+ if (!trimmed) throw new Error(`Missing required query param: ${label}`);
36
+ if (isAbsolute(trimmed)) throw new Error(`${label} must be relative`);
37
+ if (trimmed.split(/[\\/]+/).includes("..")) throw new Error(`${label} escapes workspace`);
38
+ const normalized = resolve("/", trimmed).slice(1);
39
+ if (!normalized || normalized === ".." || normalized.startsWith(`..${sep}`))
40
+ throw new Error(`${label} escapes workspace`);
41
+ return normalized;
42
+ }
43
+ function assertApprovedRenderedRoot(renderedRoot) {
44
+ const safeRoot = assertRelativePath(renderedRoot, "root");
45
+ if (!APPROVED_RENDERED_ROOTS.has(safeRoot)) throw new Error("Root is not approved for rendered workspace access");
46
+ return safeRoot;
47
+ }
48
+ async function confinedWorkspacePath(root, workspacePath) {
49
+ const resolvedRoot = await realOrResolved(resolveFsRoot(root));
50
+ const candidate = isAbsolute(workspacePath) ? resolve(workspacePath) : resolve(resolvedRoot, workspacePath);
51
+ const workspace = await realPathWithinRoot(candidate, resolvedRoot);
52
+ if (!workspace) throw new Error("Path is outside the browsable root");
53
+ return { resolvedRoot, workspace };
54
+ }
55
+ async function confinedWorkspaceRelativePath(root, workspacePath, relativePath) {
56
+ const safeRelativePath = assertRelativePath(relativePath, "path");
57
+ const { workspace } = await confinedWorkspacePath(root, workspacePath);
58
+ const candidate = resolve(workspace, safeRelativePath);
59
+ if (!isWithinRoot(candidate, workspace)) throw new Error("Path escapes workspace");
60
+ const confinedPath = await realPathWithinRoot(candidate, workspace);
61
+ if (!confinedPath) throw new Error("Path is outside the workspace");
62
+ return { workspace, path: confinedPath, relativePath: safeRelativePath };
63
+ }
64
+ async function listDirectory(root, requestedPath) {
65
+ const resolvedRoot = await realOrResolved(resolveFsRoot(root));
66
+ let target = resolvedRoot;
67
+ if (requestedPath && requestedPath.trim()) {
68
+ const candidate = isAbsolute(requestedPath) ? resolve(requestedPath) : resolve(resolvedRoot, requestedPath);
69
+ target = await realPathWithinRoot(candidate, resolvedRoot) ?? resolvedRoot;
70
+ }
71
+ try {
72
+ const info = await stat(target);
73
+ if (!info.isDirectory()) target = resolvedRoot;
74
+ } catch {
75
+ target = resolvedRoot;
76
+ }
77
+ const dirents = await readdir(target, { withFileTypes: true });
78
+ const entries = [];
79
+ for (const dirent of dirents) {
80
+ if (dirent.name.startsWith(".")) continue;
81
+ const entryPath = join(target, dirent.name);
82
+ let isDir = dirent.isDirectory();
83
+ if (dirent.isSymbolicLink()) {
84
+ const real = await realPathWithinRoot(entryPath, resolvedRoot);
85
+ isDir = real ? (await stat(real).catch(() => null))?.isDirectory() === true : false;
86
+ }
87
+ if (isDir) entries.push({ name: dirent.name, path: entryPath });
88
+ }
89
+ entries.sort((a, b) => a.name.localeCompare(b.name));
90
+ const parent = target === resolvedRoot ? null : resolve(target, "..");
91
+ return { root: resolvedRoot, path: target, parent, entries };
92
+ }
93
+ async function listRenderedEntries(rootPath, currentPath = rootPath) {
94
+ const dirents = await readdir(currentPath, { withFileTypes: true });
95
+ const entries = [];
96
+ for (const dirent of dirents) {
97
+ const entryPath = join(currentPath, dirent.name);
98
+ const info = await lstat(entryPath);
99
+ const relativePath = entryPath.slice(rootPath.length + 1);
100
+ if (info.isDirectory()) {
101
+ entries.push({
102
+ name: dirent.name,
103
+ path: relativePath,
104
+ type: "directory",
105
+ size: info.size,
106
+ updatedAt: info.mtime.toISOString()
107
+ });
108
+ entries.push(...await listRenderedEntries(rootPath, entryPath));
109
+ continue;
110
+ }
111
+ if (info.isFile()) {
112
+ entries.push({
113
+ name: dirent.name,
114
+ path: relativePath,
115
+ type: "file",
116
+ size: info.size,
117
+ updatedAt: info.mtime.toISOString()
118
+ });
119
+ }
120
+ }
121
+ return entries.sort((a, b) => a.path.localeCompare(b.path));
122
+ }
123
+ async function listWorkspaceRenderedPath(root, workspacePath, renderedRoot) {
124
+ const safeRoot = assertApprovedRenderedRoot(renderedRoot);
125
+ const { workspace } = await confinedWorkspacePath(root, workspacePath);
126
+ const renderedPath = resolve(workspace, safeRoot);
127
+ if (!isWithinRoot(renderedPath, workspace)) throw new Error("Root escapes workspace");
128
+ const confinedRootPath = await realPathWithinRoot(renderedPath, workspace);
129
+ if (!confinedRootPath) return { workspacePath: workspace, root: safeRoot, rootPath: renderedPath, entries: [] };
130
+ const info = await stat(confinedRootPath);
131
+ if (!info.isDirectory()) return { workspacePath: workspace, root: safeRoot, rootPath: confinedRootPath, entries: [] };
132
+ return {
133
+ workspacePath: workspace,
134
+ root: safeRoot,
135
+ rootPath: confinedRootPath,
136
+ entries: await listRenderedEntries(confinedRootPath)
137
+ };
138
+ }
139
+ async function readWorkspaceFile(root, workspacePath, path) {
140
+ const safePath = assertRelativePath(path, "path");
141
+ const relativeRoot = safePath.split("/")[0] ?? "";
142
+ assertApprovedRenderedRoot(relativeRoot);
143
+ const {
144
+ workspace,
145
+ path: confinedPath,
146
+ relativePath
147
+ } = await confinedWorkspaceRelativePath(root, workspacePath, path);
148
+ const info = await lstat(confinedPath);
149
+ if (info.isDirectory()) throw new Error("Path is a directory");
150
+ if (!info.isFile()) throw new Error("Unsupported file type");
151
+ const bytesToRead = Math.min(info.size, MAX_TEXT_FILE_BYTES);
152
+ const contentBuffer = Buffer.alloc(bytesToRead);
153
+ const handle = await open(confinedPath, "r");
154
+ try {
155
+ await handle.read(contentBuffer, 0, bytesToRead, 0);
156
+ } finally {
157
+ await handle.close();
158
+ }
159
+ try {
160
+ const content = TEXT_DECODER.decode(contentBuffer);
161
+ return {
162
+ workspacePath: workspace,
163
+ path: relativePath,
164
+ name: relativePath.split("/").pop() ?? relativePath,
165
+ size: info.size,
166
+ updatedAt: info.mtime.toISOString(),
167
+ contentType: "text",
168
+ content,
169
+ truncated: info.size > MAX_TEXT_FILE_BYTES
170
+ };
171
+ } catch {
172
+ return {
173
+ workspacePath: workspace,
174
+ path: relativePath,
175
+ name: relativePath.split("/").pop() ?? relativePath,
176
+ size: info.size,
177
+ updatedAt: info.mtime.toISOString(),
178
+ contentType: "unsupported"
179
+ };
180
+ }
181
+ }
182
+ async function listArtifacts(root, workspacePath) {
183
+ const listing = await listWorkspaceRenderedPath(root, workspacePath, ".artifacts");
184
+ return {
185
+ rootPath: listing.workspacePath,
186
+ artifactsPath: listing.rootPath,
187
+ entries: listing.entries
188
+ };
189
+ }
190
+ function resolveCodebase(projectPath) {
191
+ const info = detectProject(projectPath);
192
+ const override = getResourceIdOverride(info.rootPath);
193
+ return {
194
+ resourceId: override ?? info.resourceId,
195
+ name: info.name,
196
+ rootPath: info.rootPath,
197
+ gitUrl: info.gitUrl,
198
+ gitBranch: info.gitBranch
199
+ };
200
+ }
201
+ function buildFsRoutes(options = {}) {
202
+ const root = resolveFsRoot(options.root);
203
+ return [
204
+ registerApiRoute("/web/fs/list", {
205
+ method: "GET",
206
+ requiresAuth: false,
207
+ handler: async (c) => {
208
+ const path = c.req.query("path");
209
+ try {
210
+ const listing = await listDirectory(root, path);
211
+ return c.json(listing);
212
+ } catch (error) {
213
+ const message = error instanceof Error ? error.message : String(error);
214
+ return c.json({ error: message }, 500);
215
+ }
216
+ }
217
+ }),
218
+ registerApiRoute("/web/artifacts/list", {
219
+ method: "GET",
220
+ requiresAuth: false,
221
+ handler: async (c) => {
222
+ const path = c.req.query("path");
223
+ if (!path) return c.json({ error: "Missing required query param: path" }, 400);
224
+ try {
225
+ return c.json(await listArtifacts(root, path));
226
+ } catch (error) {
227
+ const message = error instanceof Error ? error.message : String(error);
228
+ const status = message === "Path is outside the browsable root" ? 403 : 500;
229
+ return c.json({ error: message }, status);
230
+ }
231
+ }
232
+ }),
233
+ registerApiRoute("/web/workspace/rendered/list", {
234
+ method: "GET",
235
+ requiresAuth: false,
236
+ handler: async (c) => {
237
+ const workspacePath = c.req.query("workspacePath");
238
+ const renderedRoot = c.req.query("root");
239
+ if (!workspacePath) return c.json({ error: "Missing required query param: workspacePath" }, 400);
240
+ if (!renderedRoot) return c.json({ error: "Missing required query param: root" }, 400);
241
+ try {
242
+ return c.json(await listWorkspaceRenderedPath(root, workspacePath, renderedRoot));
243
+ } catch (error) {
244
+ const message = error instanceof Error ? error.message : String(error);
245
+ const status = message.includes("outside") || message.includes("relative") || message.includes("escapes") || message.includes("not approved") ? 403 : 500;
246
+ return c.json({ error: message }, status);
247
+ }
248
+ }
249
+ }),
250
+ registerApiRoute("/web/workspace/file", {
251
+ method: "GET",
252
+ requiresAuth: false,
253
+ handler: async (c) => {
254
+ const workspacePath = c.req.query("workspacePath");
255
+ const path = c.req.query("path");
256
+ if (!workspacePath) return c.json({ error: "Missing required query param: workspacePath" }, 400);
257
+ if (!path) return c.json({ error: "Missing required query param: path" }, 400);
258
+ try {
259
+ return c.json(await readWorkspaceFile(root, workspacePath, path));
260
+ } catch (error) {
261
+ const message = error instanceof Error ? error.message : String(error);
262
+ const status = message.includes("outside") || message.includes("relative") || message.includes("escapes") || message.includes("not approved") ? 403 : message.includes("directory") ? 400 : 500;
263
+ return c.json({ error: message }, status);
264
+ }
265
+ }
266
+ }),
267
+ registerApiRoute("/web/codebase/resolve", {
268
+ method: "GET",
269
+ requiresAuth: false,
270
+ handler: async (c) => {
271
+ const path = c.req.query("path");
272
+ if (!path) return c.json({ error: "Missing required query param: path" }, 400);
273
+ const confined = await realPathWithinRoot(isAbsolute(path) ? resolve(path) : resolve(root, path), root);
274
+ if (!confined) return c.json({ error: "Path is outside the browsable root" }, 403);
275
+ try {
276
+ return c.json(resolveCodebase(confined));
277
+ } catch (error) {
278
+ const message = error instanceof Error ? error.message : String(error);
279
+ return c.json({ error: message }, 500);
280
+ }
281
+ }
282
+ })
283
+ ];
284
+ }
285
+ export {
286
+ buildFsRoutes,
287
+ listArtifacts,
288
+ listDirectory,
289
+ listWorkspaceRenderedPath,
290
+ readWorkspaceFile,
291
+ resolveCodebase,
292
+ resolveFsRoot
293
+ };
294
+ //# sourceMappingURL=fs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/routes/fs.ts"],"sourcesContent":["import { lstat, open, readdir, realpath, stat } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { isAbsolute, join, resolve, sep } from 'node:path';\n\nimport { detectProject, getResourceIdOverride } from '@mastra/code-sdk/utils/project';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { ApiRoute } from '@mastra/core/server';\n\n/**\n * Server-side directory browser for the web project picker.\n *\n * The browser cannot read absolute filesystem paths (the File System Access API\n * only exposes a directory *name*), so the picker must ask the server — which\n * does have filesystem access — to enumerate directories. The result is real\n * absolute paths the user can select without typing.\n *\n * All access is confined to a configured `root` (default: the user's home\n * directory). Requests that try to escape the root via `..` or symlinks are\n * clamped back to the root.\n */\n\nexport interface DirectoryEntry {\n name: string;\n /** Absolute path to the entry. */\n path: string;\n}\n\nexport interface DirectoryListing {\n /** The allowed root; clients cannot browse above this. */\n root: string;\n /** The absolute path that was listed. */\n path: string;\n /** Parent directory path, or null when `path` is the root. */\n parent: string | null;\n /** Subdirectories of `path` (directories only, sorted, hidden excluded). */\n entries: DirectoryEntry[];\n}\n\nexport interface WorkspaceRenderedEntry {\n name: string;\n /** Path relative to the configured rendered root. */\n path: string;\n type: 'file' | 'directory';\n size: number;\n updatedAt: string;\n}\n\nexport interface WorkspaceRenderedListing {\n /** The confined workspace/project root. */\n workspacePath: string;\n /** Configured workspace-relative rendered root, e.g. `.artifacts`. */\n root: string;\n /** The confined absolute path for the rendered root. */\n rootPath: string;\n entries: WorkspaceRenderedEntry[];\n}\n\nexport interface WorkspaceFile {\n /** The confined workspace/project root. */\n workspacePath: string;\n /** Workspace-relative file path. */\n path: string;\n name: string;\n size: number;\n updatedAt: string;\n contentType: 'text' | 'unsupported';\n content?: string;\n truncated?: boolean;\n}\n\nexport type ArtifactEntry = WorkspaceRenderedEntry;\n\nexport interface ArtifactListing {\n /** The confined workspace/project root. */\n rootPath: string;\n /** The workspace artifact directory. */\n artifactsPath: string;\n entries: ArtifactEntry[];\n}\n\nconst MAX_TEXT_FILE_BYTES = 512 * 1024;\nconst TEXT_DECODER = new TextDecoder('utf-8', { fatal: true });\nconst APPROVED_RENDERED_ROOTS = new Set(['.artifacts']);\n\n/** Resolve the browsable root, defaulting to the user's home directory. */\nexport function resolveFsRoot(root?: string): string {\n return resolve(root && root.trim() ? root : homedir());\n}\n\n/** True when `candidate` is `root` or nested under it. */\nfunction isWithinRoot(candidate: string, root: string): boolean {\n if (candidate === root) return true;\n const rootWithSep = root.endsWith(sep) ? root : root + sep;\n return candidate.startsWith(rootWithSep);\n}\n\nasync function realOrResolved(path: string): Promise<string> {\n try {\n return await realpath(path);\n } catch {\n return path;\n }\n}\n\n/**\n * Resolve a path's real location (following symlinks) and confirm it stays\n * within `root`. Returns the real path when confined, or `null` when it escapes\n * the root or does not exist. Used so a symlink inside the root that points\n * outside it cannot be browsed or selected.\n */\nasync function realPathWithinRoot(candidate: string, root: string): Promise<string | null> {\n try {\n const real = await realpath(candidate);\n return isWithinRoot(real, root) ? real : null;\n } catch {\n return null;\n }\n}\n\nfunction assertRelativePath(path: string, label: string): string {\n const trimmed = path.trim();\n if (!trimmed) throw new Error(`Missing required query param: ${label}`);\n if (isAbsolute(trimmed)) throw new Error(`${label} must be relative`);\n if (trimmed.split(/[\\\\/]+/).includes('..')) throw new Error(`${label} escapes workspace`);\n const normalized = resolve('/', trimmed).slice(1);\n if (!normalized || normalized === '..' || normalized.startsWith(`..${sep}`))\n throw new Error(`${label} escapes workspace`);\n return normalized;\n}\n\nfunction assertApprovedRenderedRoot(renderedRoot: string): string {\n const safeRoot = assertRelativePath(renderedRoot, 'root');\n if (!APPROVED_RENDERED_ROOTS.has(safeRoot)) throw new Error('Root is not approved for rendered workspace access');\n return safeRoot;\n}\n\nasync function confinedWorkspacePath(\n root: string,\n workspacePath: string,\n): Promise<{ resolvedRoot: string; workspace: string }> {\n const resolvedRoot = await realOrResolved(resolveFsRoot(root));\n const candidate = isAbsolute(workspacePath) ? resolve(workspacePath) : resolve(resolvedRoot, workspacePath);\n const workspace = await realPathWithinRoot(candidate, resolvedRoot);\n if (!workspace) throw new Error('Path is outside the browsable root');\n return { resolvedRoot, workspace };\n}\n\nasync function confinedWorkspaceRelativePath(\n root: string,\n workspacePath: string,\n relativePath: string,\n): Promise<{ workspace: string; path: string; relativePath: string }> {\n const safeRelativePath = assertRelativePath(relativePath, 'path');\n const { workspace } = await confinedWorkspacePath(root, workspacePath);\n const candidate = resolve(workspace, safeRelativePath);\n if (!isWithinRoot(candidate, workspace)) throw new Error('Path escapes workspace');\n const confinedPath = await realPathWithinRoot(candidate, workspace);\n if (!confinedPath) throw new Error('Path is outside the workspace');\n return { workspace, path: confinedPath, relativePath: safeRelativePath };\n}\n\n/**\n * List the directories inside `requestedPath`, confined to `root`. An absent or\n * out-of-root path is clamped to the root, so the worst a malicious client can\n * do is browse within the allowed root.\n */\nexport async function listDirectory(root: string, requestedPath?: string): Promise<DirectoryListing> {\n // Resolve the root through symlinks so all confinement checks compare real\n // paths; a symlink that escapes the root is then reliably detectable.\n const resolvedRoot = await realOrResolved(resolveFsRoot(root));\n\n let target = resolvedRoot;\n if (requestedPath && requestedPath.trim()) {\n const candidate = isAbsolute(requestedPath) ? resolve(requestedPath) : resolve(resolvedRoot, requestedPath);\n // Follow symlinks and re-confirm the real target stays within the root.\n target = (await realPathWithinRoot(candidate, resolvedRoot)) ?? resolvedRoot;\n }\n\n // Confirm the target is a real directory; fall back to root otherwise.\n try {\n const info = await stat(target);\n if (!info.isDirectory()) target = resolvedRoot;\n } catch {\n target = resolvedRoot;\n }\n\n const dirents = await readdir(target, { withFileTypes: true });\n const entries: DirectoryEntry[] = [];\n for (const dirent of dirents) {\n if (dirent.name.startsWith('.')) continue; // skip dotfiles/dirs\n const entryPath = join(target, dirent.name);\n let isDir = dirent.isDirectory();\n if (dirent.isSymbolicLink()) {\n // Only surface symlinks whose real target is a directory inside the root,\n // so a link pointing outside the root can't be browsed or selected.\n const real = await realPathWithinRoot(entryPath, resolvedRoot);\n isDir = real ? (await stat(real).catch(() => null))?.isDirectory() === true : false;\n }\n if (isDir) entries.push({ name: dirent.name, path: entryPath });\n }\n entries.sort((a, b) => a.name.localeCompare(b.name));\n\n const parent = target === resolvedRoot ? null : resolve(target, '..');\n\n return { root: resolvedRoot, path: target, parent, entries };\n}\n\nasync function listRenderedEntries(rootPath: string, currentPath = rootPath): Promise<WorkspaceRenderedEntry[]> {\n const dirents = await readdir(currentPath, { withFileTypes: true });\n const entries: WorkspaceRenderedEntry[] = [];\n\n for (const dirent of dirents) {\n const entryPath = join(currentPath, dirent.name);\n const info = await lstat(entryPath);\n const relativePath = entryPath.slice(rootPath.length + 1);\n\n if (info.isDirectory()) {\n entries.push({\n name: dirent.name,\n path: relativePath,\n type: 'directory',\n size: info.size,\n updatedAt: info.mtime.toISOString(),\n });\n entries.push(...(await listRenderedEntries(rootPath, entryPath)));\n continue;\n }\n\n if (info.isFile()) {\n entries.push({\n name: dirent.name,\n path: relativePath,\n type: 'file',\n size: info.size,\n updatedAt: info.mtime.toISOString(),\n });\n }\n }\n\n return entries.sort((a, b) => a.path.localeCompare(b.path));\n}\n\nexport async function listWorkspaceRenderedPath(\n root: string,\n workspacePath: string,\n renderedRoot: string,\n): Promise<WorkspaceRenderedListing> {\n const safeRoot = assertApprovedRenderedRoot(renderedRoot);\n const { workspace } = await confinedWorkspacePath(root, workspacePath);\n const renderedPath = resolve(workspace, safeRoot);\n if (!isWithinRoot(renderedPath, workspace)) throw new Error('Root escapes workspace');\n\n const confinedRootPath = await realPathWithinRoot(renderedPath, workspace);\n if (!confinedRootPath) return { workspacePath: workspace, root: safeRoot, rootPath: renderedPath, entries: [] };\n\n const info = await stat(confinedRootPath);\n if (!info.isDirectory()) return { workspacePath: workspace, root: safeRoot, rootPath: confinedRootPath, entries: [] };\n\n return {\n workspacePath: workspace,\n root: safeRoot,\n rootPath: confinedRootPath,\n entries: await listRenderedEntries(confinedRootPath),\n };\n}\n\nexport async function readWorkspaceFile(root: string, workspacePath: string, path: string): Promise<WorkspaceFile> {\n const safePath = assertRelativePath(path, 'path');\n const relativeRoot = safePath.split('/')[0] ?? '';\n assertApprovedRenderedRoot(relativeRoot);\n const {\n workspace,\n path: confinedPath,\n relativePath,\n } = await confinedWorkspaceRelativePath(root, workspacePath, path);\n const info = await lstat(confinedPath);\n if (info.isDirectory()) throw new Error('Path is a directory');\n if (!info.isFile()) throw new Error('Unsupported file type');\n\n const bytesToRead = Math.min(info.size, MAX_TEXT_FILE_BYTES);\n const contentBuffer = Buffer.alloc(bytesToRead);\n const handle = await open(confinedPath, 'r');\n try {\n await handle.read(contentBuffer, 0, bytesToRead, 0);\n } finally {\n await handle.close();\n }\n\n try {\n const content = TEXT_DECODER.decode(contentBuffer);\n return {\n workspacePath: workspace,\n path: relativePath,\n name: relativePath.split('/').pop() ?? relativePath,\n size: info.size,\n updatedAt: info.mtime.toISOString(),\n contentType: 'text',\n content,\n truncated: info.size > MAX_TEXT_FILE_BYTES,\n };\n } catch {\n return {\n workspacePath: workspace,\n path: relativePath,\n name: relativePath.split('/').pop() ?? relativePath,\n size: info.size,\n updatedAt: info.mtime.toISOString(),\n contentType: 'unsupported',\n };\n }\n}\n\nexport async function listArtifacts(root: string, workspacePath: string): Promise<ArtifactListing> {\n const listing = await listWorkspaceRenderedPath(root, workspacePath, '.artifacts');\n return {\n rootPath: listing.workspacePath,\n artifactsPath: listing.rootPath,\n entries: listing.entries,\n };\n}\n\nexport interface ResolvedCodebase {\n /**\n * The resourceId the TUI would use for this path — derived identically so a\n * project opened in the terminal and in the web app resolve to the SAME\n * session (and therefore the same threads).\n */\n resourceId: string;\n name: string;\n rootPath: string;\n gitUrl?: string;\n gitBranch?: string;\n}\n\n/**\n * Resolve a project path to the same resourceId the TUI uses. Mirrors\n * `createMastraCode`: detect the project, then apply any resourceId override\n * (MASTRA_RESOURCE_ID env var or `.mastracode/database.json`). This is the\n * shared continuity point — start in the TUI, continue on the web, same path\n * → same resourceId → same session.\n */\nexport function resolveCodebase(projectPath: string): ResolvedCodebase {\n const info = detectProject(projectPath);\n const override = getResourceIdOverride(info.rootPath);\n return {\n resourceId: override ?? info.resourceId,\n name: info.name,\n rootPath: info.rootPath,\n gitUrl: info.gitUrl,\n gitBranch: info.gitBranch,\n };\n}\n\n/**\n * Build the web filesystem routes as Mastra `apiRoutes`:\n * - `GET /web/fs/list?path=...` — browse directories (confined to root)\n * - `GET /web/codebase/resolve?path=...` — TUI-compatible codebase resourceId\n */\nexport function buildFsRoutes(options: { root?: string } = {}): ApiRoute[] {\n const root = resolveFsRoot(options.root);\n\n return [\n registerApiRoute('/web/fs/list', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const path = c.req.query('path');\n try {\n const listing = await listDirectory(root, path);\n return c.json(listing);\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return c.json({ error: message }, 500);\n }\n },\n }),\n registerApiRoute('/web/artifacts/list', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const path = c.req.query('path');\n if (!path) return c.json({ error: 'Missing required query param: path' }, 400);\n try {\n return c.json(await listArtifacts(root, path));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const status = message === 'Path is outside the browsable root' ? 403 : 500;\n return c.json({ error: message }, status);\n }\n },\n }),\n registerApiRoute('/web/workspace/rendered/list', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const workspacePath = c.req.query('workspacePath');\n const renderedRoot = c.req.query('root');\n if (!workspacePath) return c.json({ error: 'Missing required query param: workspacePath' }, 400);\n if (!renderedRoot) return c.json({ error: 'Missing required query param: root' }, 400);\n try {\n return c.json(await listWorkspaceRenderedPath(root, workspacePath, renderedRoot));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const status =\n message.includes('outside') ||\n message.includes('relative') ||\n message.includes('escapes') ||\n message.includes('not approved')\n ? 403\n : 500;\n return c.json({ error: message }, status);\n }\n },\n }),\n registerApiRoute('/web/workspace/file', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const workspacePath = c.req.query('workspacePath');\n const path = c.req.query('path');\n if (!workspacePath) return c.json({ error: 'Missing required query param: workspacePath' }, 400);\n if (!path) return c.json({ error: 'Missing required query param: path' }, 400);\n try {\n return c.json(await readWorkspaceFile(root, workspacePath, path));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const status =\n message.includes('outside') ||\n message.includes('relative') ||\n message.includes('escapes') ||\n message.includes('not approved')\n ? 403\n : message.includes('directory')\n ? 400\n : 500;\n return c.json({ error: message }, status);\n }\n },\n }),\n registerApiRoute('/web/codebase/resolve', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const path = c.req.query('path');\n if (!path) return c.json({ error: 'Missing required query param: path' }, 400);\n // Confine resolution to the browsable root (following symlinks), so this\n // endpoint can't be used to probe arbitrary filesystem paths. The web UI\n // only ever resolves directories the user picked via the root-confined\n // browser, so legitimate requests are always within the root.\n const confined = await realPathWithinRoot(isAbsolute(path) ? resolve(path) : resolve(root, path), root);\n if (!confined) return c.json({ error: 'Path is outside the browsable root' }, 403);\n try {\n return c.json(resolveCodebase(confined));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return c.json({ error: message }, 500);\n }\n },\n }),\n ];\n}\n"],"mappings":"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","names":[]}