feat: harden support, localize knowledge and add operator availability [skip ci]

Source-only publication of reviewed commit 64e96a7f423673dc29ad56485be9fc79bce8af1c.

Passed in isolation: focused frontend/backend tests, race checks, TypeScript typecheck, lint, Go compilation, Flowgram build, and deterministic SDK build/tests.

Not accepted as a production release: Next.js export/native assembly remains blocked by environment memory limits; browser interaction and MySQL staging acceptance remain unverified. No production server, database/schema, runtime configuration, deployment, release tag, or workflow execution was performed.

Expected source tree: 0a4a7528e9.
This commit is contained in:
2026-10-16 15:21:42 +00:00
parent 299ea50fb5
commit 2f7a491666
176 changed files with 11869 additions and 2716 deletions

View File

@@ -1,4 +1,7 @@
import { expireSession, readSession } from "@/lib/auth"
// Modified in the independent hardening fork; see docs/hardening/README.md.
// Modified for AgentDeskFixes: scope authentication expiry to the session that issued each request.
import { expireSession, readSession, type AuthSession } from "@/lib/auth"
import { isSameSession } from "@/lib/session-request-guard"
import { translateCurrentMessage } from "@/i18n/messages"
const API_BASE_URL =
@@ -50,10 +53,10 @@ function normalizeApiRequestError(error: unknown) {
)
}
async function parseResult<T>(response: Response) {
async function parseResult<T>(response: Response, requestSession: AuthSession | null) {
const payload = (await response.json()) as JsonResult<T>
if (!response.ok || !payload.success) {
if (payload.errorCode === 3000 || payload.errorCode === 3002) {
if ((payload.errorCode === 3000 || payload.errorCode === 3002) && isSameSession(requestSession, readSession())) {
expireSession()
}
throw new ApiRequestError(
@@ -65,11 +68,10 @@ async function parseResult<T>(response: Response) {
return payload.data
}
function buildRequestHeaders(headers: HeadersInit | undefined, skipAuth?: boolean, body?: BodyInit | null) {
const session = readSession()
function buildRequestHeaders(headers: HeadersInit | undefined, session: AuthSession | null, body?: BodyInit | null) {
const authHeaders = new Headers(headers)
if (!skipAuth && session?.accessToken) {
if (session?.accessToken) {
authHeaders.set("Authorization", `Bearer ${session.accessToken}`)
}
if (
@@ -101,7 +103,8 @@ export async function request<T>(
const { headers, skipAuth, baseUrl, onResponse, ...rest } = options
delete (rest as RequestOptions).baseUrl
delete (rest as RequestOptions).onResponse
const authHeaders = buildRequestHeaders(headers, skipAuth, rest.body)
const requestSession = skipAuth ? null : readSession()
const authHeaders = buildRequestHeaders(headers, requestSession, rest.body)
const requestBaseUrl = baseUrl !== undefined ? baseUrl : API_BASE_URL
try {
@@ -111,7 +114,7 @@ export async function request<T>(
cache: "no-store",
})
onResponse?.(response)
return await parseResult<T>(response)
return await parseResult<T>(response, requestSession)
} catch (error) {
throw normalizeApiRequestError(error)
}
@@ -124,7 +127,8 @@ export async function requestBlob(
const { headers, skipAuth, baseUrl, onResponse, ...rest } = options
delete (rest as RequestOptions).baseUrl
delete (rest as RequestOptions).onResponse
const authHeaders = buildRequestHeaders(headers, skipAuth, rest.body)
const requestSession = skipAuth ? null : readSession()
const authHeaders = buildRequestHeaders(headers, requestSession, rest.body)
const requestBaseUrl = baseUrl !== undefined ? baseUrl : API_BASE_URL
const response = await fetch(`${requestBaseUrl}${path}`, {
...rest,
@@ -136,7 +140,7 @@ export async function requestBlob(
const contentType = response.headers.get("Content-Type") ?? ""
if (contentType.includes("application/json")) {
try {
await parseResult<never>(response)
await parseResult<never>(response, requestSession)
} catch (error) {
throw error
}