Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
d79d388
feat(mobile-api): Bearer JWT auth helper + mobile login endpoint
Jul 17, 2026
dfce275
feat(mobile-api): mobile chat generation, regenerate & skills endpoints
Jul 17, 2026
fb1b651
feat(mobile-api): accept Bearer auth on dashboard chat sessions & mes…
Jul 17, 2026
6c31c26
fix(chat/upload): strip media-type params before MIME allowlist check
Jul 17, 2026
fe4d90f
feat(mobile-api): agent (assistant) CRUD endpoints + models list
Jul 17, 2026
98fab98
feat(mobile-api): honor agent config in mobile chat generation
Jul 17, 2026
d1f5bb0
feat(mobile-api): workflow read/run/monitor endpoints
Jul 20, 2026
025f0af
feat(mobile-api): media studio endpoints (generate, gallery, byte-pro…
Jul 21, 2026
899664d
feat(mobile-api): mobile-only audio generation path (pcm16 stream -> …
Jul 21, 2026
2849517
fix(mobile-api): accept ?token= on media file proxy for RN Image/Video
Jul 21, 2026
a305610
feat(mobile-api): honor image aspect ratio via center-crop
Jul 21, 2026
43a3449
feat(mobile-api): knowledge base (Files) endpoints
Jul 22, 2026
e211a33
feat(mobile-api): document intelligence endpoint + default-on enhance…
Jul 22, 2026
5f45eab
fix(mobile-api): honor ?enhanced on JSON document create (entity extr…
Jul 22, 2026
f283a93
chore(mobile-api): integrate agent tool/skill routes for local run
Jul 24, 2026
9424554
chore: add mobile marketplace routes to integration
Jul 24, 2026
252d1c5
Merge remote-tracking branch 'local/feat/mobile-integration' into clo…
Jul 28, 2026
1c1fcd5
feat(mobile-api): settings/org/credentials/mcp/memory/api-keys/featur…
Jul 29, 2026
2370978
Merge remote-tracking branch 'local/feat/mobile-integration' into clo…
Jul 29, 2026
13c82c8
perf(rag): support smaller embedding dim + opt-in HNSW KNN vector search
claude Jul 29, 2026
a47a994
fix(rag): re-embed UPDATE by RecordId; allow HNSW KNN with scope filter
claude Jul 29, 2026
217708a
perf(rag): make entity/graph search arm disable-able via KB_ENTITY_SE…
claude Jul 29, 2026
665618d
Merge commit '217708a6' into cloud-mobile-local
Jul 31, 2026
5c2220b
feat(mobile-admin): admin console API (users/models/providers/kb) + r…
Aug 13, 2026
ecb072b
fix(mobile-auth): reject suspended accounts on login; refresh lastAct…
Aug 13, 2026
d8c1aac
feat(skills): expose 'editable' flag (org-owned vs global platform sk…
Aug 13, 2026
5c09ab6
chore(mobile): workflow detail route update
Aug 13, 2026
831bff3
ci: pin bun to 1.3.12 in test workflow (match committed bun.lock)
Aug 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:

- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
bun-version: "1.3.12" # pinned to match the committed bun.lock

- name: Install canvas build deps
run: sudo apt-get update && sudo apt-get install -y libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev
Expand Down Expand Up @@ -68,7 +68,7 @@ jobs:

- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
bun-version: "1.3.12" # pinned to match the committed bun.lock

- name: Install canvas build deps
run: sudo apt-get update && sudo apt-get install -y libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev
Expand Down Expand Up @@ -103,7 +103,7 @@ jobs:

- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
bun-version: "1.3.12" # pinned to match the committed bun.lock

- name: Install canvas build deps
run: sudo apt-get update && sudo apt-get install -y libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev
Expand Down
8 changes: 4 additions & 4 deletions src/app/api/chat/upload/route.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { NextResponse } from "next/server"
import { auth } from "@/lib/auth"
import { getRequestUserId } from "@/lib/mobile-auth"
import { ChatUploadFormSchema } from "@/features/chat-public/schema"
import {
isChatPublicServiceError,
uploadChatAttachment,
} from "@/features/chat-public/service"

export async function POST(req: Request) {
const session = await auth()
if (!session?.user?.id) {
const userId = await getRequestUserId(req)
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
}

Expand All @@ -28,7 +28,7 @@ export async function POST(req: Request) {
const result = await uploadChatAttachment({
file: parsedForm.data.file,
sessionId: parsedForm.data.sessionId,
userId: session.user.id,
userId,
})

if (isChatPublicServiceError(result)) {
Expand Down
13 changes: 7 additions & 6 deletions src/app/api/dashboard/chat/sessions/[id]/messages/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { NextResponse } from "next/server"
import { auth } from "@/lib/auth"
import { getRequestUserId } from "@/lib/mobile-auth"
import {
DashboardChatSessionIdParamsSchema,
DashboardChatSessionMessageDeleteBodySchema,
Expand All @@ -18,8 +19,8 @@ export async function POST(
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth()
if (!session?.user?.id) {
const userId = await getRequestUserId(req)
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
}

Expand All @@ -36,7 +37,7 @@ export async function POST(
)
}
const result = await addDashboardChatSessionMessages({
userId: session.user.id,
userId,
sessionId: parsedParams.data.id,
input: parsedBody.data,
})
Expand Down Expand Up @@ -96,8 +97,8 @@ export async function DELETE(
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth()
if (!session?.user?.id) {
const userId = await getRequestUserId(req)
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
}

Expand All @@ -114,7 +115,7 @@ export async function DELETE(
)
}
const result = await deleteDashboardChatSessionMessages({
userId: session.user.id,
userId,
sessionId: parsedParams.data.id,
input: parsedBody.data,
})
Expand Down
20 changes: 10 additions & 10 deletions src/app/api/dashboard/chat/sessions/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { NextResponse } from "next/server"
import { auth } from "@/lib/auth"
import { getRequestUserId } from "@/lib/mobile-auth"
import {
DashboardChatSessionIdParamsSchema,
DashboardChatSessionUpdateBodySchema,
Expand All @@ -16,8 +16,8 @@ export async function GET(
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth()
if (!session?.user?.id) {
const userId = await getRequestUserId(req)
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
}

Expand All @@ -27,7 +27,7 @@ export async function GET(
}

const result = await getDashboardChatSession({
userId: session.user.id,
userId,
sessionId: parsedParams.data.id,
})

Expand All @@ -47,8 +47,8 @@ export async function PATCH(
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth()
if (!session?.user?.id) {
const userId = await getRequestUserId(req)
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
}

Expand All @@ -67,7 +67,7 @@ export async function PATCH(
)
}
const result = await updateDashboardChatSession({
userId: session.user.id,
userId,
sessionId: parsedParams.data.id,
input: parsedBody.data,
})
Expand All @@ -88,8 +88,8 @@ export async function DELETE(
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await auth()
if (!session?.user?.id) {
const userId = await getRequestUserId(req)
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
}

Expand All @@ -99,7 +99,7 @@ export async function DELETE(
}

const result = await deleteDashboardChatSession({
userId: session.user.id,
userId,
sessionId: parsedParams.data.id,
})

Expand Down
16 changes: 8 additions & 8 deletions src/app/api/dashboard/chat/sessions/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { NextResponse } from "next/server"
import { auth } from "@/lib/auth"
import { getRequestUserId } from "@/lib/mobile-auth"
import {
DashboardChatSessionCreateBodySchema,
} from "@/features/conversations/sessions/schema"
Expand All @@ -9,14 +9,14 @@ import {
} from "@/features/conversations/sessions/service"
import { isHttpServiceError } from "@/features/shared/http-service-error"

export async function GET() {
export async function GET(request: Request) {
try {
const session = await auth()
if (!session?.user?.id) {
const userId = await getRequestUserId(request)
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
}

const result = await listDashboardChatSessions({ userId: session.user.id })
const result = await listDashboardChatSessions({ userId })
return NextResponse.json(result)
} catch (error) {
console.error("[Chat Sessions API] GET error:", error)
Expand All @@ -26,8 +26,8 @@ export async function GET() {

export async function POST(req: Request) {
try {
const session = await auth()
if (!session?.user?.id) {
const userId = await getRequestUserId(req)
if (!userId) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
}

Expand All @@ -43,7 +43,7 @@ export async function POST(req: Request) {
)
}
const result = await createDashboardChatSession({
userId: session.user.id,
userId,
input: parsedBody.data,
})

Expand Down
42 changes: 42 additions & 0 deletions src/app/api/mobile/admin/models/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { NextResponse } from "next/server"
import { requireMobileAdmin } from "@/lib/mobile-admin"
import {
listAdminModels,
setModelEnabled,
setModelToolCalling,
setDefaultChatModel,
} from "@/features/platform-admin/models-service"

export async function GET(request: Request) {
const auth = await requireMobileAdmin(request)
if ("error" in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
const params = new URL(request.url).searchParams
const result = await listAdminModels({
search: params.get("search") ?? undefined,
providerId: params.get("providerId") ?? undefined,
})
return NextResponse.json(result)
}

export async function PATCH(request: Request) {
const auth = await requireMobileAdmin(request)
if ("error" in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
const body = await request.json().catch(() => null)
if (!body?.id) return NextResponse.json({ error: "id is required" }, { status: 400 })

let result: { ok?: boolean; error?: string; defaultModelId?: string }
if (body.default === true) {
result = await setDefaultChatModel(auth.user, body.id)
} else if (typeof body.enabled === "boolean") {
result = await setModelEnabled(auth.user, body.id, body.enabled)
} else if (typeof body.hasToolCalling === "boolean") {
result = await setModelToolCalling(auth.user, body.id, body.hasToolCalling)
} else {
return NextResponse.json(
{ error: "Nothing to update (enabled, default, or hasToolCalling)" },
{ status: 400 },
)
}
if (result.error) return NextResponse.json({ error: result.error }, { status: 400 })
return NextResponse.json(result)
}
19 changes: 19 additions & 0 deletions src/app/api/mobile/admin/models/sync/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { NextResponse } from "next/server"
import { requireMobileAdmin } from "@/lib/mobile-admin"
import { syncModelsFromOpenRouter } from "@/lib/models/sync"

export async function POST(request: Request) {
const auth = await requireMobileAdmin(request)
if ("error" in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
if (!process.env.OPENROUTER_API_KEY) {
return NextResponse.json({ error: "OPENROUTER_API_KEY is not configured" }, { status: 400 })
}
try {
return NextResponse.json(await syncModelsFromOpenRouter())
} catch (err) {
return NextResponse.json(
{ error: `Sync failed: ${err instanceof Error ? err.message : err}` },
{ status: 502 },
)
}
}
9 changes: 9 additions & 0 deletions src/app/api/mobile/admin/providers/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { NextResponse } from "next/server"
import { requireMobileAdmin } from "@/lib/mobile-admin"
import { listProviders } from "@/features/platform-admin/providers-service"

export async function GET(request: Request) {
const auth = await requireMobileAdmin(request)
if ("error" in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
return NextResponse.json({ providers: await listProviders() })
}
25 changes: 25 additions & 0 deletions src/app/api/mobile/admin/settings/kb/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { NextResponse } from "next/server"
import { requireMobileAdmin } from "@/lib/mobile-admin"
import { getKbSettings, updateKbSettings } from "@/features/platform-admin/kb-settings-service"

export async function GET(request: Request) {
const auth = await requireMobileAdmin(request)
if ("error" in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
return NextResponse.json(await getKbSettings())
}

export async function PUT(request: Request) {
const auth = await requireMobileAdmin(request)
if ("error" in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
const body = await request.json().catch(() => null)
if (!body?.updates || typeof body.updates !== "object") {
return NextResponse.json({ error: "updates object is required" }, { status: 400 })
}
const result = await updateKbSettings(auth.user, body.updates, {
confirmEmbeddingChange: body.confirmEmbeddingChange === true,
})
if ("error" in result) {
return NextResponse.json(result, { status: result.requiresEmbeddingConfirmation ? 409 : 400 })
}
return NextResponse.json(await getKbSettings())
}
12 changes: 12 additions & 0 deletions src/app/api/mobile/admin/users/[id]/reset-password/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { NextResponse } from "next/server"
import { requireMobileAdmin } from "@/lib/mobile-admin"
import { resetUserPassword } from "@/features/platform-admin/users-service"

export async function POST(request: Request, { params }: { params: Promise<{ id: string }> }) {
const auth = await requireMobileAdmin(request)
if ("error" in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
const { id } = await params
const result = await resetUserPassword(auth.user, id)
if ("error" in result) return NextResponse.json({ error: result.error }, { status: 404 })
return NextResponse.json(result)
}
31 changes: 31 additions & 0 deletions src/app/api/mobile/admin/users/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { NextResponse } from "next/server"
import { requireMobileAdmin } from "@/lib/mobile-admin"
import { getUserDetail, setUserSuspended, setUserRole } from "@/features/platform-admin/users-service"

export async function GET(request: Request, { params }: { params: Promise<{ id: string }> }) {
const auth = await requireMobileAdmin(request)
if ("error" in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
const { id } = await params
const detail = await getUserDetail(id)
if (!detail) return NextResponse.json({ error: "User not found" }, { status: 404 })
return NextResponse.json(detail)
}

export async function PATCH(request: Request, { params }: { params: Promise<{ id: string }> }) {
const auth = await requireMobileAdmin(request)
if ("error" in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
const { id } = await params
const body = await request.json().catch(() => null)
if (!body) return NextResponse.json({ error: "Invalid body" }, { status: 400 })
if (typeof body.suspended === "boolean") {
const result = await setUserSuspended(auth.user, id, body.suspended)
if ("error" in result) return NextResponse.json({ error: result.error }, { status: 400 })
}
if (body.role === "USER" || body.role === "ADMIN") {
const result = await setUserRole(auth.user, id, body.role)
if ("error" in result) return NextResponse.json({ error: result.error }, { status: 400 })
}
const detail = await getUserDetail(id)
if (!detail) return NextResponse.json({ error: "User not found" }, { status: 404 })
return NextResponse.json(detail)
}
33 changes: 33 additions & 0 deletions src/app/api/mobile/admin/users/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { NextResponse } from "next/server"
import { requireMobileAdmin } from "@/lib/mobile-admin"
import { listUsers, createUser } from "@/features/platform-admin/users-service"

export async function GET(request: Request) {
const auth = await requireMobileAdmin(request)
if ("error" in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
const params = new URL(request.url).searchParams
const suspendedParam = params.get("suspended")
const result = await listUsers({
search: params.get("search") ?? undefined,
role: (params.get("role") as "USER" | "ADMIN" | null) ?? undefined,
suspended: suspendedParam === null ? undefined : suspendedParam === "true",
page: params.get("page") ? Number(params.get("page")) : undefined,
pageSize: params.get("pageSize") ? Number(params.get("pageSize")) : undefined,
})
return NextResponse.json(result)
}

export async function POST(request: Request) {
const auth = await requireMobileAdmin(request)
if ("error" in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })
const body = await request.json().catch(() => null)
if (!body?.email) return NextResponse.json({ error: "email is required" }, { status: 400 })
const result = await createUser(auth.user, {
email: body.email,
name: body.name ?? "",
password: body.password || undefined,
role: body.role,
})
if ("error" in result) return NextResponse.json({ error: result.error }, { status: 400 })
return NextResponse.json(result, { status: 201 })
}
Loading
Loading