@mastra/factory 0.24.0-alpha.10 → 0.24.0-alpha.12
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.
- package/README.md +4 -4
- package/dist/environment/build-runner.js +2 -1
- package/dist/environment/build-runner.js.map +1 -1
- package/dist/environment/build-workflow.js +1 -1
- package/dist/environment/build-workflow.js.map +1 -1
- package/dist/environment/build.js +16 -14
- package/dist/environment/build.js.map +1 -1
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +1 -0
- package/dist/factory.js.map +1 -1
- package/dist/integrations/github/app-identity.js.map +1 -1
- package/dist/integrations/github/integration.js +1 -1
- package/dist/integrations/github/integration.js.map +1 -1
- package/dist/integrations/github/reconcile-worker.js +1 -1
- package/dist/integrations/github/reconcile-worker.js.map +1 -1
- package/dist/integrations/github/routes.js +1 -1
- package/dist/integrations/github/routes.js.map +1 -1
- package/dist/integrations/github/rules.d.ts.map +1 -1
- package/dist/integrations/github/rules.js +53 -9
- package/dist/integrations/github/rules.js.map +1 -1
- package/dist/integrations/github/sandbox.js +2 -2
- package/dist/integrations/github/sandbox.js.map +1 -1
- package/dist/integrations/gitlab/api.js.map +1 -1
- package/dist/integrations/gitlab/integration.js.map +1 -1
- package/dist/integrations/gitlab/reconciliation-config.js +2 -1
- package/dist/integrations/gitlab/reconciliation-config.js.map +1 -1
- package/dist/integrations/gitlab/rules.js.map +1 -1
- package/dist/integrations/incidentio/api.js.map +1 -1
- package/dist/integrations/incidentio/integration.js.map +1 -1
- package/dist/integrations/incidentio/rules.js.map +1 -1
- package/dist/integrations/issue-reconcile-worker.js +1 -1
- package/dist/integrations/issue-reconcile-worker.js.map +1 -1
- package/dist/integrations/issue-reconciler.js +1 -1
- package/dist/integrations/issue-reconciler.js.map +1 -1
- package/dist/integrations/jira/api.js.map +1 -1
- package/dist/integrations/jira/integration.js.map +1 -1
- package/dist/integrations/jira/rules.js.map +1 -1
- package/dist/integrations/linear/integration.js.map +1 -1
- package/dist/integrations/linear/rules.js.map +1 -1
- package/dist/integrations/platform/api-client.js.map +1 -1
- package/dist/integrations/platform/github/event-worker.js +1 -1
- package/dist/integrations/platform/github/event-worker.js.map +1 -1
- package/dist/integrations/platform/github/integration.js +2 -2
- package/dist/integrations/platform/github/integration.js.map +1 -1
- package/dist/integrations/platform/gitlab/event-worker.js.map +1 -1
- package/dist/integrations/platform/gitlab/integration.js.map +1 -1
- package/dist/integrations/platform/incidentio/integration.js +2 -1
- package/dist/integrations/platform/incidentio/integration.js.map +1 -1
- package/dist/integrations/platform/jira/integration.js.map +1 -1
- package/dist/integrations/platform/linear/event-worker.js +1 -1
- package/dist/integrations/platform/linear/event-worker.js.map +1 -1
- package/dist/integrations/platform/linear/integration.js.map +1 -1
- package/dist/integrations/slack/connect-route.js +1 -1
- package/dist/integrations/slack/connect-route.js.map +1 -1
- package/dist/integrations/slack/feed-publisher.js +1 -1
- package/dist/integrations/slack/feed-publisher.js.map +1 -1
- package/dist/integrations/slack/integration.js +1 -1
- package/dist/integrations/slack/integration.js.map +1 -1
- package/dist/integrations/slack/slack.js +2 -1
- package/dist/integrations/slack/slack.js.map +1 -1
- package/dist/integrations/workos/integration.js.map +1 -1
- package/dist/packages/_internals/workspace/dist/index.js +18 -1
- package/dist/packages/_internals/workspace/dist/index.js.map +1 -1
- package/dist/routes/attention-activity.js.map +1 -1
- package/dist/routes/attention-parked.js.map +1 -1
- package/dist/routes/attention-providers.js.map +1 -1
- package/dist/routes/automation-runs.js.map +1 -1
- package/dist/routes/contracts.js +1 -1
- package/dist/routes/contracts.js.map +1 -1
- package/dist/routes/custom-provider-source.js.map +1 -1
- package/dist/routes/fs.d.ts.map +1 -1
- package/dist/routes/fs.js +4 -3
- package/dist/routes/fs.js.map +1 -1
- package/dist/routes/intake.js +2 -1
- package/dist/routes/intake.js.map +1 -1
- package/dist/routes/knowledge.js +1 -1
- package/dist/routes/knowledge.js.map +1 -1
- package/dist/routes/oauth.js +3 -2
- package/dist/routes/oauth.js.map +1 -1
- package/dist/routes/projects.js +32 -30
- package/dist/routes/projects.js.map +1 -1
- package/dist/routes/skills.js.map +1 -1
- package/dist/routes/source-control-sessions.js +1 -1
- package/dist/routes/source-control-sessions.js.map +1 -1
- package/dist/routes/surface.js +8 -2
- package/dist/routes/surface.js.map +1 -1
- package/dist/routes/tenant-credentials.js.map +1 -1
- package/dist/routes/work-items.js.map +1 -1
- package/dist/rules/binding-context.js +4 -2
- package/dist/rules/binding-context.js.map +1 -1
- package/dist/rules/dispatcher.js +4 -4
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/start-coordinator.js.map +1 -1
- package/dist/rules/tools.js +2 -2
- package/dist/rules/tools.js.map +1 -1
- package/dist/rules/transition-service.js.map +1 -1
- package/dist/sandbox/callback-factory-sandbox.js.map +1 -1
- package/dist/sandbox/local-factory-sandbox.js.map +1 -1
- package/dist/sandbox/session-retirement.js.map +1 -1
- package/dist/session/live-sessions.js.map +1 -1
- package/dist/session/memory-settings-hydration.js +2 -1
- package/dist/session/memory-settings-hydration.js.map +1 -1
- package/dist/spa-static.js.map +1 -1
- package/dist/state-signing.js +1 -1
- package/dist/state-signing.js.map +1 -1
- package/dist/storage/domains/audit/base.js.map +1 -1
- package/dist/storage/domains/audit/domain.js.map +1 -1
- package/dist/storage/domains/channel-identity/base.js.map +1 -1
- package/dist/storage/domains/comments/base.js.map +1 -1
- package/dist/storage/domains/comments/domain.js.map +1 -1
- package/dist/storage/domains/comments/feed-context.js.map +1 -1
- package/dist/storage/domains/credentials/base.js.map +1 -1
- package/dist/storage/domains/custom-providers/base.js.map +1 -1
- package/dist/storage/domains/intake/base.js.map +1 -1
- package/dist/storage/domains/integrations/base.js.map +1 -1
- package/dist/storage/domains/memory-settings/base.js.map +1 -1
- package/dist/storage/domains/model-defaults/base.js.map +1 -1
- package/dist/storage/domains/projects/base.js.map +1 -1
- package/dist/storage/domains/queue-health/base.js.map +1 -1
- package/dist/storage/domains/source-control/base.js.map +1 -1
- package/dist/storage/domains/work-items/base.js +2 -1
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/dist/storage/domains/work-items/board-version.js.map +1 -1
- package/dist/storage/domains/work-items/metrics.js.map +1 -1
- package/dist/tools/messaging-tools.d.ts.map +1 -1
- package/dist/tools/messaging-tools.js +201 -73
- package/dist/tools/messaging-tools.js.map +1 -1
- package/dist/tools/run-watch.d.ts +2 -0
- package/dist/tools/run-watch.d.ts.map +1 -1
- package/dist/tools/run-watch.js +42 -2
- package/dist/tools/run-watch.js.map +1 -1
- package/dist/tools/session-tools.d.ts.map +1 -1
- package/dist/tools/session-tools.js +24 -9
- package/dist/tools/session-tools.js.map +1 -1
- package/dist/tools/sessions-state-processor.js +2 -2
- package/dist/tools/sessions-state-processor.js.map +1 -1
- package/dist/workspace.js +15 -12
- package/dist/workspace.js.map +1 -1
- package/package.json +8 -8
package/dist/routes/fs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs.js","names":["posixPath"],"sources":["../../src/routes/fs.ts"],"sourcesContent":["import { lstat, open, readdir, realpath, stat } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { isAbsolute, join, posix as posixPath, resolve, sep } from 'node:path';\n\nimport { SandboxFilesystem } from '@mastra/code-sdk/agents/sandbox-filesystem';\nimport { detectProject, getResourceIdOverride } from '@mastra/code-sdk/utils/project';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { ApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport { requireExec } from '../sandbox/materialization.js';\nimport type { ExecutableSandbox } from '../sandbox/materialization.js';\nimport { peekSessionSandbox, sessionLayout } from '../sandbox/session-sandbox.js';\nimport type { SessionLayout, SessionLayoutRepository } from '../sandbox/session-sandbox.js';\nimport { peekSessionEnvironment } from '../session/environment-state-processor.js';\nimport { waitForPendingFilesystemCapture } from '../session/filesystem-capture.js';\nimport type { FilesystemStorage } from '../storage/domains/filesystem/base.js';\nimport type { SourceControlSession } from '../storage/domains/source-control/base.js';\nimport type { RouteAuth } from './route.js';\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 interface WorkspaceFilesListing {\n /** The Factory session resource id. */\n workspacePath: string;\n /** The agent thread whose terminal file list was captured. */\n threadId: string;\n files: Array<{ path: string }>;\n /**\n * The environment repositories the paths are prefixed by, in position\n * order. Present only when the session's environment holds two or more\n * repositories; a one-repository session lists unprefixed paths.\n */\n repositories?: Array<{ slug: string; prefix: string }>;\n}\n\nexport type WorkspaceChangeStatus =\n | 'modified'\n | 'added'\n | 'deleted'\n | 'renamed'\n | 'copied'\n | 'untracked'\n | 'conflicted';\n\nexport interface WorkspaceChange {\n path: string;\n previousPath?: string;\n status: WorkspaceChangeStatus;\n additions?: number;\n deletions?: number;\n binary?: boolean;\n}\n\nexport interface WorkspaceChangesRepository {\n slug: string;\n /** The path prefix of this repository's changes in the flat list, e.g. `platform/`. */\n prefix: string;\n /** False when the checkout could not be inspected (missing, or git failed). */\n available: boolean;\n changes: WorkspaceChange[];\n additions?: number;\n deletions?: number;\n}\n\nexport interface WorkspaceChanges {\n workspacePath: string;\n available: boolean;\n /** Every repository's changes, paths prefixed by the repository directory in a multi-repository session. */\n changes: WorkspaceChange[];\n additions?: number;\n deletions?: number;\n /**\n * One group per environment repository, in position order. Present only\n * when the session's environment holds two or more repositories.\n */\n repositories?: WorkspaceChangesRepository[];\n}\n\nexport interface WorkspaceDiff {\n workspacePath: string;\n path: string;\n patch: 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 MAX_DIFF_BYTES = 512 * 1024;\nconst WORKSPACE_NUMSTAT_SCRIPT = `\nset -e\nworkdir=$1\nbase=$2\nindex_file=$(mktemp)\nuntracked_file=$(mktemp)\nobject_dir=$(mktemp -d)\ntrap 'rm -f \"$index_file\" \"$untracked_file\"; rm -rf \"$object_dir\"' EXIT\nrm -f \"$index_file\"\ngit -C \"$workdir\" diff --numstat -z --find-renames --no-ext-diff --no-textconv \"$base\"\ngit -C \"$workdir\" ls-files --others --exclude-standard -z >\"$untracked_file\"\ngit_dir=$(git -C \"$workdir\" rev-parse --absolute-git-dir)\nexport GIT_INDEX_FILE=\"$index_file\"\nexport GIT_OBJECT_DIRECTORY=\"$object_dir\"\nexport GIT_ALTERNATE_OBJECT_DIRECTORIES=\"$git_dir/objects\"\ngit -C \"$workdir\" read-tree --empty\ngit -C \"$workdir\" update-index --add -z --stdin <\"$untracked_file\"\nempty_tree=$(git -C \"$workdir\" hash-object -t tree /dev/null)\ngit -C \"$workdir\" diff --cached --numstat -z --no-ext-diff --no-textconv \"$empty_tree\"\n`;\nconst BOUNDED_GIT_DIFF_SCRIPT = `\nallow_exit_one=$1\nshift\nstatus_file=$(mktemp)\nstderr_file=$(mktemp)\ntrap 'rm -f \"$status_file\" \"$stderr_file\"' EXIT\n(\n git \"$@\" 2>\"$stderr_file\"\n printf '%s' \"$?\" >\"$status_file\"\n) | head -c ${MAX_DIFF_BYTES + 1}\nstatus=$(cat \"$status_file\")\ncase \"$status\" in\n 0|141) exit 0 ;;\n 1) [ \"$allow_exit_one\" = \"1\" ] && exit 0 ;;\nesac\ncat \"$stderr_file\" >&2\nexit \"\\${status:-1}\"\n`;\nconst TEXT_DECODER = new TextDecoder('utf-8', { fatal: true });\nconst APPROVED_RENDERED_ROOTS = new Set(['.artifacts']);\n\n/** Erase a route handler's path-parameterized context to a plain `Context`. */\nfunction loose(c: unknown): Context {\n return c as Context;\n}\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\n// ── Session-backed workspace access ──────────────────────────────────────────\n//\n// The web UI identifies a Factory session workspace by its session id (a UUID),\n// not by a server-local filesystem path — the session's files live inside the\n// session's sandbox (a remote VM on deployed factories). These helpers resolve\n// the session, enforce that the caller owns it, reattach to its sandbox, and\n// serve the approved rendered roots through `SandboxFilesystem`.\n\n/** Dependencies for resolving a `workspacePath` that is a Factory session id. */\nexport interface SessionFsDeps {\n auth: RouteAuth;\n sessions: { getBySessionId(sessionId: string): Promise<SourceControlSession | null> };\n filesystem: Pick<FilesystemStorage, 'listFiles'>;\n}\n\n/**\n * Resolve a `workspacePath` query param as a Factory session id. Returns the\n * session when one exists and the caller owns it, `null` when no session\n * matches (the caller should fall back to local-path handling), and throws\n * when a session exists but belongs to another tenant.\n */\nasync function resolveAuthorizedSession(\n c: Context,\n deps: SessionFsDeps | undefined,\n workspacePath: string,\n): Promise<SourceControlSession | null> {\n if (!deps) return null;\n const session = await deps.sessions.getBySessionId(workspacePath);\n if (!session) return null;\n if (deps.auth.enabled()) {\n await deps.auth.ensureUser(c);\n const tenant = deps.auth.tenant(c);\n if (!tenant || tenant.orgId !== session.orgId || tenant.userId !== session.userId) {\n throw new Error('Session is not available to the current user');\n }\n }\n return session;\n}\n\nexport async function listSessionFilesystemFiles(\n filesystem: Pick<FilesystemStorage, 'listFiles'>,\n session: SourceControlSession,\n threadId: string,\n): Promise<WorkspaceFilesListing> {\n const safeThreadId = threadId.trim();\n if (!safeThreadId) throw new Error('Missing required query param: threadId');\n\n // The turn-end capture no longer gates agent_end, so a reader refetching on\n // run completion could otherwise race it and serve the previous turn's\n // listing. Await the in-flight capture (bounded) before reading.\n await waitForPendingFilesystemCapture(session.sessionId);\n\n const layout = sessionLayout(peekSessionSandbox(session.id));\n return {\n workspacePath: session.sessionId,\n threadId: safeThreadId,\n files: await filesystem.listFiles({ resourceId: session.sessionId, threadId: safeThreadId }),\n ...(layout && layout.repos.length > 1\n ? { repositories: layout.repos.map(repo => ({ slug: repo.slug, prefix: repo.prefix })) }\n : {}),\n };\n}\n\ninterface SessionSandboxHandle {\n sandbox: ExecutableSandbox;\n filesystem: SandboxFilesystem;\n layout: SessionLayout;\n /** The branch each layout repository is compared against, by slug. */\n baseBranches: Map<string, string>;\n}\n\n/**\n * Resolve the session's sandbox from the per-process memo and wrap the\n * session layout's root in a `SandboxFilesystem`: the checkout for a\n * one-repository session, the workspace root (one directory per repository)\n * otherwise. Returns `null` when the session has no sandbox in this process\n * (never opened here, or evicted by retirement). This is a passive read path,\n * so it never constructs or provisions: the session's files come back the\n * next time the workspace is actually opened (e.g. by sending a message).\n */\nasync function sessionSandbox(session: SourceControlSession): Promise<SessionSandboxHandle | null> {\n const entry = peekSessionSandbox(session.id);\n // An unresolved workdir means the sandbox never started in this process —\n // nothing is materialized, so there are no files to browse.\n const layout = sessionLayout(entry);\n if (!entry || !layout) return null;\n const sandbox = requireExec(entry.sandbox);\n return {\n sandbox,\n filesystem: new SandboxFilesystem({ sandbox, workdir: layout.root }),\n layout,\n baseBranches: repositoryBaseBranches(session, layout, entry.ownRepo),\n };\n}\n\n/**\n * The branch each layout repository is compared against: the session base\n * branch for the session's own repository (session branch vs base), the\n * default branch the boot recorded for every other one, `HEAD` (the checkout's\n * own `origin/HEAD`) until the boot has recorded it.\n */\nfunction repositoryBaseBranches(\n session: SourceControlSession,\n layout: SessionLayout,\n ownRepo: string,\n): Map<string, string> {\n const environment = layout.repos.length > 1 ? peekSessionEnvironment(session.sessionId) : undefined;\n const bases = new Map<string, string>();\n for (const repo of layout.repos) {\n if (layout.repos.length === 1 || repo.slug.toLowerCase() === ownRepo.toLowerCase()) {\n bases.set(repo.slug, session.baseBranch);\n continue;\n }\n const state = environment?.repositories.find(candidate => candidate.slug.toLowerCase() === repo.slug.toLowerCase());\n // The boot records the state at its end, after every setup command; until\n // then a secondary checkout compares against its own `origin/HEAD`.\n bases.set(repo.slug, state?.defaultBranch ?? 'HEAD');\n }\n return bases;\n}\n\n/**\n * Split a client path into the layout repository it belongs to and the path\n * inside that checkout. Longest prefix wins; the inside path is validated\n * again so a prefix cannot smuggle a traversal. A path matching no repository\n * (including a bare prefix) is not available: the file routes only serve the\n * checkouts, never the workspace root itself.\n */\nfunction resolveLayoutPath(\n layout: SessionLayout,\n path: string,\n label: string,\n): { repo: SessionLayoutRepository; relative: string } {\n const safePath = assertRelativePath(path, label);\n if (layout.repos.length === 1) return { repo: layout.repos[0]!, relative: safePath };\n const repo = layout.repos\n .filter(candidate => safePath.startsWith(candidate.prefix) && safePath.length > candidate.prefix.length)\n .toSorted((a, b) => b.prefix.length - a.prefix.length)[0];\n if (!repo) throw new Error(`${label} is not available in this session's repositories`);\n return { repo, relative: assertRelativePath(safePath.slice(repo.prefix.length), label) };\n}\n\n/** List an approved rendered root inside a Factory session's sandbox workdir. */\nexport async function listSessionRenderedPath(\n session: SourceControlSession,\n renderedRoot: string,\n): Promise<WorkspaceRenderedListing> {\n const safeRoot = assertApprovedRenderedRoot(renderedRoot);\n const handle = await sessionSandbox(session);\n const rootPath = posixPath.join(handle?.layout.root ?? '', safeRoot);\n const empty: WorkspaceRenderedListing = { workspacePath: session.sessionId, root: safeRoot, rootPath, entries: [] };\n if (!handle) return empty;\n\n // One round trip: emit \"type\\tsize\\tmtime\\tpath\" per entry. `safeRoot` comes\n // from a fixed allowlist so interpolating it (quoted) is safe. The listing is\n // parsed line by line, so retain all of it rather than the default 1 MiB tail.\n const quotedRoot = `'${rootPath.replace(/'/g, `'\\\\''`)}'`;\n const result = await handle.sandbox.executeCommand(\n 'sh',\n [\n '-c',\n `test -d ${quotedRoot} && find ${quotedRoot} -mindepth 1 -printf '%y\\\\t%s\\\\t%T@\\\\t%p\\\\n' 2>/dev/null || true`,\n ],\n { timeout: 30_000, maxRetainedBytes: Infinity },\n );\n if (result.exitCode !== 0) return empty;\n\n const entries: WorkspaceRenderedEntry[] = [];\n for (const line of result.stdout.split('\\n')) {\n if (!line) continue;\n const [type, sizeStr, mtimeStr, ...pathParts] = line.split('\\t');\n const fullPath = pathParts.join('\\t');\n if (!fullPath || !fullPath.startsWith(`${rootPath}/`)) continue;\n const relativePath = fullPath.slice(rootPath.length + 1);\n entries.push({\n name: posixPath.basename(relativePath),\n path: relativePath,\n type: type === 'd' ? 'directory' : 'file',\n size: type === 'd' ? 0 : Number(sizeStr) || 0,\n updatedAt: new Date((Number(mtimeStr) || 0) * 1000).toISOString(),\n });\n }\n entries.sort((a, b) => a.path.localeCompare(b.path));\n\n return { workspacePath: session.sessionId, root: safeRoot, rootPath, entries };\n}\n\n/** Read a file inside a session's sandbox. Paths outside rendered roots require a persisted-file allowlist check in the route. */\nexport async function readSessionWorkspaceFile(\n session: SourceControlSession,\n path: string,\n options: { allowUnapprovedPath?: boolean } = {},\n): Promise<WorkspaceFile> {\n const safePath = assertRelativePath(path, 'path');\n if (!options.allowUnapprovedPath) assertApprovedRenderedRoot(safePath.split('/')[0] ?? '');\n\n const handle = await sessionSandbox(session);\n if (!handle) throw new Error('Session workspace is not available');\n const { filesystem } = handle;\n const info = await filesystem.stat(safePath);\n if (info.type === 'directory') throw new Error('Path is a directory');\n\n const buffer = (await filesystem.readFile(safePath)) as Buffer;\n const truncated = buffer.length > MAX_TEXT_FILE_BYTES;\n const base = {\n workspacePath: session.sessionId,\n path: safePath,\n name: posixPath.basename(safePath),\n size: buffer.length,\n updatedAt: info.modifiedAt.toISOString(),\n };\n try {\n const content = TEXT_DECODER.decode(truncated ? buffer.subarray(0, MAX_TEXT_FILE_BYTES) : buffer);\n return { ...base, contentType: 'text', content, truncated };\n } catch {\n return { ...base, contentType: 'unsupported' };\n }\n}\n\nfunction changeStatus(code: string): WorkspaceChangeStatus {\n if (code === '??') return 'untracked';\n if (code.includes('U') || code === 'AA' || code === 'DD') return 'conflicted';\n if (code.includes('R')) return 'renamed';\n if (code.includes('C')) return 'copied';\n if (code.includes('D')) return 'deleted';\n if (code.includes('A')) return 'added';\n return 'modified';\n}\n\nexport function parseWorkspaceChanges(output: string): WorkspaceChange[] {\n const records = output.split('\\0');\n const changes: WorkspaceChange[] = [];\n\n for (let index = 0; index < records.length; index += 1) {\n const record = records[index];\n if (!record || record.length < 4) continue;\n const code = record.slice(0, 2);\n const path = record.slice(3);\n const status = changeStatus(code);\n if (status === 'renamed' || status === 'copied') {\n const previousPath = records[index + 1];\n if (previousPath) index += 1;\n changes.push({ path, previousPath: previousPath || undefined, status });\n continue;\n }\n changes.push({ path, status });\n }\n\n return changes.toSorted((a, b) => a.path.localeCompare(b.path));\n}\nexport function parseWorkspaceTrackedChanges(output: string): WorkspaceChange[] {\n const records = output.split('\\0');\n const changes: WorkspaceChange[] = [];\n for (let index = 0; index < records.length;) {\n const code = records[index++];\n if (!code) continue;\n const status = changeStatus(code);\n if (status === 'renamed' || status === 'copied') {\n const previousPath = records[index++];\n const path = records[index++];\n if (path) changes.push({ path, previousPath: previousPath || undefined, status });\n continue;\n }\n const path = records[index++];\n if (path) changes.push({ path, status });\n }\n return changes.toSorted((a, b) => a.path.localeCompare(b.path));\n}\n\nexport function parseWorkspaceChangeStats(output: string) {\n const records = output.split('\\0');\n const stats = new Map<string, { additions?: number; deletions?: number; binary?: boolean }>();\n\n for (let index = 0; index < records.length; index += 1) {\n const record = records[index];\n if (!record) continue;\n const firstTab = record.indexOf('\\t');\n const secondTab = record.indexOf('\\t', firstTab + 1);\n if (firstTab < 0 || secondTab < 0) continue;\n\n const additionsText = record.slice(0, firstTab);\n const deletionsText = record.slice(firstTab + 1, secondTab);\n let path = record.slice(secondTab + 1);\n if (!path) {\n const renamedPath = records[index + 2];\n if (!renamedPath) continue;\n path = renamedPath;\n index += 2;\n }\n if (path.startsWith('./')) path = path.slice(2);\n\n if (additionsText === '-' || deletionsText === '-') {\n stats.set(path, { binary: true });\n continue;\n }\n\n const additions = Number(additionsText);\n const deletions = Number(deletionsText);\n if (Number.isFinite(additions) && Number.isFinite(deletions)) stats.set(path, { additions, deletions });\n }\n\n return stats;\n}\n\nfunction unavailableWorkspaceChanges(workspacePath: string): WorkspaceChanges {\n return { workspacePath, available: false, changes: [] };\n}\n\nasync function resolveSessionComparisonBase(\n sandbox: ExecutableSandbox,\n workdir: string,\n baseBranch: string,\n): Promise<string> {\n const result = await sandbox.executeCommand('git', ['-C', workdir, 'merge-base', 'HEAD', `origin/${baseBranch}`], {\n timeout: 30_000,\n });\n const sha = result.stdout.trim();\n return result.exitCode === 0 && /^[0-9a-f]{40,64}$/i.test(sha) ? sha : 'HEAD';\n}\n\ntype RepositoryChanges = Pick<WorkspaceChangesRepository, 'available' | 'changes' | 'additions' | 'deletions'>;\n\n/** The changes of one checkout against its comparison base; `available: false` when git cannot inspect it. */\nasync function listRepositoryChanges(\n sandbox: ExecutableSandbox,\n workdir: string,\n baseBranch: string,\n): Promise<RepositoryChanges> {\n const comparisonBase = await resolveSessionComparisonBase(sandbox, workdir, baseBranch);\n\n // These outputs are parsed record by record (-z) and per path, so retain all\n // of them rather than the default 1 MiB tail, which would drop or split entries.\n const [statusResult, trackedResult, statsResult] = await Promise.all([\n sandbox.executeCommand('git', ['-C', workdir, 'status', '--porcelain=v1', '-z', '--untracked-files=all'], {\n timeout: 30_000,\n maxRetainedBytes: Infinity,\n }),\n sandbox.executeCommand('git', ['-C', workdir, 'diff', '--name-status', '-z', '--find-renames', comparisonBase], {\n timeout: 30_000,\n maxRetainedBytes: Infinity,\n }),\n sandbox.executeCommand('sh', ['-c', WORKSPACE_NUMSTAT_SCRIPT, 'mastracode-numstat', workdir, comparisonBase], {\n timeout: 30_000,\n maxRetainedBytes: Infinity,\n }),\n ]);\n if (statusResult.exitCode !== 0) return { available: false, changes: [] };\n\n const statusChanges = parseWorkspaceChanges(statusResult.stdout);\n const trackedChanges = trackedResult.exitCode === 0 ? parseWorkspaceTrackedChanges(trackedResult.stdout) : [];\n const changesByPath = new Map(trackedChanges.map(change => [change.path, change]));\n for (const change of statusChanges) {\n if (trackedResult.exitCode !== 0 || change.status === 'untracked' || change.status === 'conflicted') {\n changesByPath.set(change.path, change);\n }\n }\n const changes = [...changesByPath.values()].toSorted((a, b) => a.path.localeCompare(b.path));\n if (statsResult.exitCode !== 0) return { available: true, changes };\n\n const stats = parseWorkspaceChangeStats(statsResult.stdout);\n let additions = 0;\n let deletions = 0;\n const changesWithStats = changes.map(change => {\n const changeStats = stats.get(change.path);\n additions += changeStats?.additions ?? 0;\n deletions += changeStats?.deletions ?? 0;\n return { ...change, ...changeStats };\n });\n return { available: true, changes: changesWithStats, additions, deletions };\n}\n\nfunction prefixChange(change: WorkspaceChange, prefix: string): WorkspaceChange {\n if (!prefix) return change;\n return {\n ...change,\n path: `${prefix}${change.path}`,\n ...(change.previousPath ? { previousPath: `${prefix}${change.previousPath}` } : {}),\n };\n}\n\nexport async function listSessionWorkspaceChanges(session: SourceControlSession): Promise<WorkspaceChanges> {\n const handle = await sessionSandbox(session);\n if (!handle) return unavailableWorkspaceChanges(session.sessionId);\n const { layout, baseBranches: bases } = handle;\n\n // One repository: the response is exactly what it was before layouts.\n if (layout.repos.length === 1) {\n const repo = layout.repos[0]!;\n const result = await listRepositoryChanges(handle.sandbox, repo.dir, bases.get(repo.slug) ?? session.baseBranch);\n if (!result.available) return unavailableWorkspaceChanges(session.sessionId);\n return { workspacePath: session.sessionId, ...result };\n }\n\n // Several: inspect every checkout in parallel, one group each. A failing\n // one (missing checkout, git error) is unavailable on its own, the others\n // still answer.\n const groups: WorkspaceChangesRepository[] = await Promise.all(\n layout.repos.map(async repo => {\n const result = await listRepositoryChanges(handle.sandbox, repo.dir, bases.get(repo.slug)!);\n return {\n slug: repo.slug,\n prefix: repo.prefix,\n ...result,\n changes: result.changes.map(change => prefixChange(change, repo.prefix)),\n };\n }),\n );\n const available = groups.filter(group => group.available);\n const changes = available.flatMap(group => group.changes).toSorted((a, b) => a.path.localeCompare(b.path));\n const counted = available.filter(group => group.additions !== undefined);\n const totals =\n counted.length > 0\n ? {\n additions: counted.reduce((sum, group) => sum + (group.additions ?? 0), 0),\n deletions: counted.reduce((sum, group) => sum + (group.deletions ?? 0), 0),\n }\n : {};\n return {\n workspacePath: session.sessionId,\n available: available.length > 0,\n changes,\n ...totals,\n repositories: groups,\n };\n}\n\nasync function executeBoundedGitDiff(sandbox: ExecutableSandbox, args: string[], allowExitOne = false) {\n return sandbox.executeCommand(\n 'sh',\n ['-c', BOUNDED_GIT_DIFF_SCRIPT, 'mastracode-diff', allowExitOne ? '1' : '0', ...args],\n { timeout: 30_000 },\n );\n}\n\nfunction truncatePatch(patchBuffer: Buffer): string {\n const patch = patchBuffer.subarray(0, MAX_DIFF_BYTES).toString('utf8');\n const lastNewline = patch.lastIndexOf('\\n');\n if (lastNewline >= 0) return patch.slice(0, lastNewline + 1);\n return patch.replace(/\\uFFFD+$/, '');\n}\n\nexport async function readSessionWorkspaceDiff(\n session: SourceControlSession,\n path: string,\n previousPath?: string,\n): Promise<WorkspaceDiff> {\n const safePath = assertRelativePath(path, 'path');\n if (previousPath) assertRelativePath(previousPath, 'previousPath');\n const handle = await sessionSandbox(session);\n if (!handle) throw new Error('Session workspace is not available');\n // The client sends the prefixed paths it was listed; git runs inside the\n // checkout they belong to, on the paths relative to it. A rename never\n // crosses checkouts, so both halves must resolve to the same repository.\n const target = resolveLayoutPath(handle.layout, safePath, 'path');\n const previous = previousPath ? resolveLayoutPath(handle.layout, previousPath, 'previousPath') : undefined;\n if (previous && previous.repo !== target.repo) {\n throw new Error('previousPath is not available in the repository of path');\n }\n const workdir = target.repo.dir;\n const comparisonBase = await resolveSessionComparisonBase(\n handle.sandbox,\n workdir,\n handle.baseBranches.get(target.repo.slug)!,\n );\n\n const pathspecs = previous ? [previous.relative, target.relative] : [target.relative];\n let result = await executeBoundedGitDiff(handle.sandbox, [\n '--literal-pathspecs',\n '-C',\n workdir,\n 'diff',\n '--find-renames',\n '--no-ext-diff',\n '--no-color',\n '--unified=3',\n comparisonBase,\n '--',\n ...pathspecs,\n ]);\n if (result.exitCode !== 0) throw new Error(result.stderr || 'Unable to read workspace diff');\n\n if (!result.stdout) {\n const untracked = await handle.sandbox.executeCommand(\n 'git',\n ['--literal-pathspecs', '-C', workdir, 'ls-files', '--others', '--exclude-standard', '--', target.relative],\n { timeout: 30_000, maxRetainedBytes: Infinity },\n );\n if (untracked.exitCode === 0 && untracked.stdout.trim()) {\n result = await executeBoundedGitDiff(\n handle.sandbox,\n [\n '-C',\n workdir,\n 'diff',\n '--no-index',\n '--no-ext-diff',\n '--no-color',\n '--unified=3',\n '--',\n '/dev/null',\n target.relative,\n ],\n true,\n );\n if (result.exitCode !== 0 && result.exitCode !== 1) {\n throw new Error(result.stderr || 'Unable to read workspace diff');\n }\n }\n }\n\n const patchBuffer = Buffer.from(result.stdout);\n const truncated = patchBuffer.length > MAX_DIFF_BYTES;\n return {\n workspacePath: session.sessionId,\n path: safePath,\n patch: truncated ? truncatePatch(patchBuffer) : result.stdout,\n truncated,\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; sessionFs?: SessionFsDeps } = {}): ApiRoute[] {\n const root = resolveFsRoot(options.root);\n const sessionFs = options.sessionFs;\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 const session = await resolveAuthorizedSession(loose(c), sessionFs, workspacePath);\n if (session && sessionFs) {\n return c.json(await listSessionRenderedPath(session, renderedRoot));\n }\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 message.includes('not available')\n ? 403\n : 500;\n return c.json({ error: message }, status);\n }\n },\n }),\n registerApiRoute('/web/workspace/files', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const workspacePath = c.req.query('workspacePath');\n const threadId = c.req.query('threadId')?.trim();\n if (!workspacePath) return c.json({ error: 'Missing required query param: workspacePath' }, 400);\n if (!threadId) return c.json({ error: 'Missing required query param: threadId' }, 400);\n try {\n const session = await resolveAuthorizedSession(loose(c), sessionFs, workspacePath);\n if (!session || !sessionFs) return c.json({ error: 'Session workspace is not available' }, 403);\n return c.json(await listSessionFilesystemFiles(sessionFs.filesystem, session, threadId));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const status = message.includes('not available') ? 403 : 500;\n return c.json({ error: message }, status);\n }\n },\n }),\n registerApiRoute('/web/workspace/changes', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const workspacePath = c.req.query('workspacePath');\n if (!workspacePath) return c.json({ error: 'Missing required query param: workspacePath' }, 400);\n try {\n const session = await resolveAuthorizedSession(loose(c), sessionFs, workspacePath);\n if (!session || !sessionFs) return c.json(unavailableWorkspaceChanges(workspacePath));\n return c.json(await listSessionWorkspaceChanges(session));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return c.json({ error: message }, message.includes('not available') ? 403 : 500);\n }\n },\n }),\n registerApiRoute('/web/workspace/changes/diff', {\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 const previousPath = c.req.query('previousPath');\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 const session = await resolveAuthorizedSession(loose(c), sessionFs, workspacePath);\n if (!session || !sessionFs) return c.json({ error: 'Session workspace is not available' }, 403);\n return c.json(await readSessionWorkspaceDiff(session, path, previousPath));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const status =\n message.includes('relative') || message.includes('escapes') || message.includes('not available')\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 const requestedThreadId = c.req.query('threadId');\n const threadId = requestedThreadId?.trim();\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 if (requestedThreadId !== undefined && !threadId) {\n return c.json({ error: 'Missing required query param: threadId' }, 400);\n }\n try {\n const session = await resolveAuthorizedSession(loose(c), sessionFs, workspacePath);\n if (session && sessionFs) {\n if (threadId) {\n const safePath = assertRelativePath(path, 'path');\n const listing = await listSessionFilesystemFiles(sessionFs.filesystem, session, threadId);\n if (!listing.files.some(file => file.path === safePath)) {\n return c.json({ error: 'Path is not available for this thread' }, 404);\n }\n return c.json(await readSessionWorkspaceFile(session, safePath, { allowUnapprovedPath: true }));\n }\n return c.json(await readSessionWorkspaceFile(session, path));\n }\n if (threadId) return c.json({ error: 'Session workspace is not available' }, 403);\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 message.includes('not available')\n ? 403\n : message.includes('directory')\n ? 400\n : message.includes('not found')\n ? 404\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":";;;;;;;;;;;AA4JA,MAAM,sBAAsB,MAAM;AAClC,MAAM,iBAAiB,MAAM;AAC7B,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;AAoBjC,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;AAkBhC,MAAM,eAAe,IAAI,YAAY,SAAS,EAAE,OAAO,KAAK,CAAC;AAC7D,MAAM,0CAA0B,IAAI,IAAI,CAAC,YAAY,CAAC;;AAGtD,SAAS,MAAM,GAAqB;CAClC,OAAO;AACT;;AAGA,SAAgB,cAAc,MAAuB;CACnD,OAAO,QAAQ,QAAQ,KAAK,KAAK,IAAI,OAAO,QAAQ,CAAC;AACvD;;AAGA,SAAS,aAAa,WAAmB,MAAuB;CAC9D,IAAI,cAAc,MAAM,OAAO;CAC/B,MAAM,cAAc,KAAK,SAAS,GAAG,IAAI,OAAO,OAAO;CACvD,OAAO,UAAU,WAAW,WAAW;AACzC;AAEA,eAAe,eAAe,MAA+B;CAC3D,IAAI;EACF,OAAO,MAAM,SAAS,IAAI;CAC5B,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;AAQA,eAAe,mBAAmB,WAAmB,MAAsC;CACzF,IAAI;EACF,MAAM,OAAO,MAAM,SAAS,SAAS;EACrC,OAAO,aAAa,MAAM,IAAI,IAAI,OAAO;CAC3C,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,mBAAmB,MAAc,OAAuB;CAC/D,MAAM,UAAU,KAAK,KAAK;CAC1B,IAAI,CAAC,SAAS,MAAM,IAAI,MAAM,iCAAiC,OAAO;CACtE,IAAI,WAAW,OAAO,GAAG,MAAM,IAAI,MAAM,GAAG,MAAM,kBAAkB;CACpE,IAAI,QAAQ,MAAM,QAAQ,CAAC,CAAC,SAAS,IAAI,GAAG,MAAM,IAAI,MAAM,GAAG,MAAM,mBAAmB;CACxF,MAAM,aAAa,QAAQ,KAAK,OAAO,CAAC,CAAC,MAAM,CAAC;CAChD,IAAI,CAAC,cAAc,eAAe,QAAQ,WAAW,WAAW,KAAK,KAAK,GACxE,MAAM,IAAI,MAAM,GAAG,MAAM,mBAAmB;CAC9C,OAAO;AACT;AAEA,SAAS,2BAA2B,cAA8B;CAChE,MAAM,WAAW,mBAAmB,cAAc,MAAM;CACxD,IAAI,CAAC,wBAAwB,IAAI,QAAQ,GAAG,MAAM,IAAI,MAAM,oDAAoD;CAChH,OAAO;AACT;AAEA,eAAe,sBACb,MACA,eACsD;CACtD,MAAM,eAAe,MAAM,eAAe,cAAc,IAAI,CAAC;CAE7D,MAAM,YAAY,MAAM,mBADN,WAAW,aAAa,IAAI,QAAQ,aAAa,IAAI,QAAQ,cAAc,aAAa,GACpD,YAAY;CAClE,IAAI,CAAC,WAAW,MAAM,IAAI,MAAM,oCAAoC;CACpE,OAAO;EAAE;EAAc;CAAU;AACnC;AAEA,eAAe,8BACb,MACA,eACA,cACoE;CACpE,MAAM,mBAAmB,mBAAmB,cAAc,MAAM;CAChE,MAAM,EAAE,cAAc,MAAM,sBAAsB,MAAM,aAAa;CACrE,MAAM,YAAY,QAAQ,WAAW,gBAAgB;CACrD,IAAI,CAAC,aAAa,WAAW,SAAS,GAAG,MAAM,IAAI,MAAM,wBAAwB;CACjF,MAAM,eAAe,MAAM,mBAAmB,WAAW,SAAS;CAClE,IAAI,CAAC,cAAc,MAAM,IAAI,MAAM,+BAA+B;CAClE,OAAO;EAAE;EAAW,MAAM;EAAc,cAAc;CAAiB;AACzE;;;;;;AAOA,eAAsB,cAAc,MAAc,eAAmD;CAGnG,MAAM,eAAe,MAAM,eAAe,cAAc,IAAI,CAAC;CAE7D,IAAI,SAAS;CACb,IAAI,iBAAiB,cAAc,KAAK,GAGtC,SAAU,MAAM,mBAFE,WAAW,aAAa,IAAI,QAAQ,aAAa,IAAI,QAAQ,cAAc,aAAa,GAE5D,YAAY,KAAM;CAIlE,IAAI;EAEF,IAAI,EAAC,MADc,KAAK,MAAM,EAAA,CACpB,YAAY,GAAG,SAAS;CACpC,QAAQ;EACN,SAAS;CACX;CAEA,MAAM,UAAU,MAAM,QAAQ,QAAQ,EAAE,eAAe,KAAK,CAAC;CAC7D,MAAM,UAA4B,CAAC;CACnC,KAAK,MAAM,UAAU,SAAS;EAC5B,IAAI,OAAO,KAAK,WAAW,GAAG,GAAG;EACjC,MAAM,YAAY,KAAK,QAAQ,OAAO,IAAI;EAC1C,IAAI,QAAQ,OAAO,YAAY;EAC/B,IAAI,OAAO,eAAe,GAAG;GAG3B,MAAM,OAAO,MAAM,mBAAmB,WAAW,YAAY;GAC7D,QAAQ,QAAQ,MAAM,KAAK,IAAI,CAAC,CAAC,YAAY,IAAI,EAAA,EAAI,YAAY,MAAM,OAAO;EAChF;EACA,IAAI,OAAO,QAAQ,KAAK;GAAE,MAAM,OAAO;GAAM,MAAM;EAAU,CAAC;CAChE;CACA,QAAQ,MAAM,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;CAEnD,MAAM,SAAS,WAAW,eAAe,OAAO,QAAQ,QAAQ,IAAI;CAEpE,OAAO;EAAE,MAAM;EAAc,MAAM;EAAQ;EAAQ;CAAQ;AAC7D;AAEA,eAAe,oBAAoB,UAAkB,cAAc,UAA6C;CAC9G,MAAM,UAAU,MAAM,QAAQ,aAAa,EAAE,eAAe,KAAK,CAAC;CAClE,MAAM,UAAoC,CAAC;CAE3C,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,YAAY,KAAK,aAAa,OAAO,IAAI;EAC/C,MAAM,OAAO,MAAM,MAAM,SAAS;EAClC,MAAM,eAAe,UAAU,MAAM,SAAS,SAAS,CAAC;EAExD,IAAI,KAAK,YAAY,GAAG;GACtB,QAAQ,KAAK;IACX,MAAM,OAAO;IACb,MAAM;IACN,MAAM;IACN,MAAM,KAAK;IACX,WAAW,KAAK,MAAM,YAAY;GACpC,CAAC;GACD,QAAQ,KAAK,GAAI,MAAM,oBAAoB,UAAU,SAAS,CAAE;GAChE;EACF;EAEA,IAAI,KAAK,OAAO,GACd,QAAQ,KAAK;GACX,MAAM,OAAO;GACb,MAAM;GACN,MAAM;GACN,MAAM,KAAK;GACX,WAAW,KAAK,MAAM,YAAY;EACpC,CAAC;CAEL;CAEA,OAAO,QAAQ,MAAM,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AAC5D;AAEA,eAAsB,0BACpB,MACA,eACA,cACmC;CACnC,MAAM,WAAW,2BAA2B,YAAY;CACxD,MAAM,EAAE,cAAc,MAAM,sBAAsB,MAAM,aAAa;CACrE,MAAM,eAAe,QAAQ,WAAW,QAAQ;CAChD,IAAI,CAAC,aAAa,cAAc,SAAS,GAAG,MAAM,IAAI,MAAM,wBAAwB;CAEpF,MAAM,mBAAmB,MAAM,mBAAmB,cAAc,SAAS;CACzE,IAAI,CAAC,kBAAkB,OAAO;EAAE,eAAe;EAAW,MAAM;EAAU,UAAU;EAAc,SAAS,CAAC;CAAE;CAG9G,IAAI,EAAC,MADc,KAAK,gBAAgB,EAAA,CAC9B,YAAY,GAAG,OAAO;EAAE,eAAe;EAAW,MAAM;EAAU,UAAU;EAAkB,SAAS,CAAC;CAAE;CAEpH,OAAO;EACL,eAAe;EACf,MAAM;EACN,UAAU;EACV,SAAS,MAAM,oBAAoB,gBAAgB;CACrD;AACF;AAEA,eAAsB,kBAAkB,MAAc,eAAuB,MAAsC;CAGjH,2BAFiB,mBAAmB,MAAM,MACd,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,EACR;CACvC,MAAM,EACJ,WACA,MAAM,cACN,iBACE,MAAM,8BAA8B,MAAM,eAAe,IAAI;CACjE,MAAM,OAAO,MAAM,MAAM,YAAY;CACrC,IAAI,KAAK,YAAY,GAAG,MAAM,IAAI,MAAM,qBAAqB;CAC7D,IAAI,CAAC,KAAK,OAAO,GAAG,MAAM,IAAI,MAAM,uBAAuB;CAE3D,MAAM,cAAc,KAAK,IAAI,KAAK,MAAM,mBAAmB;CAC3D,MAAM,gBAAgB,OAAO,MAAM,WAAW;CAC9C,MAAM,SAAS,MAAM,KAAK,cAAc,GAAG;CAC3C,IAAI;EACF,MAAM,OAAO,KAAK,eAAe,GAAG,aAAa,CAAC;CACpD,UAAU;EACR,MAAM,OAAO,MAAM;CACrB;CAEA,IAAI;EACF,MAAM,UAAU,aAAa,OAAO,aAAa;EACjD,OAAO;GACL,eAAe;GACf,MAAM;GACN,MAAM,aAAa,MAAM,GAAG,CAAC,CAAC,IAAI,KAAK;GACvC,MAAM,KAAK;GACX,WAAW,KAAK,MAAM,YAAY;GAClC,aAAa;GACb;GACA,WAAW,KAAK,OAAO;EACzB;CACF,QAAQ;EACN,OAAO;GACL,eAAe;GACf,MAAM;GACN,MAAM,aAAa,MAAM,GAAG,CAAC,CAAC,IAAI,KAAK;GACvC,MAAM,KAAK;GACX,WAAW,KAAK,MAAM,YAAY;GAClC,aAAa;EACf;CACF;AACF;AAEA,eAAsB,cAAc,MAAc,eAAiD;CACjG,MAAM,UAAU,MAAM,0BAA0B,MAAM,eAAe,YAAY;CACjF,OAAO;EACL,UAAU,QAAQ;EAClB,eAAe,QAAQ;EACvB,SAAS,QAAQ;CACnB;AACF;;;;;;;AAuBA,eAAe,yBACb,GACA,MACA,eACsC;CACtC,IAAI,CAAC,MAAM,OAAO;CAClB,MAAM,UAAU,MAAM,KAAK,SAAS,eAAe,aAAa;CAChE,IAAI,CAAC,SAAS,OAAO;CACrB,IAAI,KAAK,KAAK,QAAQ,GAAG;EACvB,MAAM,KAAK,KAAK,WAAW,CAAC;EAC5B,MAAM,SAAS,KAAK,KAAK,OAAO,CAAC;EACjC,IAAI,CAAC,UAAU,OAAO,UAAU,QAAQ,SAAS,OAAO,WAAW,QAAQ,QACzE,MAAM,IAAI,MAAM,8CAA8C;CAElE;CACA,OAAO;AACT;AAEA,eAAsB,2BACpB,YACA,SACA,UACgC;CAChC,MAAM,eAAe,SAAS,KAAK;CACnC,IAAI,CAAC,cAAc,MAAM,IAAI,MAAM,wCAAwC;CAK3E,MAAM,gCAAgC,QAAQ,SAAS;CAEvD,MAAM,SAAS,cAAc,mBAAmB,QAAQ,EAAE,CAAC;CAC3D,OAAO;EACL,eAAe,QAAQ;EACvB,UAAU;EACV,OAAO,MAAM,WAAW,UAAU;GAAE,YAAY,QAAQ;GAAW,UAAU;EAAa,CAAC;EAC3F,GAAI,UAAU,OAAO,MAAM,SAAS,IAChC,EAAE,cAAc,OAAO,MAAM,KAAI,UAAS;GAAE,MAAM,KAAK;GAAM,QAAQ,KAAK;EAAO,EAAE,EAAE,IACrF,CAAC;CACP;AACF;;;;;;;;;;AAmBA,eAAe,eAAe,SAAqE;CACjG,MAAM,QAAQ,mBAAmB,QAAQ,EAAE;CAG3C,MAAM,SAAS,cAAc,KAAK;CAClC,IAAI,CAAC,SAAS,CAAC,QAAQ,OAAO;CAC9B,MAAM,UAAU,YAAY,MAAM,OAAO;CACzC,OAAO;EACL;EACA,YAAY,IAAI,kBAAkB;GAAE;GAAS,SAAS,OAAO;EAAK,CAAC;EACnE;EACA,cAAc,uBAAuB,SAAS,QAAQ,MAAM,OAAO;CACrE;AACF;;;;;;;AAQA,SAAS,uBACP,SACA,QACA,SACqB;CACrB,MAAM,cAAc,OAAO,MAAM,SAAS,IAAI,uBAAuB,QAAQ,SAAS,IAAI,KAAA;CAC1F,MAAM,wBAAQ,IAAI,IAAoB;CACtC,KAAK,MAAM,QAAQ,OAAO,OAAO;EAC/B,IAAI,OAAO,MAAM,WAAW,KAAK,KAAK,KAAK,YAAY,MAAM,QAAQ,YAAY,GAAG;GAClF,MAAM,IAAI,KAAK,MAAM,QAAQ,UAAU;GACvC;EACF;EACA,MAAM,QAAQ,aAAa,aAAa,MAAK,cAAa,UAAU,KAAK,YAAY,MAAM,KAAK,KAAK,YAAY,CAAC;EAGlH,MAAM,IAAI,KAAK,MAAM,OAAO,iBAAiB,MAAM;CACrD;CACA,OAAO;AACT;;;;;;;;AASA,SAAS,kBACP,QACA,MACA,OACqD;CACrD,MAAM,WAAW,mBAAmB,MAAM,KAAK;CAC/C,IAAI,OAAO,MAAM,WAAW,GAAG,OAAO;EAAE,MAAM,OAAO,MAAM;EAAK,UAAU;CAAS;CACnF,MAAM,OAAO,OAAO,MACjB,QAAO,cAAa,SAAS,WAAW,UAAU,MAAM,KAAK,SAAS,SAAS,UAAU,OAAO,MAAM,CAAC,CACvG,UAAU,GAAG,MAAM,EAAE,OAAO,SAAS,EAAE,OAAO,MAAM,CAAC,CAAC;CACzD,IAAI,CAAC,MAAM,MAAM,IAAI,MAAM,GAAG,MAAM,iDAAiD;CACrF,OAAO;EAAE;EAAM,UAAU,mBAAmB,SAAS,MAAM,KAAK,OAAO,MAAM,GAAG,KAAK;CAAE;AACzF;;AAGA,eAAsB,wBACpB,SACA,cACmC;CACnC,MAAM,WAAW,2BAA2B,YAAY;CACxD,MAAM,SAAS,MAAM,eAAe,OAAO;CAC3C,MAAM,WAAWA,MAAU,KAAK,QAAQ,OAAO,QAAQ,IAAI,QAAQ;CACnE,MAAM,QAAkC;EAAE,eAAe,QAAQ;EAAW,MAAM;EAAU;EAAU,SAAS,CAAC;CAAE;CAClH,IAAI,CAAC,QAAQ,OAAO;CAKpB,MAAM,aAAa,IAAI,SAAS,QAAQ,MAAM,OAAO,EAAE;CACvD,MAAM,SAAS,MAAM,OAAO,QAAQ,eAClC,MACA,CACE,MACA,WAAW,WAAW,WAAW,WAAW,iEAC9C,GACA;EAAE,SAAS;EAAQ,kBAAkB;CAAS,CAChD;CACA,IAAI,OAAO,aAAa,GAAG,OAAO;CAElC,MAAM,UAAoC,CAAC;CAC3C,KAAK,MAAM,QAAQ,OAAO,OAAO,MAAM,IAAI,GAAG;EAC5C,IAAI,CAAC,MAAM;EACX,MAAM,CAAC,MAAM,SAAS,UAAU,GAAG,aAAa,KAAK,MAAM,GAAI;EAC/D,MAAM,WAAW,UAAU,KAAK,GAAI;EACpC,IAAI,CAAC,YAAY,CAAC,SAAS,WAAW,GAAG,SAAS,EAAE,GAAG;EACvD,MAAM,eAAe,SAAS,MAAM,SAAS,SAAS,CAAC;EACvD,QAAQ,KAAK;GACX,MAAMA,MAAU,SAAS,YAAY;GACrC,MAAM;GACN,MAAM,SAAS,MAAM,cAAc;GACnC,MAAM,SAAS,MAAM,IAAI,OAAO,OAAO,KAAK;GAC5C,4BAAW,IAAI,MAAM,OAAO,QAAQ,KAAK,KAAK,GAAI,EAAA,CAAE,YAAY;EAClE,CAAC;CACH;CACA,QAAQ,MAAM,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;CAEnD,OAAO;EAAE,eAAe,QAAQ;EAAW,MAAM;EAAU;EAAU;CAAQ;AAC/E;;AAGA,eAAsB,yBACpB,SACA,MACA,UAA6C,CAAC,GACtB;CACxB,MAAM,WAAW,mBAAmB,MAAM,MAAM;CAChD,IAAI,CAAC,QAAQ,qBAAqB,2BAA2B,SAAS,MAAM,GAAG,CAAC,CAAC,MAAM,EAAE;CAEzF,MAAM,SAAS,MAAM,eAAe,OAAO;CAC3C,IAAI,CAAC,QAAQ,MAAM,IAAI,MAAM,oCAAoC;CACjE,MAAM,EAAE,eAAe;CACvB,MAAM,OAAO,MAAM,WAAW,KAAK,QAAQ;CAC3C,IAAI,KAAK,SAAS,aAAa,MAAM,IAAI,MAAM,qBAAqB;CAEpE,MAAM,SAAU,MAAM,WAAW,SAAS,QAAQ;CAClD,MAAM,YAAY,OAAO,SAAS;CAClC,MAAM,OAAO;EACX,eAAe,QAAQ;EACvB,MAAM;EACN,MAAMA,MAAU,SAAS,QAAQ;EACjC,MAAM,OAAO;EACb,WAAW,KAAK,WAAW,YAAY;CACzC;CACA,IAAI;EACF,MAAM,UAAU,aAAa,OAAO,YAAY,OAAO,SAAS,GAAG,mBAAmB,IAAI,MAAM;EAChG,OAAO;GAAE,GAAG;GAAM,aAAa;GAAQ;GAAS;EAAU;CAC5D,QAAQ;EACN,OAAO;GAAE,GAAG;GAAM,aAAa;EAAc;CAC/C;AACF;AAEA,SAAS,aAAa,MAAqC;CACzD,IAAI,SAAS,MAAM,OAAO;CAC1B,IAAI,KAAK,SAAS,GAAG,KAAK,SAAS,QAAQ,SAAS,MAAM,OAAO;CACjE,IAAI,KAAK,SAAS,GAAG,GAAG,OAAO;CAC/B,IAAI,KAAK,SAAS,GAAG,GAAG,OAAO;CAC/B,IAAI,KAAK,SAAS,GAAG,GAAG,OAAO;CAC/B,IAAI,KAAK,SAAS,GAAG,GAAG,OAAO;CAC/B,OAAO;AACT;AAEA,SAAgB,sBAAsB,QAAmC;CACvE,MAAM,UAAU,OAAO,MAAM,IAAI;CACjC,MAAM,UAA6B,CAAC;CAEpC,KAAK,IAAI,QAAQ,GAAG,QAAQ,QAAQ,QAAQ,SAAS,GAAG;EACtD,MAAM,SAAS,QAAQ;EACvB,IAAI,CAAC,UAAU,OAAO,SAAS,GAAG;EAClC,MAAM,OAAO,OAAO,MAAM,GAAG,CAAC;EAC9B,MAAM,OAAO,OAAO,MAAM,CAAC;EAC3B,MAAM,SAAS,aAAa,IAAI;EAChC,IAAI,WAAW,aAAa,WAAW,UAAU;GAC/C,MAAM,eAAe,QAAQ,QAAQ;GACrC,IAAI,cAAc,SAAS;GAC3B,QAAQ,KAAK;IAAE;IAAM,cAAc,gBAAgB,KAAA;IAAW;GAAO,CAAC;GACtE;EACF;EACA,QAAQ,KAAK;GAAE;GAAM;EAAO,CAAC;CAC/B;CAEA,OAAO,QAAQ,UAAU,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AAChE;AACA,SAAgB,6BAA6B,QAAmC;CAC9E,MAAM,UAAU,OAAO,MAAM,IAAI;CACjC,MAAM,UAA6B,CAAC;CACpC,KAAK,IAAI,QAAQ,GAAG,QAAQ,QAAQ,SAAS;EAC3C,MAAM,OAAO,QAAQ;EACrB,IAAI,CAAC,MAAM;EACX,MAAM,SAAS,aAAa,IAAI;EAChC,IAAI,WAAW,aAAa,WAAW,UAAU;GAC/C,MAAM,eAAe,QAAQ;GAC7B,MAAM,OAAO,QAAQ;GACrB,IAAI,MAAM,QAAQ,KAAK;IAAE;IAAM,cAAc,gBAAgB,KAAA;IAAW;GAAO,CAAC;GAChF;EACF;EACA,MAAM,OAAO,QAAQ;EACrB,IAAI,MAAM,QAAQ,KAAK;GAAE;GAAM;EAAO,CAAC;CACzC;CACA,OAAO,QAAQ,UAAU,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AAChE;AAEA,SAAgB,0BAA0B,QAAgB;CACxD,MAAM,UAAU,OAAO,MAAM,IAAI;CACjC,MAAM,wBAAQ,IAAI,IAA0E;CAE5F,KAAK,IAAI,QAAQ,GAAG,QAAQ,QAAQ,QAAQ,SAAS,GAAG;EACtD,MAAM,SAAS,QAAQ;EACvB,IAAI,CAAC,QAAQ;EACb,MAAM,WAAW,OAAO,QAAQ,GAAI;EACpC,MAAM,YAAY,OAAO,QAAQ,KAAM,WAAW,CAAC;EACnD,IAAI,WAAW,KAAK,YAAY,GAAG;EAEnC,MAAM,gBAAgB,OAAO,MAAM,GAAG,QAAQ;EAC9C,MAAM,gBAAgB,OAAO,MAAM,WAAW,GAAG,SAAS;EAC1D,IAAI,OAAO,OAAO,MAAM,YAAY,CAAC;EACrC,IAAI,CAAC,MAAM;GACT,MAAM,cAAc,QAAQ,QAAQ;GACpC,IAAI,CAAC,aAAa;GAClB,OAAO;GACP,SAAS;EACX;EACA,IAAI,KAAK,WAAW,IAAI,GAAG,OAAO,KAAK,MAAM,CAAC;EAE9C,IAAI,kBAAkB,OAAO,kBAAkB,KAAK;GAClD,MAAM,IAAI,MAAM,EAAE,QAAQ,KAAK,CAAC;GAChC;EACF;EAEA,MAAM,YAAY,OAAO,aAAa;EACtC,MAAM,YAAY,OAAO,aAAa;EACtC,IAAI,OAAO,SAAS,SAAS,KAAK,OAAO,SAAS,SAAS,GAAG,MAAM,IAAI,MAAM;GAAE;GAAW;EAAU,CAAC;CACxG;CAEA,OAAO;AACT;AAEA,SAAS,4BAA4B,eAAyC;CAC5E,OAAO;EAAE;EAAe,WAAW;EAAO,SAAS,CAAC;CAAE;AACxD;AAEA,eAAe,6BACb,SACA,SACA,YACiB;CACjB,MAAM,SAAS,MAAM,QAAQ,eAAe,OAAO;EAAC;EAAM;EAAS;EAAc;EAAQ,UAAU;CAAY,GAAG,EAChH,SAAS,IACX,CAAC;CACD,MAAM,MAAM,OAAO,OAAO,KAAK;CAC/B,OAAO,OAAO,aAAa,KAAK,qBAAqB,KAAK,GAAG,IAAI,MAAM;AACzE;;AAKA,eAAe,sBACb,SACA,SACA,YAC4B;CAC5B,MAAM,iBAAiB,MAAM,6BAA6B,SAAS,SAAS,UAAU;CAItF,MAAM,CAAC,cAAc,eAAe,eAAe,MAAM,QAAQ,IAAI;EACnE,QAAQ,eAAe,OAAO;GAAC;GAAM;GAAS;GAAU;GAAkB;GAAM;EAAuB,GAAG;GACxG,SAAS;GACT,kBAAkB;EACpB,CAAC;EACD,QAAQ,eAAe,OAAO;GAAC;GAAM;GAAS;GAAQ;GAAiB;GAAM;GAAkB;EAAc,GAAG;GAC9G,SAAS;GACT,kBAAkB;EACpB,CAAC;EACD,QAAQ,eAAe,MAAM;GAAC;GAAM;GAA0B;GAAsB;GAAS;EAAc,GAAG;GAC5G,SAAS;GACT,kBAAkB;EACpB,CAAC;CACH,CAAC;CACD,IAAI,aAAa,aAAa,GAAG,OAAO;EAAE,WAAW;EAAO,SAAS,CAAC;CAAE;CAExE,MAAM,gBAAgB,sBAAsB,aAAa,MAAM;CAC/D,MAAM,iBAAiB,cAAc,aAAa,IAAI,6BAA6B,cAAc,MAAM,IAAI,CAAC;CAC5G,MAAM,gBAAgB,IAAI,IAAI,eAAe,KAAI,WAAU,CAAC,OAAO,MAAM,MAAM,CAAC,CAAC;CACjF,KAAK,MAAM,UAAU,eACnB,IAAI,cAAc,aAAa,KAAK,OAAO,WAAW,eAAe,OAAO,WAAW,cACrF,cAAc,IAAI,OAAO,MAAM,MAAM;CAGzC,MAAM,UAAU,CAAC,GAAG,cAAc,OAAO,CAAC,CAAC,CAAC,UAAU,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;CAC3F,IAAI,YAAY,aAAa,GAAG,OAAO;EAAE,WAAW;EAAM;CAAQ;CAElE,MAAM,QAAQ,0BAA0B,YAAY,MAAM;CAC1D,IAAI,YAAY;CAChB,IAAI,YAAY;CAOhB,OAAO;EAAE,WAAW;EAAM,SAND,QAAQ,KAAI,WAAU;GAC7C,MAAM,cAAc,MAAM,IAAI,OAAO,IAAI;GACzC,aAAa,aAAa,aAAa;GACvC,aAAa,aAAa,aAAa;GACvC,OAAO;IAAE,GAAG;IAAQ,GAAG;GAAY;EACrC,CACkD;EAAG;EAAW;CAAU;AAC5E;AAEA,SAAS,aAAa,QAAyB,QAAiC;CAC9E,IAAI,CAAC,QAAQ,OAAO;CACpB,OAAO;EACL,GAAG;EACH,MAAM,GAAG,SAAS,OAAO;EACzB,GAAI,OAAO,eAAe,EAAE,cAAc,GAAG,SAAS,OAAO,eAAe,IAAI,CAAC;CACnF;AACF;AAEA,eAAsB,4BAA4B,SAA0D;CAC1G,MAAM,SAAS,MAAM,eAAe,OAAO;CAC3C,IAAI,CAAC,QAAQ,OAAO,4BAA4B,QAAQ,SAAS;CACjE,MAAM,EAAE,QAAQ,cAAc,UAAU;CAGxC,IAAI,OAAO,MAAM,WAAW,GAAG;EAC7B,MAAM,OAAO,OAAO,MAAM;EAC1B,MAAM,SAAS,MAAM,sBAAsB,OAAO,SAAS,KAAK,KAAK,MAAM,IAAI,KAAK,IAAI,KAAK,QAAQ,UAAU;EAC/G,IAAI,CAAC,OAAO,WAAW,OAAO,4BAA4B,QAAQ,SAAS;EAC3E,OAAO;GAAE,eAAe,QAAQ;GAAW,GAAG;EAAO;CACvD;CAKA,MAAM,SAAuC,MAAM,QAAQ,IACzD,OAAO,MAAM,IAAI,OAAM,SAAQ;EAC7B,MAAM,SAAS,MAAM,sBAAsB,OAAO,SAAS,KAAK,KAAK,MAAM,IAAI,KAAK,IAAI,CAAE;EAC1F,OAAO;GACL,MAAM,KAAK;GACX,QAAQ,KAAK;GACb,GAAG;GACH,SAAS,OAAO,QAAQ,KAAI,WAAU,aAAa,QAAQ,KAAK,MAAM,CAAC;EACzE;CACF,CAAC,CACH;CACA,MAAM,YAAY,OAAO,QAAO,UAAS,MAAM,SAAS;CACxD,MAAM,UAAU,UAAU,SAAQ,UAAS,MAAM,OAAO,CAAC,CAAC,UAAU,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;CACzG,MAAM,UAAU,UAAU,QAAO,UAAS,MAAM,cAAc,KAAA,CAAS;CACvE,MAAM,SACJ,QAAQ,SAAS,IACb;EACE,WAAW,QAAQ,QAAQ,KAAK,UAAU,OAAO,MAAM,aAAa,IAAI,CAAC;EACzE,WAAW,QAAQ,QAAQ,KAAK,UAAU,OAAO,MAAM,aAAa,IAAI,CAAC;CAC3E,IACA,CAAC;CACP,OAAO;EACL,eAAe,QAAQ;EACvB,WAAW,UAAU,SAAS;EAC9B;EACA,GAAG;EACH,cAAc;CAChB;AACF;AAEA,eAAe,sBAAsB,SAA4B,MAAgB,eAAe,OAAO;CACrG,OAAO,QAAQ,eACb,MACA;EAAC;EAAM;EAAyB;EAAmB,eAAe,MAAM;EAAK,GAAG;CAAI,GACpF,EAAE,SAAS,IAAO,CACpB;AACF;AAEA,SAAS,cAAc,aAA6B;CAClD,MAAM,QAAQ,YAAY,SAAS,GAAG,cAAc,CAAC,CAAC,SAAS,MAAM;CACrE,MAAM,cAAc,MAAM,YAAY,IAAI;CAC1C,IAAI,eAAe,GAAG,OAAO,MAAM,MAAM,GAAG,cAAc,CAAC;CAC3D,OAAO,MAAM,QAAQ,YAAY,EAAE;AACrC;AAEA,eAAsB,yBACpB,SACA,MACA,cACwB;CACxB,MAAM,WAAW,mBAAmB,MAAM,MAAM;CAChD,IAAI,cAAc,mBAAmB,cAAc,cAAc;CACjE,MAAM,SAAS,MAAM,eAAe,OAAO;CAC3C,IAAI,CAAC,QAAQ,MAAM,IAAI,MAAM,oCAAoC;CAIjE,MAAM,SAAS,kBAAkB,OAAO,QAAQ,UAAU,MAAM;CAChE,MAAM,WAAW,eAAe,kBAAkB,OAAO,QAAQ,cAAc,cAAc,IAAI,KAAA;CACjG,IAAI,YAAY,SAAS,SAAS,OAAO,MACvC,MAAM,IAAI,MAAM,yDAAyD;CAE3E,MAAM,UAAU,OAAO,KAAK;CAC5B,MAAM,iBAAiB,MAAM,6BAC3B,OAAO,SACP,SACA,OAAO,aAAa,IAAI,OAAO,KAAK,IAAI,CAC1C;CAEA,MAAM,YAAY,WAAW,CAAC,SAAS,UAAU,OAAO,QAAQ,IAAI,CAAC,OAAO,QAAQ;CACpF,IAAI,SAAS,MAAM,sBAAsB,OAAO,SAAS;EACvD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,GAAG;CACL,CAAC;CACD,IAAI,OAAO,aAAa,GAAG,MAAM,IAAI,MAAM,OAAO,UAAU,+BAA+B;CAE3F,IAAI,CAAC,OAAO,QAAQ;EAClB,MAAM,YAAY,MAAM,OAAO,QAAQ,eACrC,OACA;GAAC;GAAuB;GAAM;GAAS;GAAY;GAAY;GAAsB;GAAM,OAAO;EAAQ,GAC1G;GAAE,SAAS;GAAQ,kBAAkB;EAAS,CAChD;EACA,IAAI,UAAU,aAAa,KAAK,UAAU,OAAO,KAAK,GAAG;GACvD,SAAS,MAAM,sBACb,OAAO,SACP;IACE;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA,OAAO;GACT,GACA,IACF;GACA,IAAI,OAAO,aAAa,KAAK,OAAO,aAAa,GAC/C,MAAM,IAAI,MAAM,OAAO,UAAU,+BAA+B;EAEpE;CACF;CAEA,MAAM,cAAc,OAAO,KAAK,OAAO,MAAM;CAC7C,MAAM,YAAY,YAAY,SAAS;CACvC,OAAO;EACL,eAAe,QAAQ;EACvB,MAAM;EACN,OAAO,YAAY,cAAc,WAAW,IAAI,OAAO;EACvD;CACF;AACF;;;;;;;;AAsBA,SAAgB,gBAAgB,aAAuC;CACrE,MAAM,OAAO,cAAc,WAAW;CAEtC,OAAO;EACL,YAFe,sBAAsB,KAAK,QAEvB,KAAK,KAAK;EAC7B,MAAM,KAAK;EACX,UAAU,KAAK;EACf,QAAQ,KAAK;EACb,WAAW,KAAK;CAClB;AACF;;;;;;AAOA,SAAgB,cAAc,UAAwD,CAAC,GAAe;CACpG,MAAM,OAAO,cAAc,QAAQ,IAAI;CACvC,MAAM,YAAY,QAAQ;CAE1B,OAAO;EACL,iBAAiB,gBAAgB;GAC/B,QAAQ;GACR,cAAc;GACd,SAAS,OAAM,MAAK;IAClB,MAAM,OAAO,EAAE,IAAI,MAAM,MAAM;IAC/B,IAAI;KACF,MAAM,UAAU,MAAM,cAAc,MAAM,IAAI;KAC9C,OAAO,EAAE,KAAK,OAAO;IACvB,SAAS,OAAO;KACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;KACrE,OAAO,EAAE,KAAK,EAAE,OAAO,QAAQ,GAAG,GAAG;IACvC;GACF;EACF,CAAC;EACD,iBAAiB,uBAAuB;GACtC,QAAQ;GACR,cAAc;GACd,SAAS,OAAM,MAAK;IAClB,MAAM,OAAO,EAAE,IAAI,MAAM,MAAM;IAC/B,IAAI,CAAC,MAAM,OAAO,EAAE,KAAK,EAAE,OAAO,qCAAqC,GAAG,GAAG;IAC7E,IAAI;KACF,OAAO,EAAE,KAAK,MAAM,cAAc,MAAM,IAAI,CAAC;IAC/C,SAAS,OAAO;KACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;KACrE,MAAM,SAAS,YAAY,uCAAuC,MAAM;KACxE,OAAO,EAAE,KAAK,EAAE,OAAO,QAAQ,GAAG,MAAM;IAC1C;GACF;EACF,CAAC;EACD,iBAAiB,gCAAgC;GAC/C,QAAQ;GACR,cAAc;GACd,SAAS,OAAM,MAAK;IAClB,MAAM,gBAAgB,EAAE,IAAI,MAAM,eAAe;IACjD,MAAM,eAAe,EAAE,IAAI,MAAM,MAAM;IACvC,IAAI,CAAC,eAAe,OAAO,EAAE,KAAK,EAAE,OAAO,8CAA8C,GAAG,GAAG;IAC/F,IAAI,CAAC,cAAc,OAAO,EAAE,KAAK,EAAE,OAAO,qCAAqC,GAAG,GAAG;IACrF,IAAI;KACF,MAAM,UAAU,MAAM,yBAAyB,MAAM,CAAC,GAAG,WAAW,aAAa;KACjF,IAAI,WAAW,WACb,OAAO,EAAE,KAAK,MAAM,wBAAwB,SAAS,YAAY,CAAC;KAEpE,OAAO,EAAE,KAAK,MAAM,0BAA0B,MAAM,eAAe,YAAY,CAAC;IAClF,SAAS,OAAO;KACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;KACrE,MAAM,SACJ,QAAQ,SAAS,SAAS,KAC1B,QAAQ,SAAS,UAAU,KAC3B,QAAQ,SAAS,SAAS,KAC1B,QAAQ,SAAS,cAAc,KAC/B,QAAQ,SAAS,eAAe,IAC5B,MACA;KACN,OAAO,EAAE,KAAK,EAAE,OAAO,QAAQ,GAAG,MAAM;IAC1C;GACF;EACF,CAAC;EACD,iBAAiB,wBAAwB;GACvC,QAAQ;GACR,cAAc;GACd,SAAS,OAAM,MAAK;IAClB,MAAM,gBAAgB,EAAE,IAAI,MAAM,eAAe;IACjD,MAAM,WAAW,EAAE,IAAI,MAAM,UAAU,CAAC,EAAE,KAAK;IAC/C,IAAI,CAAC,eAAe,OAAO,EAAE,KAAK,EAAE,OAAO,8CAA8C,GAAG,GAAG;IAC/F,IAAI,CAAC,UAAU,OAAO,EAAE,KAAK,EAAE,OAAO,yCAAyC,GAAG,GAAG;IACrF,IAAI;KACF,MAAM,UAAU,MAAM,yBAAyB,MAAM,CAAC,GAAG,WAAW,aAAa;KACjF,IAAI,CAAC,WAAW,CAAC,WAAW,OAAO,EAAE,KAAK,EAAE,OAAO,qCAAqC,GAAG,GAAG;KAC9F,OAAO,EAAE,KAAK,MAAM,2BAA2B,UAAU,YAAY,SAAS,QAAQ,CAAC;IACzF,SAAS,OAAO;KACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;KACrE,MAAM,SAAS,QAAQ,SAAS,eAAe,IAAI,MAAM;KACzD,OAAO,EAAE,KAAK,EAAE,OAAO,QAAQ,GAAG,MAAM;IAC1C;GACF;EACF,CAAC;EACD,iBAAiB,0BAA0B;GACzC,QAAQ;GACR,cAAc;GACd,SAAS,OAAM,MAAK;IAClB,MAAM,gBAAgB,EAAE,IAAI,MAAM,eAAe;IACjD,IAAI,CAAC,eAAe,OAAO,EAAE,KAAK,EAAE,OAAO,8CAA8C,GAAG,GAAG;IAC/F,IAAI;KACF,MAAM,UAAU,MAAM,yBAAyB,MAAM,CAAC,GAAG,WAAW,aAAa;KACjF,IAAI,CAAC,WAAW,CAAC,WAAW,OAAO,EAAE,KAAK,4BAA4B,aAAa,CAAC;KACpF,OAAO,EAAE,KAAK,MAAM,4BAA4B,OAAO,CAAC;IAC1D,SAAS,OAAO;KACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;KACrE,OAAO,EAAE,KAAK,EAAE,OAAO,QAAQ,GAAG,QAAQ,SAAS,eAAe,IAAI,MAAM,GAAG;IACjF;GACF;EACF,CAAC;EACD,iBAAiB,+BAA+B;GAC9C,QAAQ;GACR,cAAc;GACd,SAAS,OAAM,MAAK;IAClB,MAAM,gBAAgB,EAAE,IAAI,MAAM,eAAe;IACjD,MAAM,OAAO,EAAE,IAAI,MAAM,MAAM;IAC/B,MAAM,eAAe,EAAE,IAAI,MAAM,cAAc;IAC/C,IAAI,CAAC,eAAe,OAAO,EAAE,KAAK,EAAE,OAAO,8CAA8C,GAAG,GAAG;IAC/F,IAAI,CAAC,MAAM,OAAO,EAAE,KAAK,EAAE,OAAO,qCAAqC,GAAG,GAAG;IAC7E,IAAI;KACF,MAAM,UAAU,MAAM,yBAAyB,MAAM,CAAC,GAAG,WAAW,aAAa;KACjF,IAAI,CAAC,WAAW,CAAC,WAAW,OAAO,EAAE,KAAK,EAAE,OAAO,qCAAqC,GAAG,GAAG;KAC9F,OAAO,EAAE,KAAK,MAAM,yBAAyB,SAAS,MAAM,YAAY,CAAC;IAC3E,SAAS,OAAO;KACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;KACrE,MAAM,SACJ,QAAQ,SAAS,UAAU,KAAK,QAAQ,SAAS,SAAS,KAAK,QAAQ,SAAS,eAAe,IAC3F,MACA;KACN,OAAO,EAAE,KAAK,EAAE,OAAO,QAAQ,GAAG,MAAM;IAC1C;GACF;EACF,CAAC;EACD,iBAAiB,uBAAuB;GACtC,QAAQ;GACR,cAAc;GACd,SAAS,OAAM,MAAK;IAClB,MAAM,gBAAgB,EAAE,IAAI,MAAM,eAAe;IACjD,MAAM,OAAO,EAAE,IAAI,MAAM,MAAM;IAC/B,MAAM,oBAAoB,EAAE,IAAI,MAAM,UAAU;IAChD,MAAM,WAAW,mBAAmB,KAAK;IACzC,IAAI,CAAC,eAAe,OAAO,EAAE,KAAK,EAAE,OAAO,8CAA8C,GAAG,GAAG;IAC/F,IAAI,CAAC,MAAM,OAAO,EAAE,KAAK,EAAE,OAAO,qCAAqC,GAAG,GAAG;IAC7E,IAAI,sBAAsB,KAAA,KAAa,CAAC,UACtC,OAAO,EAAE,KAAK,EAAE,OAAO,yCAAyC,GAAG,GAAG;IAExE,IAAI;KACF,MAAM,UAAU,MAAM,yBAAyB,MAAM,CAAC,GAAG,WAAW,aAAa;KACjF,IAAI,WAAW,WAAW;MACxB,IAAI,UAAU;OACZ,MAAM,WAAW,mBAAmB,MAAM,MAAM;OAEhD,IAAI,EAAC,MADiB,2BAA2B,UAAU,YAAY,SAAS,QAAQ,EAAA,CAC3E,MAAM,MAAK,SAAQ,KAAK,SAAS,QAAQ,GACpD,OAAO,EAAE,KAAK,EAAE,OAAO,wCAAwC,GAAG,GAAG;OAEvE,OAAO,EAAE,KAAK,MAAM,yBAAyB,SAAS,UAAU,EAAE,qBAAqB,KAAK,CAAC,CAAC;MAChG;MACA,OAAO,EAAE,KAAK,MAAM,yBAAyB,SAAS,IAAI,CAAC;KAC7D;KACA,IAAI,UAAU,OAAO,EAAE,KAAK,EAAE,OAAO,qCAAqC,GAAG,GAAG;KAChF,OAAO,EAAE,KAAK,MAAM,kBAAkB,MAAM,eAAe,IAAI,CAAC;IAClE,SAAS,OAAO;KACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;KACrE,MAAM,SACJ,QAAQ,SAAS,SAAS,KAC1B,QAAQ,SAAS,UAAU,KAC3B,QAAQ,SAAS,SAAS,KAC1B,QAAQ,SAAS,cAAc,KAC/B,QAAQ,SAAS,eAAe,IAC5B,MACA,QAAQ,SAAS,WAAW,IAC1B,MACA,QAAQ,SAAS,WAAW,IAC1B,MACA;KACV,OAAO,EAAE,KAAK,EAAE,OAAO,QAAQ,GAAG,MAAM;IAC1C;GACF;EACF,CAAC;EACD,iBAAiB,yBAAyB;GACxC,QAAQ;GACR,cAAc;GACd,SAAS,OAAM,MAAK;IAClB,MAAM,OAAO,EAAE,IAAI,MAAM,MAAM;IAC/B,IAAI,CAAC,MAAM,OAAO,EAAE,KAAK,EAAE,OAAO,qCAAqC,GAAG,GAAG;IAK7E,MAAM,WAAW,MAAM,mBAAmB,WAAW,IAAI,IAAI,QAAQ,IAAI,IAAI,QAAQ,MAAM,IAAI,GAAG,IAAI;IACtG,IAAI,CAAC,UAAU,OAAO,EAAE,KAAK,EAAE,OAAO,qCAAqC,GAAG,GAAG;IACjF,IAAI;KACF,OAAO,EAAE,KAAK,gBAAgB,QAAQ,CAAC;IACzC,SAAS,OAAO;KACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;KACrE,OAAO,EAAE,KAAK,EAAE,OAAO,QAAQ,GAAG,GAAG;IACvC;GACF;EACF,CAAC;CACH;AACF"}
|
|
1
|
+
{"version":3,"file":"fs.js","names":["posixPath"],"sources":["../../src/routes/fs.ts"],"sourcesContent":["import { lstat, open, readdir, realpath, stat } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { isAbsolute, join, posix as posixPath, resolve, sep } from 'node:path';\n\nimport { SandboxFilesystem } from '@mastra/code-sdk/agents/sandbox-filesystem';\nimport { detectProject, getResourceIdOverride } from '@mastra/code-sdk/utils/project';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { ApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\n\nimport { requireExec } from '../sandbox/materialization.js';\nimport type { ExecutableSandbox } from '../sandbox/materialization.js';\nimport { peekSessionSandbox, sessionLayout } from '../sandbox/session-sandbox.js';\nimport type { SessionLayout, SessionLayoutRepository } from '../sandbox/session-sandbox.js';\nimport { peekSessionEnvironment } from '../session/environment-state-processor.js';\nimport { waitForPendingFilesystemCapture } from '../session/filesystem-capture.js';\nimport type { FilesystemStorage } from '../storage/domains/filesystem/base.js';\nimport type { SourceControlSession } from '../storage/domains/source-control/base.js';\nimport type { RouteAuth } from './route.js';\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 interface WorkspaceFilesListing {\n /** The Factory session resource id. */\n workspacePath: string;\n /** The agent thread whose terminal file list was captured. */\n threadId: string;\n files: Array<{ path: string }>;\n /**\n * The environment repositories the paths are prefixed by, in position\n * order. Present only when the session's environment holds two or more\n * repositories; a one-repository session lists unprefixed paths.\n */\n repositories?: Array<{ slug: string; prefix: string }>;\n}\n\nexport type WorkspaceChangeStatus =\n | 'modified'\n | 'added'\n | 'deleted'\n | 'renamed'\n | 'copied'\n | 'untracked'\n | 'conflicted';\n\nexport interface WorkspaceChange {\n path: string;\n previousPath?: string;\n status: WorkspaceChangeStatus;\n additions?: number;\n deletions?: number;\n binary?: boolean;\n}\n\nexport interface WorkspaceChangesRepository {\n slug: string;\n /** The path prefix of this repository's changes in the flat list, e.g. `platform/`. */\n prefix: string;\n /** False when the checkout could not be inspected (missing, or git failed). */\n available: boolean;\n changes: WorkspaceChange[];\n additions?: number;\n deletions?: number;\n}\n\nexport interface WorkspaceChanges {\n workspacePath: string;\n available: boolean;\n /** Every repository's changes, paths prefixed by the repository directory in a multi-repository session. */\n changes: WorkspaceChange[];\n additions?: number;\n deletions?: number;\n /**\n * One group per environment repository, in position order. Present only\n * when the session's environment holds two or more repositories.\n */\n repositories?: WorkspaceChangesRepository[];\n}\n\nexport interface WorkspaceDiff {\n workspacePath: string;\n path: string;\n patch: 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 MAX_DIFF_BYTES = 512 * 1024;\nconst WORKSPACE_NUMSTAT_SCRIPT = `\nset -e\nworkdir=$1\nbase=$2\nindex_file=$(mktemp)\nuntracked_file=$(mktemp)\nobject_dir=$(mktemp -d)\ntrap 'rm -f \"$index_file\" \"$untracked_file\"; rm -rf \"$object_dir\"' EXIT\nrm -f \"$index_file\"\ngit -C \"$workdir\" diff --numstat -z --find-renames --no-ext-diff --no-textconv \"$base\"\ngit -C \"$workdir\" ls-files --others --exclude-standard -z >\"$untracked_file\"\ngit_dir=$(git -C \"$workdir\" rev-parse --absolute-git-dir)\nexport GIT_INDEX_FILE=\"$index_file\"\nexport GIT_OBJECT_DIRECTORY=\"$object_dir\"\nexport GIT_ALTERNATE_OBJECT_DIRECTORIES=\"$git_dir/objects\"\ngit -C \"$workdir\" read-tree --empty\ngit -C \"$workdir\" update-index --add -z --stdin <\"$untracked_file\"\nempty_tree=$(git -C \"$workdir\" hash-object -t tree /dev/null)\ngit -C \"$workdir\" diff --cached --numstat -z --no-ext-diff --no-textconv \"$empty_tree\"\n`;\nconst BOUNDED_GIT_DIFF_SCRIPT = `\nallow_exit_one=$1\nshift\nstatus_file=$(mktemp)\nstderr_file=$(mktemp)\ntrap 'rm -f \"$status_file\" \"$stderr_file\"' EXIT\n(\n git \"$@\" 2>\"$stderr_file\"\n printf '%s' \"$?\" >\"$status_file\"\n) | head -c ${MAX_DIFF_BYTES + 1}\nstatus=$(cat \"$status_file\")\ncase \"$status\" in\n 0|141) exit 0 ;;\n 1) [ \"$allow_exit_one\" = \"1\" ] && exit 0 ;;\nesac\ncat \"$stderr_file\" >&2\nexit \"\\${status:-1}\"\n`;\nconst TEXT_DECODER = new TextDecoder('utf-8', { fatal: true });\nconst APPROVED_RENDERED_ROOTS = new Set(['.artifacts']);\n\n/** Erase a route handler's path-parameterized context to a plain `Context`. */\nfunction loose(c: unknown): Context {\n return c as Context;\n}\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\n// ── Session-backed workspace access ──────────────────────────────────────────\n//\n// The web UI identifies a Factory session workspace by its session id (a UUID),\n// not by a server-local filesystem path — the session's files live inside the\n// session's sandbox (a remote VM on deployed factories). These helpers resolve\n// the session, enforce that the caller owns it, reattach to its sandbox, and\n// serve the approved rendered roots through `SandboxFilesystem`.\n\n/** Dependencies for resolving a `workspacePath` that is a Factory session id. */\nexport interface SessionFsDeps {\n auth: RouteAuth;\n sessions: { getBySessionId(sessionId: string): Promise<SourceControlSession | null> };\n filesystem: Pick<FilesystemStorage, 'listFiles'>;\n}\n\n/**\n * Resolve a `workspacePath` query param as a Factory session id. Returns the\n * session when one exists and the caller owns it, `null` when no session\n * matches (the caller should fall back to local-path handling), and throws\n * when a session exists but belongs to another tenant.\n */\nasync function resolveAuthorizedSession(\n c: Context,\n deps: SessionFsDeps | undefined,\n workspacePath: string,\n): Promise<SourceControlSession | null> {\n if (!deps) return null;\n const session = await deps.sessions.getBySessionId(workspacePath);\n if (!session) return null;\n if (deps.auth.enabled()) {\n await deps.auth.ensureUser(c);\n const tenant = deps.auth.tenant(c);\n if (!tenant || tenant.orgId !== session.orgId || tenant.userId !== session.userId) {\n throw new Error('Session is not available to the current user');\n }\n }\n return session;\n}\n\nexport async function listSessionFilesystemFiles(\n filesystem: Pick<FilesystemStorage, 'listFiles'>,\n session: SourceControlSession,\n threadId: string,\n): Promise<WorkspaceFilesListing> {\n const safeThreadId = threadId.trim();\n if (!safeThreadId) throw new Error('Missing required query param: threadId');\n\n // The turn-end capture no longer gates agent_end, so a reader refetching on\n // run completion could otherwise race it and serve the previous turn's\n // listing. Await the in-flight capture (bounded) before reading.\n await waitForPendingFilesystemCapture(session.sessionId);\n\n const layout = sessionLayout(peekSessionSandbox(session.id));\n return {\n workspacePath: session.sessionId,\n threadId: safeThreadId,\n files: await filesystem.listFiles({ resourceId: session.sessionId, threadId: safeThreadId }),\n ...(layout && layout.repos.length > 1\n ? { repositories: layout.repos.map(repo => ({ slug: repo.slug, prefix: repo.prefix })) }\n : {}),\n };\n}\n\ninterface SessionSandboxHandle {\n sandbox: ExecutableSandbox;\n filesystem: SandboxFilesystem;\n layout: SessionLayout;\n /** The branch each layout repository is compared against, by slug. */\n baseBranches: Map<string, string>;\n}\n\n/**\n * Resolve the session's sandbox from the per-process memo and wrap the\n * session layout's root in a `SandboxFilesystem`: the checkout for a\n * one-repository session, the workspace root (one directory per repository)\n * otherwise. Returns `null` when the session has no sandbox in this process\n * (never opened here, or evicted by retirement). This is a passive read path,\n * so it never constructs or provisions: the session's files come back the\n * next time the workspace is actually opened (e.g. by sending a message).\n */\nasync function sessionSandbox(session: SourceControlSession): Promise<SessionSandboxHandle | null> {\n const entry = peekSessionSandbox(session.id);\n // An unresolved workdir means the sandbox never started in this process —\n // nothing is materialized, so there are no files to browse.\n const layout = sessionLayout(entry);\n if (!entry || !layout) return null;\n const sandbox = requireExec(entry.sandbox);\n return {\n sandbox,\n // Session viewers read these files over HTTP, so a symlink must not lead\n // them outside the checkout.\n filesystem: new SandboxFilesystem({ sandbox, workdir: layout.root, verifySymlinks: true }),\n layout,\n baseBranches: repositoryBaseBranches(session, layout, entry.ownRepo),\n };\n}\n\n/**\n * The branch each layout repository is compared against: the session base\n * branch for the session's own repository (session branch vs base), the\n * default branch the boot recorded for every other one, `HEAD` (the checkout's\n * own `origin/HEAD`) until the boot has recorded it.\n */\nfunction repositoryBaseBranches(\n session: SourceControlSession,\n layout: SessionLayout,\n ownRepo: string,\n): Map<string, string> {\n const environment = layout.repos.length > 1 ? peekSessionEnvironment(session.sessionId) : undefined;\n const bases = new Map<string, string>();\n for (const repo of layout.repos) {\n if (layout.repos.length === 1 || repo.slug.toLowerCase() === ownRepo.toLowerCase()) {\n bases.set(repo.slug, session.baseBranch);\n continue;\n }\n const state = environment?.repositories.find(candidate => candidate.slug.toLowerCase() === repo.slug.toLowerCase());\n // The boot records the state at its end, after every setup command; until\n // then a secondary checkout compares against its own `origin/HEAD`.\n bases.set(repo.slug, state?.defaultBranch ?? 'HEAD');\n }\n return bases;\n}\n\n/**\n * Split a client path into the layout repository it belongs to and the path\n * inside that checkout. Longest prefix wins; the inside path is validated\n * again so a prefix cannot smuggle a traversal. A path matching no repository\n * (including a bare prefix) is not available: the file routes only serve the\n * checkouts, never the workspace root itself.\n */\nfunction resolveLayoutPath(\n layout: SessionLayout,\n path: string,\n label: string,\n): { repo: SessionLayoutRepository; relative: string } {\n const safePath = assertRelativePath(path, label);\n if (layout.repos.length === 1) return { repo: layout.repos[0]!, relative: safePath };\n const repo = layout.repos\n .filter(candidate => safePath.startsWith(candidate.prefix) && safePath.length > candidate.prefix.length)\n .toSorted((a, b) => b.prefix.length - a.prefix.length)[0];\n if (!repo) throw new Error(`${label} is not available in this session's repositories`);\n return { repo, relative: assertRelativePath(safePath.slice(repo.prefix.length), label) };\n}\n\n/** List an approved rendered root inside a Factory session's sandbox workdir. */\nexport async function listSessionRenderedPath(\n session: SourceControlSession,\n renderedRoot: string,\n): Promise<WorkspaceRenderedListing> {\n const safeRoot = assertApprovedRenderedRoot(renderedRoot);\n const handle = await sessionSandbox(session);\n const rootPath = posixPath.join(handle?.layout.root ?? '', safeRoot);\n const empty: WorkspaceRenderedListing = { workspacePath: session.sessionId, root: safeRoot, rootPath, entries: [] };\n if (!handle) return empty;\n\n // One round trip: emit \"type\\tsize\\tmtime\\tpath\" per entry. `safeRoot` comes\n // from a fixed allowlist so interpolating it (quoted) is safe. The listing is\n // parsed line by line, so retain all of it rather than the default 1 MiB tail.\n const quotedRoot = `'${rootPath.replace(/'/g, `'\\\\''`)}'`;\n const result = await handle.sandbox.executeCommand(\n 'sh',\n [\n '-c',\n `test -d ${quotedRoot} && find ${quotedRoot} -mindepth 1 -printf '%y\\\\t%s\\\\t%T@\\\\t%p\\\\n' 2>/dev/null || true`,\n ],\n { timeout: 30_000, maxRetainedBytes: Infinity },\n );\n if (result.exitCode !== 0) return empty;\n\n const entries: WorkspaceRenderedEntry[] = [];\n for (const line of result.stdout.split('\\n')) {\n if (!line) continue;\n const [type, sizeStr, mtimeStr, ...pathParts] = line.split('\\t');\n const fullPath = pathParts.join('\\t');\n if (!fullPath || !fullPath.startsWith(`${rootPath}/`)) continue;\n const relativePath = fullPath.slice(rootPath.length + 1);\n entries.push({\n name: posixPath.basename(relativePath),\n path: relativePath,\n type: type === 'd' ? 'directory' : 'file',\n size: type === 'd' ? 0 : Number(sizeStr) || 0,\n updatedAt: new Date((Number(mtimeStr) || 0) * 1000).toISOString(),\n });\n }\n entries.sort((a, b) => a.path.localeCompare(b.path));\n\n return { workspacePath: session.sessionId, root: safeRoot, rootPath, entries };\n}\n\n/** Read a file inside a session's sandbox. Paths outside rendered roots require a persisted-file allowlist check in the route. */\nexport async function readSessionWorkspaceFile(\n session: SourceControlSession,\n path: string,\n options: { allowUnapprovedPath?: boolean } = {},\n): Promise<WorkspaceFile> {\n const safePath = assertRelativePath(path, 'path');\n if (!options.allowUnapprovedPath) assertApprovedRenderedRoot(safePath.split('/')[0] ?? '');\n\n const handle = await sessionSandbox(session);\n if (!handle) throw new Error('Session workspace is not available');\n const { filesystem } = handle;\n const info = await filesystem.stat(safePath);\n if (info.type === 'directory') throw new Error('Path is a directory');\n\n const buffer = (await filesystem.readFile(safePath)) as Buffer;\n const truncated = buffer.length > MAX_TEXT_FILE_BYTES;\n const base = {\n workspacePath: session.sessionId,\n path: safePath,\n name: posixPath.basename(safePath),\n size: buffer.length,\n updatedAt: info.modifiedAt.toISOString(),\n };\n try {\n const content = TEXT_DECODER.decode(truncated ? buffer.subarray(0, MAX_TEXT_FILE_BYTES) : buffer);\n return { ...base, contentType: 'text', content, truncated };\n } catch {\n return { ...base, contentType: 'unsupported' };\n }\n}\n\nfunction changeStatus(code: string): WorkspaceChangeStatus {\n if (code === '??') return 'untracked';\n if (code.includes('U') || code === 'AA' || code === 'DD') return 'conflicted';\n if (code.includes('R')) return 'renamed';\n if (code.includes('C')) return 'copied';\n if (code.includes('D')) return 'deleted';\n if (code.includes('A')) return 'added';\n return 'modified';\n}\n\nexport function parseWorkspaceChanges(output: string): WorkspaceChange[] {\n const records = output.split('\\0');\n const changes: WorkspaceChange[] = [];\n\n for (let index = 0; index < records.length; index += 1) {\n const record = records[index];\n if (!record || record.length < 4) continue;\n const code = record.slice(0, 2);\n const path = record.slice(3);\n const status = changeStatus(code);\n if (status === 'renamed' || status === 'copied') {\n const previousPath = records[index + 1];\n if (previousPath) index += 1;\n changes.push({ path, previousPath: previousPath || undefined, status });\n continue;\n }\n changes.push({ path, status });\n }\n\n return changes.toSorted((a, b) => a.path.localeCompare(b.path));\n}\nexport function parseWorkspaceTrackedChanges(output: string): WorkspaceChange[] {\n const records = output.split('\\0');\n const changes: WorkspaceChange[] = [];\n for (let index = 0; index < records.length;) {\n const code = records[index++];\n if (!code) continue;\n const status = changeStatus(code);\n if (status === 'renamed' || status === 'copied') {\n const previousPath = records[index++];\n const path = records[index++];\n if (path) changes.push({ path, previousPath: previousPath || undefined, status });\n continue;\n }\n const path = records[index++];\n if (path) changes.push({ path, status });\n }\n return changes.toSorted((a, b) => a.path.localeCompare(b.path));\n}\n\nexport function parseWorkspaceChangeStats(output: string) {\n const records = output.split('\\0');\n const stats = new Map<string, { additions?: number; deletions?: number; binary?: boolean }>();\n\n for (let index = 0; index < records.length; index += 1) {\n const record = records[index];\n if (!record) continue;\n const firstTab = record.indexOf('\\t');\n const secondTab = record.indexOf('\\t', firstTab + 1);\n if (firstTab < 0 || secondTab < 0) continue;\n\n const additionsText = record.slice(0, firstTab);\n const deletionsText = record.slice(firstTab + 1, secondTab);\n let path = record.slice(secondTab + 1);\n if (!path) {\n const renamedPath = records[index + 2];\n if (!renamedPath) continue;\n path = renamedPath;\n index += 2;\n }\n if (path.startsWith('./')) path = path.slice(2);\n\n if (additionsText === '-' || deletionsText === '-') {\n stats.set(path, { binary: true });\n continue;\n }\n\n const additions = Number(additionsText);\n const deletions = Number(deletionsText);\n if (Number.isFinite(additions) && Number.isFinite(deletions)) stats.set(path, { additions, deletions });\n }\n\n return stats;\n}\n\nfunction unavailableWorkspaceChanges(workspacePath: string): WorkspaceChanges {\n return { workspacePath, available: false, changes: [] };\n}\n\nasync function resolveSessionComparisonBase(\n sandbox: ExecutableSandbox,\n workdir: string,\n baseBranch: string,\n): Promise<string> {\n const result = await sandbox.executeCommand('git', ['-C', workdir, 'merge-base', 'HEAD', `origin/${baseBranch}`], {\n timeout: 30_000,\n });\n const sha = result.stdout.trim();\n return result.exitCode === 0 && /^[0-9a-f]{40,64}$/i.test(sha) ? sha : 'HEAD';\n}\n\ntype RepositoryChanges = Pick<WorkspaceChangesRepository, 'available' | 'changes' | 'additions' | 'deletions'>;\n\n/** The changes of one checkout against its comparison base; `available: false` when git cannot inspect it. */\nasync function listRepositoryChanges(\n sandbox: ExecutableSandbox,\n workdir: string,\n baseBranch: string,\n): Promise<RepositoryChanges> {\n const comparisonBase = await resolveSessionComparisonBase(sandbox, workdir, baseBranch);\n\n // These outputs are parsed record by record (-z) and per path, so retain all\n // of them rather than the default 1 MiB tail, which would drop or split entries.\n const [statusResult, trackedResult, statsResult] = await Promise.all([\n sandbox.executeCommand('git', ['-C', workdir, 'status', '--porcelain=v1', '-z', '--untracked-files=all'], {\n timeout: 30_000,\n maxRetainedBytes: Infinity,\n }),\n sandbox.executeCommand('git', ['-C', workdir, 'diff', '--name-status', '-z', '--find-renames', comparisonBase], {\n timeout: 30_000,\n maxRetainedBytes: Infinity,\n }),\n sandbox.executeCommand('sh', ['-c', WORKSPACE_NUMSTAT_SCRIPT, 'mastracode-numstat', workdir, comparisonBase], {\n timeout: 30_000,\n maxRetainedBytes: Infinity,\n }),\n ]);\n if (statusResult.exitCode !== 0) return { available: false, changes: [] };\n\n const statusChanges = parseWorkspaceChanges(statusResult.stdout);\n const trackedChanges = trackedResult.exitCode === 0 ? parseWorkspaceTrackedChanges(trackedResult.stdout) : [];\n const changesByPath = new Map(trackedChanges.map(change => [change.path, change]));\n for (const change of statusChanges) {\n if (trackedResult.exitCode !== 0 || change.status === 'untracked' || change.status === 'conflicted') {\n changesByPath.set(change.path, change);\n }\n }\n const changes = [...changesByPath.values()].toSorted((a, b) => a.path.localeCompare(b.path));\n if (statsResult.exitCode !== 0) return { available: true, changes };\n\n const stats = parseWorkspaceChangeStats(statsResult.stdout);\n let additions = 0;\n let deletions = 0;\n const changesWithStats = changes.map(change => {\n const changeStats = stats.get(change.path);\n additions += changeStats?.additions ?? 0;\n deletions += changeStats?.deletions ?? 0;\n return { ...change, ...changeStats };\n });\n return { available: true, changes: changesWithStats, additions, deletions };\n}\n\nfunction prefixChange(change: WorkspaceChange, prefix: string): WorkspaceChange {\n if (!prefix) return change;\n return {\n ...change,\n path: `${prefix}${change.path}`,\n ...(change.previousPath ? { previousPath: `${prefix}${change.previousPath}` } : {}),\n };\n}\n\nexport async function listSessionWorkspaceChanges(session: SourceControlSession): Promise<WorkspaceChanges> {\n const handle = await sessionSandbox(session);\n if (!handle) return unavailableWorkspaceChanges(session.sessionId);\n const { layout, baseBranches: bases } = handle;\n\n // One repository: the response is exactly what it was before layouts.\n if (layout.repos.length === 1) {\n const repo = layout.repos[0]!;\n const result = await listRepositoryChanges(handle.sandbox, repo.dir, bases.get(repo.slug) ?? session.baseBranch);\n if (!result.available) return unavailableWorkspaceChanges(session.sessionId);\n return { workspacePath: session.sessionId, ...result };\n }\n\n // Several: inspect every checkout in parallel, one group each. A failing\n // one (missing checkout, git error) is unavailable on its own, the others\n // still answer.\n const groups: WorkspaceChangesRepository[] = await Promise.all(\n layout.repos.map(async repo => {\n const result = await listRepositoryChanges(handle.sandbox, repo.dir, bases.get(repo.slug)!);\n return {\n slug: repo.slug,\n prefix: repo.prefix,\n ...result,\n changes: result.changes.map(change => prefixChange(change, repo.prefix)),\n };\n }),\n );\n const available = groups.filter(group => group.available);\n const changes = available.flatMap(group => group.changes).toSorted((a, b) => a.path.localeCompare(b.path));\n const counted = available.filter(group => group.additions !== undefined);\n const totals =\n counted.length > 0\n ? {\n additions: counted.reduce((sum, group) => sum + (group.additions ?? 0), 0),\n deletions: counted.reduce((sum, group) => sum + (group.deletions ?? 0), 0),\n }\n : {};\n return {\n workspacePath: session.sessionId,\n available: available.length > 0,\n changes,\n ...totals,\n repositories: groups,\n };\n}\n\nasync function executeBoundedGitDiff(sandbox: ExecutableSandbox, args: string[], allowExitOne = false) {\n return sandbox.executeCommand(\n 'sh',\n ['-c', BOUNDED_GIT_DIFF_SCRIPT, 'mastracode-diff', allowExitOne ? '1' : '0', ...args],\n { timeout: 30_000 },\n );\n}\n\nfunction truncatePatch(patchBuffer: Buffer): string {\n const patch = patchBuffer.subarray(0, MAX_DIFF_BYTES).toString('utf8');\n const lastNewline = patch.lastIndexOf('\\n');\n if (lastNewline >= 0) return patch.slice(0, lastNewline + 1);\n return patch.replace(/\\uFFFD+$/, '');\n}\n\nexport async function readSessionWorkspaceDiff(\n session: SourceControlSession,\n path: string,\n previousPath?: string,\n): Promise<WorkspaceDiff> {\n const safePath = assertRelativePath(path, 'path');\n if (previousPath) assertRelativePath(previousPath, 'previousPath');\n const handle = await sessionSandbox(session);\n if (!handle) throw new Error('Session workspace is not available');\n // The client sends the prefixed paths it was listed; git runs inside the\n // checkout they belong to, on the paths relative to it. A rename never\n // crosses checkouts, so both halves must resolve to the same repository.\n const target = resolveLayoutPath(handle.layout, safePath, 'path');\n const previous = previousPath ? resolveLayoutPath(handle.layout, previousPath, 'previousPath') : undefined;\n if (previous && previous.repo !== target.repo) {\n throw new Error('previousPath is not available in the repository of path');\n }\n const workdir = target.repo.dir;\n const comparisonBase = await resolveSessionComparisonBase(\n handle.sandbox,\n workdir,\n handle.baseBranches.get(target.repo.slug)!,\n );\n\n const pathspecs = previous ? [previous.relative, target.relative] : [target.relative];\n let result = await executeBoundedGitDiff(handle.sandbox, [\n '--literal-pathspecs',\n '-C',\n workdir,\n 'diff',\n '--find-renames',\n '--no-ext-diff',\n '--no-color',\n '--unified=3',\n comparisonBase,\n '--',\n ...pathspecs,\n ]);\n if (result.exitCode !== 0) throw new Error(result.stderr || 'Unable to read workspace diff');\n\n if (!result.stdout) {\n const untracked = await handle.sandbox.executeCommand(\n 'git',\n ['--literal-pathspecs', '-C', workdir, 'ls-files', '--others', '--exclude-standard', '--', target.relative],\n { timeout: 30_000, maxRetainedBytes: Infinity },\n );\n if (untracked.exitCode === 0 && untracked.stdout.trim()) {\n result = await executeBoundedGitDiff(\n handle.sandbox,\n [\n '-C',\n workdir,\n 'diff',\n '--no-index',\n '--no-ext-diff',\n '--no-color',\n '--unified=3',\n '--',\n '/dev/null',\n target.relative,\n ],\n true,\n );\n if (result.exitCode !== 0 && result.exitCode !== 1) {\n throw new Error(result.stderr || 'Unable to read workspace diff');\n }\n }\n }\n\n const patchBuffer = Buffer.from(result.stdout);\n const truncated = patchBuffer.length > MAX_DIFF_BYTES;\n return {\n workspacePath: session.sessionId,\n path: safePath,\n patch: truncated ? truncatePatch(patchBuffer) : result.stdout,\n truncated,\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; sessionFs?: SessionFsDeps } = {}): ApiRoute[] {\n const root = resolveFsRoot(options.root);\n const sessionFs = options.sessionFs;\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 const session = await resolveAuthorizedSession(loose(c), sessionFs, workspacePath);\n if (session && sessionFs) {\n return c.json(await listSessionRenderedPath(session, renderedRoot));\n }\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 message.includes('not available')\n ? 403\n : 500;\n return c.json({ error: message }, status);\n }\n },\n }),\n registerApiRoute('/web/workspace/files', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const workspacePath = c.req.query('workspacePath');\n const threadId = c.req.query('threadId')?.trim();\n if (!workspacePath) return c.json({ error: 'Missing required query param: workspacePath' }, 400);\n if (!threadId) return c.json({ error: 'Missing required query param: threadId' }, 400);\n try {\n const session = await resolveAuthorizedSession(loose(c), sessionFs, workspacePath);\n if (!session || !sessionFs) return c.json({ error: 'Session workspace is not available' }, 403);\n return c.json(await listSessionFilesystemFiles(sessionFs.filesystem, session, threadId));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const status = message.includes('not available') ? 403 : 500;\n return c.json({ error: message }, status);\n }\n },\n }),\n registerApiRoute('/web/workspace/changes', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const workspacePath = c.req.query('workspacePath');\n if (!workspacePath) return c.json({ error: 'Missing required query param: workspacePath' }, 400);\n try {\n const session = await resolveAuthorizedSession(loose(c), sessionFs, workspacePath);\n if (!session || !sessionFs) return c.json(unavailableWorkspaceChanges(workspacePath));\n return c.json(await listSessionWorkspaceChanges(session));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return c.json({ error: message }, message.includes('not available') ? 403 : 500);\n }\n },\n }),\n registerApiRoute('/web/workspace/changes/diff', {\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 const previousPath = c.req.query('previousPath');\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 const session = await resolveAuthorizedSession(loose(c), sessionFs, workspacePath);\n if (!session || !sessionFs) return c.json({ error: 'Session workspace is not available' }, 403);\n return c.json(await readSessionWorkspaceDiff(session, path, previousPath));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const status =\n message.includes('relative') || message.includes('escapes') || message.includes('not available')\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 const requestedThreadId = c.req.query('threadId');\n const threadId = requestedThreadId?.trim();\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 if (requestedThreadId !== undefined && !threadId) {\n return c.json({ error: 'Missing required query param: threadId' }, 400);\n }\n try {\n const session = await resolveAuthorizedSession(loose(c), sessionFs, workspacePath);\n if (session && sessionFs) {\n if (threadId) {\n const safePath = assertRelativePath(path, 'path');\n const listing = await listSessionFilesystemFiles(sessionFs.filesystem, session, threadId);\n if (!listing.files.some(file => file.path === safePath)) {\n return c.json({ error: 'Path is not available for this thread' }, 404);\n }\n return c.json(await readSessionWorkspaceFile(session, safePath, { allowUnapprovedPath: true }));\n }\n return c.json(await readSessionWorkspaceFile(session, path));\n }\n if (threadId) return c.json({ error: 'Session workspace is not available' }, 403);\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 message.includes('not available')\n ? 403\n : message.includes('directory')\n ? 400\n : message.includes('not found')\n ? 404\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":";;;;;;;;;;;AA4JA,MAAM,sBAAsB;AAC5B,MAAM,iBAAiB;AACvB,MAAM,2BAA2B;;;;;;;;;;;;;;;;;;;;AAoBjC,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;AAkBhC,MAAM,eAAe,IAAI,YAAY,SAAS,EAAE,OAAO,KAAK,CAAC;AAC7D,MAAM,0CAA0B,IAAI,IAAI,CAAC,YAAY,CAAC;;AAGtD,SAAS,MAAM,GAAqB;CAClC,OAAO;AACT;;AAGA,SAAgB,cAAc,MAAuB;CACnD,OAAO,QAAQ,QAAQ,KAAK,KAAK,IAAI,OAAO,QAAQ,CAAC;AACvD;;AAGA,SAAS,aAAa,WAAmB,MAAuB;CAC9D,IAAI,cAAc,MAAM,OAAO;CAC/B,MAAM,cAAc,KAAK,SAAS,GAAG,IAAI,OAAO,OAAO;CACvD,OAAO,UAAU,WAAW,WAAW;AACzC;AAEA,eAAe,eAAe,MAA+B;CAC3D,IAAI;EACF,OAAO,MAAM,SAAS,IAAI;CAC5B,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;AAQA,eAAe,mBAAmB,WAAmB,MAAsC;CACzF,IAAI;EACF,MAAM,OAAO,MAAM,SAAS,SAAS;EACrC,OAAO,aAAa,MAAM,IAAI,IAAI,OAAO;CAC3C,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,mBAAmB,MAAc,OAAuB;CAC/D,MAAM,UAAU,KAAK,KAAK;CAC1B,IAAI,CAAC,SAAS,MAAM,IAAI,MAAM,iCAAiC,OAAO;CACtE,IAAI,WAAW,OAAO,GAAG,MAAM,IAAI,MAAM,GAAG,MAAM,kBAAkB;CACpE,IAAI,QAAQ,MAAM,QAAQ,CAAC,CAAC,SAAS,IAAI,GAAG,MAAM,IAAI,MAAM,GAAG,MAAM,mBAAmB;CACxF,MAAM,aAAa,QAAQ,KAAK,OAAO,CAAC,CAAC,MAAM,CAAC;CAChD,IAAI,CAAC,cAAc,eAAe,QAAQ,WAAW,WAAW,KAAK,KAAK,GACxE,MAAM,IAAI,MAAM,GAAG,MAAM,mBAAmB;CAC9C,OAAO;AACT;AAEA,SAAS,2BAA2B,cAA8B;CAChE,MAAM,WAAW,mBAAmB,cAAc,MAAM;CACxD,IAAI,CAAC,wBAAwB,IAAI,QAAQ,GAAG,MAAM,IAAI,MAAM,oDAAoD;CAChH,OAAO;AACT;AAEA,eAAe,sBACb,MACA,eACsD;CACtD,MAAM,eAAe,MAAM,eAAe,cAAc,IAAI,CAAC;CAE7D,MAAM,YAAY,MAAM,mBADN,WAAW,aAAa,IAAI,QAAQ,aAAa,IAAI,QAAQ,cAAc,aAAa,GACpD,YAAY;CAClE,IAAI,CAAC,WAAW,MAAM,IAAI,MAAM,oCAAoC;CACpE,OAAO;EAAE;EAAc;CAAU;AACnC;AAEA,eAAe,8BACb,MACA,eACA,cACoE;CACpE,MAAM,mBAAmB,mBAAmB,cAAc,MAAM;CAChE,MAAM,EAAE,cAAc,MAAM,sBAAsB,MAAM,aAAa;CACrE,MAAM,YAAY,QAAQ,WAAW,gBAAgB;CACrD,IAAI,CAAC,aAAa,WAAW,SAAS,GAAG,MAAM,IAAI,MAAM,wBAAwB;CACjF,MAAM,eAAe,MAAM,mBAAmB,WAAW,SAAS;CAClE,IAAI,CAAC,cAAc,MAAM,IAAI,MAAM,+BAA+B;CAClE,OAAO;EAAE;EAAW,MAAM;EAAc,cAAc;CAAiB;AACzE;;;;;;AAOA,eAAsB,cAAc,MAAc,eAAmD;CAGnG,MAAM,eAAe,MAAM,eAAe,cAAc,IAAI,CAAC;CAE7D,IAAI,SAAS;CACb,IAAI,iBAAiB,cAAc,KAAK,GAGtC,SAAU,MAAM,mBAFE,WAAW,aAAa,IAAI,QAAQ,aAAa,IAAI,QAAQ,cAAc,aAAa,GAE5D,YAAY,KAAM;CAIlE,IAAI;EAEF,IAAI,EAAC,MADc,KAAK,MAAM,EAAA,CACpB,YAAY,GAAG,SAAS;CACpC,QAAQ;EACN,SAAS;CACX;CAEA,MAAM,UAAU,MAAM,QAAQ,QAAQ,EAAE,eAAe,KAAK,CAAC;CAC7D,MAAM,UAA4B,CAAC;CACnC,KAAK,MAAM,UAAU,SAAS;EAC5B,IAAI,OAAO,KAAK,WAAW,GAAG,GAAG;EACjC,MAAM,YAAY,KAAK,QAAQ,OAAO,IAAI;EAC1C,IAAI,QAAQ,OAAO,YAAY;EAC/B,IAAI,OAAO,eAAe,GAAG;GAG3B,MAAM,OAAO,MAAM,mBAAmB,WAAW,YAAY;GAC7D,QAAQ,QAAQ,MAAM,KAAK,IAAI,CAAC,CAAC,YAAY,IAAI,EAAA,EAAI,YAAY,MAAM,OAAO;EAChF;EACA,IAAI,OAAO,QAAQ,KAAK;GAAE,MAAM,OAAO;GAAM,MAAM;EAAU,CAAC;CAChE;CACA,QAAQ,MAAM,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;CAEnD,MAAM,SAAS,WAAW,eAAe,OAAO,QAAQ,QAAQ,IAAI;CAEpE,OAAO;EAAE,MAAM;EAAc,MAAM;EAAQ;EAAQ;CAAQ;AAC7D;AAEA,eAAe,oBAAoB,UAAkB,cAAc,UAA6C;CAC9G,MAAM,UAAU,MAAM,QAAQ,aAAa,EAAE,eAAe,KAAK,CAAC;CAClE,MAAM,UAAoC,CAAC;CAE3C,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,YAAY,KAAK,aAAa,OAAO,IAAI;EAC/C,MAAM,OAAO,MAAM,MAAM,SAAS;EAClC,MAAM,eAAe,UAAU,MAAM,SAAS,SAAS,CAAC;EAExD,IAAI,KAAK,YAAY,GAAG;GACtB,QAAQ,KAAK;IACX,MAAM,OAAO;IACb,MAAM;IACN,MAAM;IACN,MAAM,KAAK;IACX,WAAW,KAAK,MAAM,YAAY;GACpC,CAAC;GACD,QAAQ,KAAK,GAAI,MAAM,oBAAoB,UAAU,SAAS,CAAE;GAChE;EACF;EAEA,IAAI,KAAK,OAAO,GACd,QAAQ,KAAK;GACX,MAAM,OAAO;GACb,MAAM;GACN,MAAM;GACN,MAAM,KAAK;GACX,WAAW,KAAK,MAAM,YAAY;EACpC,CAAC;CAEL;CAEA,OAAO,QAAQ,MAAM,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AAC5D;AAEA,eAAsB,0BACpB,MACA,eACA,cACmC;CACnC,MAAM,WAAW,2BAA2B,YAAY;CACxD,MAAM,EAAE,cAAc,MAAM,sBAAsB,MAAM,aAAa;CACrE,MAAM,eAAe,QAAQ,WAAW,QAAQ;CAChD,IAAI,CAAC,aAAa,cAAc,SAAS,GAAG,MAAM,IAAI,MAAM,wBAAwB;CAEpF,MAAM,mBAAmB,MAAM,mBAAmB,cAAc,SAAS;CACzE,IAAI,CAAC,kBAAkB,OAAO;EAAE,eAAe;EAAW,MAAM;EAAU,UAAU;EAAc,SAAS,CAAC;CAAE;CAG9G,IAAI,EAAC,MADc,KAAK,gBAAgB,EAAA,CAC9B,YAAY,GAAG,OAAO;EAAE,eAAe;EAAW,MAAM;EAAU,UAAU;EAAkB,SAAS,CAAC;CAAE;CAEpH,OAAO;EACL,eAAe;EACf,MAAM;EACN,UAAU;EACV,SAAS,MAAM,oBAAoB,gBAAgB;CACrD;AACF;AAEA,eAAsB,kBAAkB,MAAc,eAAuB,MAAsC;CAGjH,2BAFiB,mBAAmB,MAAM,MACd,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,EACR;CACvC,MAAM,EACJ,WACA,MAAM,cACN,iBACE,MAAM,8BAA8B,MAAM,eAAe,IAAI;CACjE,MAAM,OAAO,MAAM,MAAM,YAAY;CACrC,IAAI,KAAK,YAAY,GAAG,MAAM,IAAI,MAAM,qBAAqB;CAC7D,IAAI,CAAC,KAAK,OAAO,GAAG,MAAM,IAAI,MAAM,uBAAuB;CAE3D,MAAM,cAAc,KAAK,IAAI,KAAK,MAAM,mBAAmB;CAC3D,MAAM,gBAAgB,OAAO,MAAM,WAAW;CAC9C,MAAM,SAAS,MAAM,KAAK,cAAc,GAAG;CAC3C,IAAI;EACF,MAAM,OAAO,KAAK,eAAe,GAAG,aAAa,CAAC;CACpD,UAAU;EACR,MAAM,OAAO,MAAM;CACrB;CAEA,IAAI;EACF,MAAM,UAAU,aAAa,OAAO,aAAa;EACjD,OAAO;GACL,eAAe;GACf,MAAM;GACN,MAAM,aAAa,MAAM,GAAG,CAAC,CAAC,IAAI,KAAK;GACvC,MAAM,KAAK;GACX,WAAW,KAAK,MAAM,YAAY;GAClC,aAAa;GACb;GACA,WAAW,KAAK,OAAO;EACzB;CACF,QAAQ;EACN,OAAO;GACL,eAAe;GACf,MAAM;GACN,MAAM,aAAa,MAAM,GAAG,CAAC,CAAC,IAAI,KAAK;GACvC,MAAM,KAAK;GACX,WAAW,KAAK,MAAM,YAAY;GAClC,aAAa;EACf;CACF;AACF;AAEA,eAAsB,cAAc,MAAc,eAAiD;CACjG,MAAM,UAAU,MAAM,0BAA0B,MAAM,eAAe,YAAY;CACjF,OAAO;EACL,UAAU,QAAQ;EAClB,eAAe,QAAQ;EACvB,SAAS,QAAQ;CACnB;AACF;;;;;;;AAuBA,eAAe,yBACb,GACA,MACA,eACsC;CACtC,IAAI,CAAC,MAAM,OAAO;CAClB,MAAM,UAAU,MAAM,KAAK,SAAS,eAAe,aAAa;CAChE,IAAI,CAAC,SAAS,OAAO;CACrB,IAAI,KAAK,KAAK,QAAQ,GAAG;EACvB,MAAM,KAAK,KAAK,WAAW,CAAC;EAC5B,MAAM,SAAS,KAAK,KAAK,OAAO,CAAC;EACjC,IAAI,CAAC,UAAU,OAAO,UAAU,QAAQ,SAAS,OAAO,WAAW,QAAQ,QACzE,MAAM,IAAI,MAAM,8CAA8C;CAElE;CACA,OAAO;AACT;AAEA,eAAsB,2BACpB,YACA,SACA,UACgC;CAChC,MAAM,eAAe,SAAS,KAAK;CACnC,IAAI,CAAC,cAAc,MAAM,IAAI,MAAM,wCAAwC;CAK3E,MAAM,gCAAgC,QAAQ,SAAS;CAEvD,MAAM,SAAS,cAAc,mBAAmB,QAAQ,EAAE,CAAC;CAC3D,OAAO;EACL,eAAe,QAAQ;EACvB,UAAU;EACV,OAAO,MAAM,WAAW,UAAU;GAAE,YAAY,QAAQ;GAAW,UAAU;EAAa,CAAC;EAC3F,GAAI,UAAU,OAAO,MAAM,SAAS,IAChC,EAAE,cAAc,OAAO,MAAM,KAAI,UAAS;GAAE,MAAM,KAAK;GAAM,QAAQ,KAAK;EAAO,EAAE,EAAE,IACrF,CAAC;CACP;AACF;;;;;;;;;;AAmBA,eAAe,eAAe,SAAqE;CACjG,MAAM,QAAQ,mBAAmB,QAAQ,EAAE;CAG3C,MAAM,SAAS,cAAc,KAAK;CAClC,IAAI,CAAC,SAAS,CAAC,QAAQ,OAAO;CAC9B,MAAM,UAAU,YAAY,MAAM,OAAO;CACzC,OAAO;EACL;EAGA,YAAY,IAAI,kBAAkB;GAAE;GAAS,SAAS,OAAO;GAAM,gBAAgB;EAAK,CAAC;EACzF;EACA,cAAc,uBAAuB,SAAS,QAAQ,MAAM,OAAO;CACrE;AACF;;;;;;;AAQA,SAAS,uBACP,SACA,QACA,SACqB;CACrB,MAAM,cAAc,OAAO,MAAM,SAAS,IAAI,uBAAuB,QAAQ,SAAS,IAAI,KAAA;CAC1F,MAAM,wBAAQ,IAAI,IAAoB;CACtC,KAAK,MAAM,QAAQ,OAAO,OAAO;EAC/B,IAAI,OAAO,MAAM,WAAW,KAAK,KAAK,KAAK,YAAY,MAAM,QAAQ,YAAY,GAAG;GAClF,MAAM,IAAI,KAAK,MAAM,QAAQ,UAAU;GACvC;EACF;EACA,MAAM,QAAQ,aAAa,aAAa,MAAK,cAAa,UAAU,KAAK,YAAY,MAAM,KAAK,KAAK,YAAY,CAAC;EAGlH,MAAM,IAAI,KAAK,MAAM,OAAO,iBAAiB,MAAM;CACrD;CACA,OAAO;AACT;;;;;;;;AASA,SAAS,kBACP,QACA,MACA,OACqD;CACrD,MAAM,WAAW,mBAAmB,MAAM,KAAK;CAC/C,IAAI,OAAO,MAAM,WAAW,GAAG,OAAO;EAAE,MAAM,OAAO,MAAM;EAAK,UAAU;CAAS;CACnF,MAAM,OAAO,OAAO,MACjB,QAAO,cAAa,SAAS,WAAW,UAAU,MAAM,KAAK,SAAS,SAAS,UAAU,OAAO,MAAM,CAAC,CACvG,UAAU,GAAG,MAAM,EAAE,OAAO,SAAS,EAAE,OAAO,MAAM,CAAC,CAAC;CACzD,IAAI,CAAC,MAAM,MAAM,IAAI,MAAM,GAAG,MAAM,iDAAiD;CACrF,OAAO;EAAE;EAAM,UAAU,mBAAmB,SAAS,MAAM,KAAK,OAAO,MAAM,GAAG,KAAK;CAAE;AACzF;;AAGA,eAAsB,wBACpB,SACA,cACmC;CACnC,MAAM,WAAW,2BAA2B,YAAY;CACxD,MAAM,SAAS,MAAM,eAAe,OAAO;CAC3C,MAAM,WAAWA,MAAU,KAAK,QAAQ,OAAO,QAAQ,IAAI,QAAQ;CACnE,MAAM,QAAkC;EAAE,eAAe,QAAQ;EAAW,MAAM;EAAU;EAAU,SAAS,CAAC;CAAE;CAClH,IAAI,CAAC,QAAQ,OAAO;CAKpB,MAAM,aAAa,IAAI,SAAS,QAAQ,MAAM,OAAO,EAAE;CACvD,MAAM,SAAS,MAAM,OAAO,QAAQ,eAClC,MACA,CACE,MACA,WAAW,WAAW,WAAW,WAAW,iEAC9C,GACA;EAAE,SAAS;EAAQ,kBAAkB;CAAS,CAChD;CACA,IAAI,OAAO,aAAa,GAAG,OAAO;CAElC,MAAM,UAAoC,CAAC;CAC3C,KAAK,MAAM,QAAQ,OAAO,OAAO,MAAM,IAAI,GAAG;EAC5C,IAAI,CAAC,MAAM;EACX,MAAM,CAAC,MAAM,SAAS,UAAU,GAAG,aAAa,KAAK,MAAM,GAAI;EAC/D,MAAM,WAAW,UAAU,KAAK,GAAI;EACpC,IAAI,CAAC,YAAY,CAAC,SAAS,WAAW,GAAG,SAAS,EAAE,GAAG;EACvD,MAAM,eAAe,SAAS,MAAM,SAAS,SAAS,CAAC;EACvD,QAAQ,KAAK;GACX,MAAMA,MAAU,SAAS,YAAY;GACrC,MAAM;GACN,MAAM,SAAS,MAAM,cAAc;GACnC,MAAM,SAAS,MAAM,IAAI,OAAO,OAAO,KAAK;GAC5C,4BAAW,IAAI,MAAM,OAAO,QAAQ,KAAK,KAAK,GAAI,EAAA,CAAE,YAAY;EAClE,CAAC;CACH;CACA,QAAQ,MAAM,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;CAEnD,OAAO;EAAE,eAAe,QAAQ;EAAW,MAAM;EAAU;EAAU;CAAQ;AAC/E;;AAGA,eAAsB,yBACpB,SACA,MACA,UAA6C,CAAC,GACtB;CACxB,MAAM,WAAW,mBAAmB,MAAM,MAAM;CAChD,IAAI,CAAC,QAAQ,qBAAqB,2BAA2B,SAAS,MAAM,GAAG,CAAC,CAAC,MAAM,EAAE;CAEzF,MAAM,SAAS,MAAM,eAAe,OAAO;CAC3C,IAAI,CAAC,QAAQ,MAAM,IAAI,MAAM,oCAAoC;CACjE,MAAM,EAAE,eAAe;CACvB,MAAM,OAAO,MAAM,WAAW,KAAK,QAAQ;CAC3C,IAAI,KAAK,SAAS,aAAa,MAAM,IAAI,MAAM,qBAAqB;CAEpE,MAAM,SAAU,MAAM,WAAW,SAAS,QAAQ;CAClD,MAAM,YAAY,OAAO,SAAS;CAClC,MAAM,OAAO;EACX,eAAe,QAAQ;EACvB,MAAM;EACN,MAAMA,MAAU,SAAS,QAAQ;EACjC,MAAM,OAAO;EACb,WAAW,KAAK,WAAW,YAAY;CACzC;CACA,IAAI;EACF,MAAM,UAAU,aAAa,OAAO,YAAY,OAAO,SAAS,GAAG,mBAAmB,IAAI,MAAM;EAChG,OAAO;GAAE,GAAG;GAAM,aAAa;GAAQ;GAAS;EAAU;CAC5D,QAAQ;EACN,OAAO;GAAE,GAAG;GAAM,aAAa;EAAc;CAC/C;AACF;AAEA,SAAS,aAAa,MAAqC;CACzD,IAAI,SAAS,MAAM,OAAO;CAC1B,IAAI,KAAK,SAAS,GAAG,KAAK,SAAS,QAAQ,SAAS,MAAM,OAAO;CACjE,IAAI,KAAK,SAAS,GAAG,GAAG,OAAO;CAC/B,IAAI,KAAK,SAAS,GAAG,GAAG,OAAO;CAC/B,IAAI,KAAK,SAAS,GAAG,GAAG,OAAO;CAC/B,IAAI,KAAK,SAAS,GAAG,GAAG,OAAO;CAC/B,OAAO;AACT;AAEA,SAAgB,sBAAsB,QAAmC;CACvE,MAAM,UAAU,OAAO,MAAM,IAAI;CACjC,MAAM,UAA6B,CAAC;CAEpC,KAAK,IAAI,QAAQ,GAAG,QAAQ,QAAQ,QAAQ,SAAS,GAAG;EACtD,MAAM,SAAS,QAAQ;EACvB,IAAI,CAAC,UAAU,OAAO,SAAS,GAAG;EAClC,MAAM,OAAO,OAAO,MAAM,GAAG,CAAC;EAC9B,MAAM,OAAO,OAAO,MAAM,CAAC;EAC3B,MAAM,SAAS,aAAa,IAAI;EAChC,IAAI,WAAW,aAAa,WAAW,UAAU;GAC/C,MAAM,eAAe,QAAQ,QAAQ;GACrC,IAAI,cAAc,SAAS;GAC3B,QAAQ,KAAK;IAAE;IAAM,cAAc,gBAAgB,KAAA;IAAW;GAAO,CAAC;GACtE;EACF;EACA,QAAQ,KAAK;GAAE;GAAM;EAAO,CAAC;CAC/B;CAEA,OAAO,QAAQ,UAAU,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AAChE;AACA,SAAgB,6BAA6B,QAAmC;CAC9E,MAAM,UAAU,OAAO,MAAM,IAAI;CACjC,MAAM,UAA6B,CAAC;CACpC,KAAK,IAAI,QAAQ,GAAG,QAAQ,QAAQ,SAAS;EAC3C,MAAM,OAAO,QAAQ;EACrB,IAAI,CAAC,MAAM;EACX,MAAM,SAAS,aAAa,IAAI;EAChC,IAAI,WAAW,aAAa,WAAW,UAAU;GAC/C,MAAM,eAAe,QAAQ;GAC7B,MAAM,OAAO,QAAQ;GACrB,IAAI,MAAM,QAAQ,KAAK;IAAE;IAAM,cAAc,gBAAgB,KAAA;IAAW;GAAO,CAAC;GAChF;EACF;EACA,MAAM,OAAO,QAAQ;EACrB,IAAI,MAAM,QAAQ,KAAK;GAAE;GAAM;EAAO,CAAC;CACzC;CACA,OAAO,QAAQ,UAAU,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AAChE;AAEA,SAAgB,0BAA0B,QAAgB;CACxD,MAAM,UAAU,OAAO,MAAM,IAAI;CACjC,MAAM,wBAAQ,IAAI,IAA0E;CAE5F,KAAK,IAAI,QAAQ,GAAG,QAAQ,QAAQ,QAAQ,SAAS,GAAG;EACtD,MAAM,SAAS,QAAQ;EACvB,IAAI,CAAC,QAAQ;EACb,MAAM,WAAW,OAAO,QAAQ,GAAI;EACpC,MAAM,YAAY,OAAO,QAAQ,KAAM,WAAW,CAAC;EACnD,IAAI,WAAW,KAAK,YAAY,GAAG;EAEnC,MAAM,gBAAgB,OAAO,MAAM,GAAG,QAAQ;EAC9C,MAAM,gBAAgB,OAAO,MAAM,WAAW,GAAG,SAAS;EAC1D,IAAI,OAAO,OAAO,MAAM,YAAY,CAAC;EACrC,IAAI,CAAC,MAAM;GACT,MAAM,cAAc,QAAQ,QAAQ;GACpC,IAAI,CAAC,aAAa;GAClB,OAAO;GACP,SAAS;EACX;EACA,IAAI,KAAK,WAAW,IAAI,GAAG,OAAO,KAAK,MAAM,CAAC;EAE9C,IAAI,kBAAkB,OAAO,kBAAkB,KAAK;GAClD,MAAM,IAAI,MAAM,EAAE,QAAQ,KAAK,CAAC;GAChC;EACF;EAEA,MAAM,YAAY,OAAO,aAAa;EACtC,MAAM,YAAY,OAAO,aAAa;EACtC,IAAI,OAAO,SAAS,SAAS,KAAK,OAAO,SAAS,SAAS,GAAG,MAAM,IAAI,MAAM;GAAE;GAAW;EAAU,CAAC;CACxG;CAEA,OAAO;AACT;AAEA,SAAS,4BAA4B,eAAyC;CAC5E,OAAO;EAAE;EAAe,WAAW;EAAO,SAAS,CAAC;CAAE;AACxD;AAEA,eAAe,6BACb,SACA,SACA,YACiB;CACjB,MAAM,SAAS,MAAM,QAAQ,eAAe,OAAO;EAAC;EAAM;EAAS;EAAc;EAAQ,UAAU;CAAY,GAAG,EAChH,SAAS,IACX,CAAC;CACD,MAAM,MAAM,OAAO,OAAO,KAAK;CAC/B,OAAO,OAAO,aAAa,KAAK,qBAAqB,KAAK,GAAG,IAAI,MAAM;AACzE;;AAKA,eAAe,sBACb,SACA,SACA,YAC4B;CAC5B,MAAM,iBAAiB,MAAM,6BAA6B,SAAS,SAAS,UAAU;CAItF,MAAM,CAAC,cAAc,eAAe,eAAe,MAAM,QAAQ,IAAI;EACnE,QAAQ,eAAe,OAAO;GAAC;GAAM;GAAS;GAAU;GAAkB;GAAM;EAAuB,GAAG;GACxG,SAAS;GACT,kBAAkB;EACpB,CAAC;EACD,QAAQ,eAAe,OAAO;GAAC;GAAM;GAAS;GAAQ;GAAiB;GAAM;GAAkB;EAAc,GAAG;GAC9G,SAAS;GACT,kBAAkB;EACpB,CAAC;EACD,QAAQ,eAAe,MAAM;GAAC;GAAM;GAA0B;GAAsB;GAAS;EAAc,GAAG;GAC5G,SAAS;GACT,kBAAkB;EACpB,CAAC;CACH,CAAC;CACD,IAAI,aAAa,aAAa,GAAG,OAAO;EAAE,WAAW;EAAO,SAAS,CAAC;CAAE;CAExE,MAAM,gBAAgB,sBAAsB,aAAa,MAAM;CAC/D,MAAM,iBAAiB,cAAc,aAAa,IAAI,6BAA6B,cAAc,MAAM,IAAI,CAAC;CAC5G,MAAM,gBAAgB,IAAI,IAAI,eAAe,KAAI,WAAU,CAAC,OAAO,MAAM,MAAM,CAAC,CAAC;CACjF,KAAK,MAAM,UAAU,eACnB,IAAI,cAAc,aAAa,KAAK,OAAO,WAAW,eAAe,OAAO,WAAW,cACrF,cAAc,IAAI,OAAO,MAAM,MAAM;CAGzC,MAAM,UAAU,CAAC,GAAG,cAAc,OAAO,CAAC,CAAC,CAAC,UAAU,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;CAC3F,IAAI,YAAY,aAAa,GAAG,OAAO;EAAE,WAAW;EAAM;CAAQ;CAElE,MAAM,QAAQ,0BAA0B,YAAY,MAAM;CAC1D,IAAI,YAAY;CAChB,IAAI,YAAY;CAOhB,OAAO;EAAE,WAAW;EAAM,SAND,QAAQ,KAAI,WAAU;GAC7C,MAAM,cAAc,MAAM,IAAI,OAAO,IAAI;GACzC,aAAa,aAAa,aAAa;GACvC,aAAa,aAAa,aAAa;GACvC,OAAO;IAAE,GAAG;IAAQ,GAAG;GAAY;EACrC,CACkD;EAAG;EAAW;CAAU;AAC5E;AAEA,SAAS,aAAa,QAAyB,QAAiC;CAC9E,IAAI,CAAC,QAAQ,OAAO;CACpB,OAAO;EACL,GAAG;EACH,MAAM,GAAG,SAAS,OAAO;EACzB,GAAI,OAAO,eAAe,EAAE,cAAc,GAAG,SAAS,OAAO,eAAe,IAAI,CAAC;CACnF;AACF;AAEA,eAAsB,4BAA4B,SAA0D;CAC1G,MAAM,SAAS,MAAM,eAAe,OAAO;CAC3C,IAAI,CAAC,QAAQ,OAAO,4BAA4B,QAAQ,SAAS;CACjE,MAAM,EAAE,QAAQ,cAAc,UAAU;CAGxC,IAAI,OAAO,MAAM,WAAW,GAAG;EAC7B,MAAM,OAAO,OAAO,MAAM;EAC1B,MAAM,SAAS,MAAM,sBAAsB,OAAO,SAAS,KAAK,KAAK,MAAM,IAAI,KAAK,IAAI,KAAK,QAAQ,UAAU;EAC/G,IAAI,CAAC,OAAO,WAAW,OAAO,4BAA4B,QAAQ,SAAS;EAC3E,OAAO;GAAE,eAAe,QAAQ;GAAW,GAAG;EAAO;CACvD;CAKA,MAAM,SAAuC,MAAM,QAAQ,IACzD,OAAO,MAAM,IAAI,OAAM,SAAQ;EAC7B,MAAM,SAAS,MAAM,sBAAsB,OAAO,SAAS,KAAK,KAAK,MAAM,IAAI,KAAK,IAAI,CAAE;EAC1F,OAAO;GACL,MAAM,KAAK;GACX,QAAQ,KAAK;GACb,GAAG;GACH,SAAS,OAAO,QAAQ,KAAI,WAAU,aAAa,QAAQ,KAAK,MAAM,CAAC;EACzE;CACF,CAAC,CACH;CACA,MAAM,YAAY,OAAO,QAAO,UAAS,MAAM,SAAS;CACxD,MAAM,UAAU,UAAU,SAAQ,UAAS,MAAM,OAAO,CAAC,CAAC,UAAU,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;CACzG,MAAM,UAAU,UAAU,QAAO,UAAS,MAAM,cAAc,KAAA,CAAS;CACvE,MAAM,SACJ,QAAQ,SAAS,IACb;EACE,WAAW,QAAQ,QAAQ,KAAK,UAAU,OAAO,MAAM,aAAa,IAAI,CAAC;EACzE,WAAW,QAAQ,QAAQ,KAAK,UAAU,OAAO,MAAM,aAAa,IAAI,CAAC;CAC3E,IACA,CAAC;CACP,OAAO;EACL,eAAe,QAAQ;EACvB,WAAW,UAAU,SAAS;EAC9B;EACA,GAAG;EACH,cAAc;CAChB;AACF;AAEA,eAAe,sBAAsB,SAA4B,MAAgB,eAAe,OAAO;CACrG,OAAO,QAAQ,eACb,MACA;EAAC;EAAM;EAAyB;EAAmB,eAAe,MAAM;EAAK,GAAG;CAAI,GACpF,EAAE,SAAS,IAAO,CACpB;AACF;AAEA,SAAS,cAAc,aAA6B;CAClD,MAAM,QAAQ,YAAY,SAAS,GAAG,cAAc,CAAC,CAAC,SAAS,MAAM;CACrE,MAAM,cAAc,MAAM,YAAY,IAAI;CAC1C,IAAI,eAAe,GAAG,OAAO,MAAM,MAAM,GAAG,cAAc,CAAC;CAC3D,OAAO,MAAM,QAAQ,YAAY,EAAE;AACrC;AAEA,eAAsB,yBACpB,SACA,MACA,cACwB;CACxB,MAAM,WAAW,mBAAmB,MAAM,MAAM;CAChD,IAAI,cAAc,mBAAmB,cAAc,cAAc;CACjE,MAAM,SAAS,MAAM,eAAe,OAAO;CAC3C,IAAI,CAAC,QAAQ,MAAM,IAAI,MAAM,oCAAoC;CAIjE,MAAM,SAAS,kBAAkB,OAAO,QAAQ,UAAU,MAAM;CAChE,MAAM,WAAW,eAAe,kBAAkB,OAAO,QAAQ,cAAc,cAAc,IAAI,KAAA;CACjG,IAAI,YAAY,SAAS,SAAS,OAAO,MACvC,MAAM,IAAI,MAAM,yDAAyD;CAE3E,MAAM,UAAU,OAAO,KAAK;CAC5B,MAAM,iBAAiB,MAAM,6BAC3B,OAAO,SACP,SACA,OAAO,aAAa,IAAI,OAAO,KAAK,IAAI,CAC1C;CAEA,MAAM,YAAY,WAAW,CAAC,SAAS,UAAU,OAAO,QAAQ,IAAI,CAAC,OAAO,QAAQ;CACpF,IAAI,SAAS,MAAM,sBAAsB,OAAO,SAAS;EACvD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,GAAG;CACL,CAAC;CACD,IAAI,OAAO,aAAa,GAAG,MAAM,IAAI,MAAM,OAAO,UAAU,+BAA+B;CAE3F,IAAI,CAAC,OAAO,QAAQ;EAClB,MAAM,YAAY,MAAM,OAAO,QAAQ,eACrC,OACA;GAAC;GAAuB;GAAM;GAAS;GAAY;GAAY;GAAsB;GAAM,OAAO;EAAQ,GAC1G;GAAE,SAAS;GAAQ,kBAAkB;EAAS,CAChD;EACA,IAAI,UAAU,aAAa,KAAK,UAAU,OAAO,KAAK,GAAG;GACvD,SAAS,MAAM,sBACb,OAAO,SACP;IACE;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA,OAAO;GACT,GACA,IACF;GACA,IAAI,OAAO,aAAa,KAAK,OAAO,aAAa,GAC/C,MAAM,IAAI,MAAM,OAAO,UAAU,+BAA+B;EAEpE;CACF;CAEA,MAAM,cAAc,OAAO,KAAK,OAAO,MAAM;CAC7C,MAAM,YAAY,YAAY,SAAS;CACvC,OAAO;EACL,eAAe,QAAQ;EACvB,MAAM;EACN,OAAO,YAAY,cAAc,WAAW,IAAI,OAAO;EACvD;CACF;AACF;;;;;;;;AAsBA,SAAgB,gBAAgB,aAAuC;CACrE,MAAM,OAAO,cAAc,WAAW;CAEtC,OAAO;EACL,YAFe,sBAAsB,KAAK,QAEvB,KAAK,KAAK;EAC7B,MAAM,KAAK;EACX,UAAU,KAAK;EACf,QAAQ,KAAK;EACb,WAAW,KAAK;CAClB;AACF;;;;;;AAOA,SAAgB,cAAc,UAAwD,CAAC,GAAe;CACpG,MAAM,OAAO,cAAc,QAAQ,IAAI;CACvC,MAAM,YAAY,QAAQ;CAE1B,OAAO;EACL,iBAAiB,gBAAgB;GAC/B,QAAQ;GACR,cAAc;GACd,SAAS,OAAM,MAAK;IAClB,MAAM,OAAO,EAAE,IAAI,MAAM,MAAM;IAC/B,IAAI;KACF,MAAM,UAAU,MAAM,cAAc,MAAM,IAAI;KAC9C,OAAO,EAAE,KAAK,OAAO;IACvB,SAAS,OAAO;KACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;KACrE,OAAO,EAAE,KAAK,EAAE,OAAO,QAAQ,GAAG,GAAG;IACvC;GACF;EACF,CAAC;EACD,iBAAiB,uBAAuB;GACtC,QAAQ;GACR,cAAc;GACd,SAAS,OAAM,MAAK;IAClB,MAAM,OAAO,EAAE,IAAI,MAAM,MAAM;IAC/B,IAAI,CAAC,MAAM,OAAO,EAAE,KAAK,EAAE,OAAO,qCAAqC,GAAG,GAAG;IAC7E,IAAI;KACF,OAAO,EAAE,KAAK,MAAM,cAAc,MAAM,IAAI,CAAC;IAC/C,SAAS,OAAO;KACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;KACrE,MAAM,SAAS,YAAY,uCAAuC,MAAM;KACxE,OAAO,EAAE,KAAK,EAAE,OAAO,QAAQ,GAAG,MAAM;IAC1C;GACF;EACF,CAAC;EACD,iBAAiB,gCAAgC;GAC/C,QAAQ;GACR,cAAc;GACd,SAAS,OAAM,MAAK;IAClB,MAAM,gBAAgB,EAAE,IAAI,MAAM,eAAe;IACjD,MAAM,eAAe,EAAE,IAAI,MAAM,MAAM;IACvC,IAAI,CAAC,eAAe,OAAO,EAAE,KAAK,EAAE,OAAO,8CAA8C,GAAG,GAAG;IAC/F,IAAI,CAAC,cAAc,OAAO,EAAE,KAAK,EAAE,OAAO,qCAAqC,GAAG,GAAG;IACrF,IAAI;KACF,MAAM,UAAU,MAAM,yBAAyB,MAAM,CAAC,GAAG,WAAW,aAAa;KACjF,IAAI,WAAW,WACb,OAAO,EAAE,KAAK,MAAM,wBAAwB,SAAS,YAAY,CAAC;KAEpE,OAAO,EAAE,KAAK,MAAM,0BAA0B,MAAM,eAAe,YAAY,CAAC;IAClF,SAAS,OAAO;KACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;KACrE,MAAM,SACJ,QAAQ,SAAS,SAAS,KAC1B,QAAQ,SAAS,UAAU,KAC3B,QAAQ,SAAS,SAAS,KAC1B,QAAQ,SAAS,cAAc,KAC/B,QAAQ,SAAS,eAAe,IAC5B,MACA;KACN,OAAO,EAAE,KAAK,EAAE,OAAO,QAAQ,GAAG,MAAM;IAC1C;GACF;EACF,CAAC;EACD,iBAAiB,wBAAwB;GACvC,QAAQ;GACR,cAAc;GACd,SAAS,OAAM,MAAK;IAClB,MAAM,gBAAgB,EAAE,IAAI,MAAM,eAAe;IACjD,MAAM,WAAW,EAAE,IAAI,MAAM,UAAU,CAAC,EAAE,KAAK;IAC/C,IAAI,CAAC,eAAe,OAAO,EAAE,KAAK,EAAE,OAAO,8CAA8C,GAAG,GAAG;IAC/F,IAAI,CAAC,UAAU,OAAO,EAAE,KAAK,EAAE,OAAO,yCAAyC,GAAG,GAAG;IACrF,IAAI;KACF,MAAM,UAAU,MAAM,yBAAyB,MAAM,CAAC,GAAG,WAAW,aAAa;KACjF,IAAI,CAAC,WAAW,CAAC,WAAW,OAAO,EAAE,KAAK,EAAE,OAAO,qCAAqC,GAAG,GAAG;KAC9F,OAAO,EAAE,KAAK,MAAM,2BAA2B,UAAU,YAAY,SAAS,QAAQ,CAAC;IACzF,SAAS,OAAO;KACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;KACrE,MAAM,SAAS,QAAQ,SAAS,eAAe,IAAI,MAAM;KACzD,OAAO,EAAE,KAAK,EAAE,OAAO,QAAQ,GAAG,MAAM;IAC1C;GACF;EACF,CAAC;EACD,iBAAiB,0BAA0B;GACzC,QAAQ;GACR,cAAc;GACd,SAAS,OAAM,MAAK;IAClB,MAAM,gBAAgB,EAAE,IAAI,MAAM,eAAe;IACjD,IAAI,CAAC,eAAe,OAAO,EAAE,KAAK,EAAE,OAAO,8CAA8C,GAAG,GAAG;IAC/F,IAAI;KACF,MAAM,UAAU,MAAM,yBAAyB,MAAM,CAAC,GAAG,WAAW,aAAa;KACjF,IAAI,CAAC,WAAW,CAAC,WAAW,OAAO,EAAE,KAAK,4BAA4B,aAAa,CAAC;KACpF,OAAO,EAAE,KAAK,MAAM,4BAA4B,OAAO,CAAC;IAC1D,SAAS,OAAO;KACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;KACrE,OAAO,EAAE,KAAK,EAAE,OAAO,QAAQ,GAAG,QAAQ,SAAS,eAAe,IAAI,MAAM,GAAG;IACjF;GACF;EACF,CAAC;EACD,iBAAiB,+BAA+B;GAC9C,QAAQ;GACR,cAAc;GACd,SAAS,OAAM,MAAK;IAClB,MAAM,gBAAgB,EAAE,IAAI,MAAM,eAAe;IACjD,MAAM,OAAO,EAAE,IAAI,MAAM,MAAM;IAC/B,MAAM,eAAe,EAAE,IAAI,MAAM,cAAc;IAC/C,IAAI,CAAC,eAAe,OAAO,EAAE,KAAK,EAAE,OAAO,8CAA8C,GAAG,GAAG;IAC/F,IAAI,CAAC,MAAM,OAAO,EAAE,KAAK,EAAE,OAAO,qCAAqC,GAAG,GAAG;IAC7E,IAAI;KACF,MAAM,UAAU,MAAM,yBAAyB,MAAM,CAAC,GAAG,WAAW,aAAa;KACjF,IAAI,CAAC,WAAW,CAAC,WAAW,OAAO,EAAE,KAAK,EAAE,OAAO,qCAAqC,GAAG,GAAG;KAC9F,OAAO,EAAE,KAAK,MAAM,yBAAyB,SAAS,MAAM,YAAY,CAAC;IAC3E,SAAS,OAAO;KACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;KACrE,MAAM,SACJ,QAAQ,SAAS,UAAU,KAAK,QAAQ,SAAS,SAAS,KAAK,QAAQ,SAAS,eAAe,IAC3F,MACA;KACN,OAAO,EAAE,KAAK,EAAE,OAAO,QAAQ,GAAG,MAAM;IAC1C;GACF;EACF,CAAC;EACD,iBAAiB,uBAAuB;GACtC,QAAQ;GACR,cAAc;GACd,SAAS,OAAM,MAAK;IAClB,MAAM,gBAAgB,EAAE,IAAI,MAAM,eAAe;IACjD,MAAM,OAAO,EAAE,IAAI,MAAM,MAAM;IAC/B,MAAM,oBAAoB,EAAE,IAAI,MAAM,UAAU;IAChD,MAAM,WAAW,mBAAmB,KAAK;IACzC,IAAI,CAAC,eAAe,OAAO,EAAE,KAAK,EAAE,OAAO,8CAA8C,GAAG,GAAG;IAC/F,IAAI,CAAC,MAAM,OAAO,EAAE,KAAK,EAAE,OAAO,qCAAqC,GAAG,GAAG;IAC7E,IAAI,sBAAsB,KAAA,KAAa,CAAC,UACtC,OAAO,EAAE,KAAK,EAAE,OAAO,yCAAyC,GAAG,GAAG;IAExE,IAAI;KACF,MAAM,UAAU,MAAM,yBAAyB,MAAM,CAAC,GAAG,WAAW,aAAa;KACjF,IAAI,WAAW,WAAW;MACxB,IAAI,UAAU;OACZ,MAAM,WAAW,mBAAmB,MAAM,MAAM;OAEhD,IAAI,EAAC,MADiB,2BAA2B,UAAU,YAAY,SAAS,QAAQ,EAAA,CAC3E,MAAM,MAAK,SAAQ,KAAK,SAAS,QAAQ,GACpD,OAAO,EAAE,KAAK,EAAE,OAAO,wCAAwC,GAAG,GAAG;OAEvE,OAAO,EAAE,KAAK,MAAM,yBAAyB,SAAS,UAAU,EAAE,qBAAqB,KAAK,CAAC,CAAC;MAChG;MACA,OAAO,EAAE,KAAK,MAAM,yBAAyB,SAAS,IAAI,CAAC;KAC7D;KACA,IAAI,UAAU,OAAO,EAAE,KAAK,EAAE,OAAO,qCAAqC,GAAG,GAAG;KAChF,OAAO,EAAE,KAAK,MAAM,kBAAkB,MAAM,eAAe,IAAI,CAAC;IAClE,SAAS,OAAO;KACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;KACrE,MAAM,SACJ,QAAQ,SAAS,SAAS,KAC1B,QAAQ,SAAS,UAAU,KAC3B,QAAQ,SAAS,SAAS,KAC1B,QAAQ,SAAS,cAAc,KAC/B,QAAQ,SAAS,eAAe,IAC5B,MACA,QAAQ,SAAS,WAAW,IAC1B,MACA,QAAQ,SAAS,WAAW,IAC1B,MACA;KACV,OAAO,EAAE,KAAK,EAAE,OAAO,QAAQ,GAAG,MAAM;IAC1C;GACF;EACF,CAAC;EACD,iBAAiB,yBAAyB;GACxC,QAAQ;GACR,cAAc;GACd,SAAS,OAAM,MAAK;IAClB,MAAM,OAAO,EAAE,IAAI,MAAM,MAAM;IAC/B,IAAI,CAAC,MAAM,OAAO,EAAE,KAAK,EAAE,OAAO,qCAAqC,GAAG,GAAG;IAK7E,MAAM,WAAW,MAAM,mBAAmB,WAAW,IAAI,IAAI,QAAQ,IAAI,IAAI,QAAQ,MAAM,IAAI,GAAG,IAAI;IACtG,IAAI,CAAC,UAAU,OAAO,EAAE,KAAK,EAAE,OAAO,qCAAqC,GAAG,GAAG;IACjF,IAAI;KACF,OAAO,EAAE,KAAK,gBAAgB,QAAQ,CAAC;IACzC,SAAS,OAAO;KACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;KACrE,OAAO,EAAE,KAAK,EAAE,OAAO,QAAQ,GAAG,GAAG;IACvC;GACF;EACF,CAAC;CACH;AACF"}
|
package/dist/routes/intake.js
CHANGED
|
@@ -101,12 +101,13 @@ async function relocateLabeledCards({ workItems, boardRegistry, orgId, userId, f
|
|
|
101
101
|
if (!source || source.integrationId !== integrationId || source.type !== "issue") continue;
|
|
102
102
|
const labels = cardLabels(item);
|
|
103
103
|
if (!labels.some((candidate) => normalizeIntakeLabel(candidate) === changed)) continue;
|
|
104
|
+
const targetBoard = resolveIntakeLabelRoute(routes, labels)?.board ?? repositoryBoards.find((candidate) => cardInRepository(item, candidate))?.board ?? "work";
|
|
104
105
|
const outcome = await moveCardToBoard({
|
|
105
106
|
workItems,
|
|
106
107
|
boardRegistry,
|
|
107
108
|
userId,
|
|
108
109
|
item,
|
|
109
|
-
targetBoard
|
|
110
|
+
targetBoard
|
|
110
111
|
});
|
|
111
112
|
if (outcome === "moved") moved += 1;
|
|
112
113
|
else if (outcome === "skipped") skipped += 1;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"intake.js","names":["#resolveTenant"],"sources":["../../src/routes/intake.ts"],"sourcesContent":["import type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\nimport { z } from 'zod';\n\nimport type { BoardRegistry } from '../boards/index.js';\nimport { cardLabels, moveCardToBoard } from '../boards/relocate.js';\nimport type { Intake, IntakeItem } from '../capabilities/intake.js';\nimport type { AuditEmitter } from '../storage/domains/audit/domain.js';\nimport { normalizeIntakeLabel, resolveIntakeLabelRoute } from '../storage/domains/intake/base.js';\nimport type { IntakeConfig, IntakeLabelRoute, IntakeStorage } from '../storage/domains/intake/base.js';\nimport type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport type { RouteDependencies } from './route.js';\nimport { Route } from './route.js';\n\nexport interface IntakeIntegration {\n id: string;\n intake: Pick<Intake, 'listSources' | 'listItems'>;\n}\n\ninterface AggregatedIntakeItem extends Omit<IntakeItem, 'source'> {\n integrationId: string;\n externalSource: {\n integrationId: string;\n type: string;\n externalId: string;\n url?: string;\n };\n}\n\nexport interface IntakeRoutesDeps extends RouteDependencies {\n audit: AuditEmitter;\n /** Intake selection domain handle. */\n intake: IntakeStorage;\n /** Factory project domain handle, used to validate binding targets. */\n projects?: { get(input: { orgId: string; id: string }): Promise<unknown | null> };\n integrations?: IntakeIntegration[];\n /** Installed boards, used to validate binding targets. Absent means only legacy routing is accepted. */\n boardRegistry?: BoardRegistry;\n /** Work items domain handle; when present, rebinding a source to another board moves its resting cards. */\n workItems?: Pick<WorkItemsStorage, 'list' | 'update' | 'supersedeDecisionsForWorkItem'>;\n /** GitHub repositories, used to match a card to the repository binding its source id names. */\n githubRepositories?: Pick<SourceControlStorageHandle['repositories'], 'get'>;\n}\n\n/** Upper bound on source pages read while relocating cards, so a huge source cannot stall the request. */\nconst REBIND_MAX_PAGES = 20;\n/**\n * One deadline for the whole rebind read, not one per page: the binding is already saved when we\n * get here, so the caller is only waiting on relocation and a slow provider must not hold the\n * response for pages × timeout.\n */\nconst REBIND_READ_BUDGET_MS = 30_000;\n\n/**\n * Keys under which an intake item may be persisted as a work item. Providers key items by their\n * own id, while materialization uses the human identifier (`linear:MAS-44`), so both are accepted.\n */\nfunction intakeItemSourceKeys(integrationId: string, item: IntakeItem): string[] {\n const identifier = item.metadata?.identifier;\n return typeof identifier === 'string' && identifier.length > 0\n ? [item.source.externalId, `${integrationId}:${identifier}`]\n : [item.source.externalId];\n}\n\n/**\n * Move the cards that came from a rebound source onto its new board's initial phase. Terminal cards\n * stay put (they are history that belongs where it finished), as do cards with a session attached to\n * a current stage (a run owns them). Everything else moves, including cards parked in a working\n * phase that never started a run — auto-ingested issues sit in Work › Triage that way.\n */\nasync function relocateSourceCards({\n workItems,\n integration,\n boardRegistry,\n orgId,\n userId,\n factoryProjectId,\n sourceId,\n targetBoard,\n attributionSourceIds,\n}: {\n workItems: Pick<WorkItemsStorage, 'list' | 'update' | 'supersedeDecisionsForWorkItem'>;\n integration: IntakeIntegration;\n boardRegistry: BoardRegistry;\n orgId: string;\n userId: string;\n factoryProjectId: string;\n sourceId: string;\n targetBoard: string;\n attributionSourceIds: string[];\n}): Promise<{ moved: number; skipped: number }> {\n if (!boardRegistry.has(targetBoard)) return { moved: 0, skipped: 0 };\n\n const sourceKeys = new Set<string>();\n let cursor: string | undefined;\n const deadline = Date.now() + REBIND_READ_BUDGET_MS;\n for (let page = 0; page < REBIND_MAX_PAGES; page += 1) {\n const result = await withTimeout(\n integration.id,\n () =>\n integration.intake.listItems({\n orgId,\n userId,\n sourceIds: [sourceId],\n attributionSourceIds,\n cursor,\n }),\n deadline - Date.now(),\n );\n for (const item of result.items) {\n if (item.sourceId !== sourceId) continue;\n for (const key of intakeItemSourceKeys(integration.id, item)) sourceKeys.add(key);\n }\n if (!result.nextCursor) break;\n cursor = result.nextCursor;\n }\n if (sourceKeys.size === 0) return { moved: 0, skipped: 0 };\n\n let moved = 0;\n let skipped = 0;\n const items = await workItems.list({ orgId, factoryProjectId });\n for (const item of items) {\n const source = item.externalSource;\n if (!source || source.integrationId !== integration.id || !sourceKeys.has(source.externalId)) continue;\n const outcome = await moveCardToBoard({ workItems, boardRegistry, userId, item, targetBoard });\n if (outcome === 'moved') moved += 1;\n else if (outcome === 'skipped') skipped += 1;\n }\n return { moved, skipped };\n}\n\n/**\n * After a label route changes, every issue card in the project that carries `label` is re-routed\n * under the project's current routes: to the board its labels now select, or — when none of them is\n * routed any more — to its GitHub repository binding's board, falling back to Work.\n */\nasync function relocateLabeledCards({\n workItems,\n boardRegistry,\n orgId,\n userId,\n factoryProjectId,\n integrationId,\n label,\n routes,\n intake,\n githubRepositories,\n}: {\n workItems: Pick<WorkItemsStorage, 'list' | 'update' | 'supersedeDecisionsForWorkItem'>;\n boardRegistry: BoardRegistry;\n orgId: string;\n userId: string;\n factoryProjectId: string;\n integrationId: string;\n label: string;\n routes: readonly IntakeLabelRoute[];\n intake: Pick<IntakeStorage, 'listBindings'>;\n githubRepositories?: Pick<SourceControlStorageHandle['repositories'], 'get'>;\n}): Promise<{ moved: number; skipped: number }> {\n const changed = normalizeIntakeLabel(label);\n const repositoryBoards =\n integrationId === 'github'\n ? await githubRepositoryBoards({ intake, githubRepositories, boardRegistry, orgId, factoryProjectId })\n : [];\n let moved = 0;\n let skipped = 0;\n for (const item of await workItems.list({ orgId, factoryProjectId })) {\n const source = item.externalSource;\n if (!source || source.integrationId !== integrationId || source.type !== 'issue') continue;\n const labels = cardLabels(item);\n if (!labels.some(candidate => normalizeIntakeLabel(candidate) === changed)) continue;\n const targetBoard =\n resolveIntakeLabelRoute(routes, labels)?.board ??\n repositoryBoards.find(candidate => cardInRepository(item, candidate))?.board ??\n 'work';\n const outcome = await moveCardToBoard({ workItems, boardRegistry, userId, item, targetBoard });\n if (outcome === 'moved') moved += 1;\n else if (outcome === 'skipped') skipped += 1;\n }\n return { moved, skipped };\n}\n\ninterface GithubRepositoryBoard {\n board: string;\n slug: string;\n externalId?: string;\n}\n\n/**\n * The project's GitHub repository bindings that name an installed board. A binding's source id is\n * the internal repository id or the repository slug, so ids are resolved to the slug and external id\n * a card carries.\n */\nasync function githubRepositoryBoards({\n intake,\n githubRepositories,\n boardRegistry,\n orgId,\n factoryProjectId,\n}: {\n intake: Pick<IntakeStorage, 'listBindings'>;\n githubRepositories?: Pick<SourceControlStorageHandle['repositories'], 'get'>;\n boardRegistry: BoardRegistry;\n orgId: string;\n factoryProjectId: string;\n}): Promise<GithubRepositoryBoard[]> {\n const boards: GithubRepositoryBoard[] = [];\n for (const binding of await intake.listBindings({ orgId, integrationId: 'github' })) {\n if (binding.factoryProjectId !== factoryProjectId || !binding.board || !boardRegistry.has(binding.board)) continue;\n const repository = await githubRepositories?.get({ orgId, id: binding.sourceId });\n boards.push(\n repository\n ? { board: binding.board, slug: repository.slug.toLowerCase(), externalId: repository.externalId }\n : { board: binding.board, slug: binding.sourceId.toLowerCase() },\n );\n }\n return boards;\n}\n\nfunction cardInRepository(\n item: { metadata?: Record<string, unknown> | null; externalSource?: { url?: string } | null },\n repository: GithubRepositoryBoard,\n): boolean {\n const repositoryId = item.metadata?.githubRepositoryId;\n if (\n repository.externalId !== undefined &&\n repositoryId !== undefined &&\n String(repositoryId) === repository.externalId\n )\n return true;\n const match = /^https?:\\/\\/[^/]+\\/(.+)\\/(?:issues|pull)\\/\\d+(?:[/?#]|$)/.exec(item.externalSource?.url ?? '');\n return match?.[1]?.toLowerCase() === repository.slug;\n}\n\n/** One integration that failed while the rest of the aggregation succeeded. */\nexport interface IntakeIntegrationFailure {\n integrationId: string;\n message: string;\n}\n\ntype SettledIntegration<T> = { integrationId: string; value: T } | IntakeIntegrationFailure;\n\nconst PROVIDER_READ_TIMEOUT_MS = 15_000;\n\n/** The Intake contract takes no abort signal, so a slow read is abandoned, not cancelled. */\nfunction withTimeout<T>(\n integrationId: string,\n read: () => Promise<T>,\n timeoutMs: number = PROVIDER_READ_TIMEOUT_MS,\n): Promise<T> {\n return new Promise((resolve, reject) => {\n const budget = Math.max(0, timeoutMs);\n const timer = setTimeout(\n () => reject(new Error(`${integrationId} did not answer within ${Math.round(budget / 1000)}s`)),\n budget,\n );\n read()\n .then(resolve, reject)\n .finally(() => clearTimeout(timer));\n });\n}\n\n/**\n * Read every integration concurrently and isolate the ones that throw or hang, so a single\n * unreachable provider degrades to a per-source error instead of failing the listing.\n */\nasync function settleByIntegration<T>(\n requests: Array<{ integrationId: string; read: () => Promise<T> }>,\n): Promise<{ pages: Array<{ integrationId: string; value: T }>; failures: IntakeIntegrationFailure[] }> {\n const settled = await Promise.all(\n requests.map(async ({ integrationId, read }): Promise<SettledIntegration<T>> => {\n try {\n return { integrationId, value: await withTimeout(integrationId, read) };\n } catch (error) {\n console.error(`[factory] intake integration ${integrationId} is unavailable:`, error);\n return { integrationId, message: error instanceof Error ? error.message : String(error) };\n }\n }),\n );\n const pages: Array<{ integrationId: string; value: T }> = [];\n const failures: IntakeIntegrationFailure[] = [];\n for (const entry of settled) {\n if ('value' in entry) pages.push(entry);\n else failures.push(entry);\n }\n return { pages, failures };\n}\n\nconst MAX_INTAKE_SOURCE_ID_LENGTH = 1024;\n\ninterface ParsedBinding {\n integrationId: string;\n sourceId: string;\n factoryProjectId: string | null;\n /** Installed board target; `null`/omitted keeps legacy source-type routing. */\n board: string | null;\n}\n\n/** Validate a binding request body, rejecting unknown shapes. */\nexport function parseIntakeBinding(body: unknown): ParsedBinding | null {\n if (typeof body !== 'object' || body === null || Array.isArray(body)) return null;\n const { integrationId, sourceId, factoryProjectId, board } = body as Record<string, unknown>;\n const isId = (value: unknown) => typeof value === 'string' && value.length > 0 && value.length <= 256;\n const isSourceId = (value: unknown) =>\n typeof value === 'string' && value.length > 0 && value.length <= MAX_INTAKE_SOURCE_ID_LENGTH;\n if (!isId(integrationId) || !isSourceId(sourceId)) return null;\n if (factoryProjectId !== null && !isId(factoryProjectId)) return null;\n if (board !== undefined && board !== null && !isId(board)) return null;\n return {\n integrationId: integrationId as string,\n sourceId: sourceId as string,\n factoryProjectId: factoryProjectId as string | null,\n board: (board as string | null | undefined) ?? null,\n };\n}\n\nconst identifier = z.string().min(1).max(256);\nconst labelRouteBodySchema = z.object({\n factoryProjectId: identifier,\n integrationId: identifier,\n label: z\n .string()\n .max(256)\n .transform(normalizeIntakeLabel)\n .refine(label => label.length > 0, 'label must not be blank'),\n /** Installed board target; `null` removes the route so the label falls back to Work. */\n board: identifier.nullish().transform(board => board ?? null),\n});\ntype ParsedLabelRoute = z.output<typeof labelRouteBodySchema>;\n\n/** Validate a label-route request body, rejecting unknown shapes. */\nexport function parseIntakeLabelRoute(body: unknown): ParsedLabelRoute | null {\n const parsed = labelRouteBodySchema.safeParse(body);\n return parsed.success ? parsed.data : null;\n}\n\nfunction loose(c: unknown): Context {\n return c as Context;\n}\n\nfunction sanitizeIdList(value: unknown): string[] | null | undefined {\n if (value === null) return null;\n if (!Array.isArray(value) || value.length > 200) return undefined;\n const ids = value.filter(\n (item): item is string => typeof item === 'string' && item.length > 0 && item.length <= MAX_INTAKE_SOURCE_ID_LENGTH,\n );\n return ids.length === value.length && new Set(ids).size === ids.length ? ids : undefined;\n}\n\nfunction sanitizeLinearRepositoryMap(value: unknown): Record<string, string> | undefined {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return undefined;\n const entries = Object.entries(value);\n if (entries.length > 200) return undefined;\n const mapping: Record<string, string> = Object.create(null);\n for (const [projectId, slug] of entries) {\n if (\n !projectId ||\n projectId.length > MAX_INTAKE_SOURCE_ID_LENGTH ||\n typeof slug !== 'string' ||\n !slug.trim() ||\n slug.length > 512\n ) {\n return undefined;\n }\n mapping[projectId] = slug;\n }\n return mapping;\n}\n\n/** Validate a request body into an intake config, rejecting unknown shapes. */\nexport function parseIntakeConfig(body: unknown): IntakeConfig | null {\n if (typeof body !== 'object' || body === null || Array.isArray(body)) return null;\n const entries = Object.entries(body);\n if (entries.length > 50) return null;\n\n // Null-prototype so an `__proto__` key lands as a real entry instead of silently\n // reassigning the prototype and disappearing from the validation below.\n const config: IntakeConfig = Object.create(null);\n for (const [integrationId, value] of entries) {\n if (\n !integrationId ||\n integrationId.length > 128 ||\n typeof value !== 'object' ||\n value === null ||\n Array.isArray(value)\n ) {\n return null;\n }\n const selection = value as { enabled?: unknown; sourceIds?: unknown; repositoryByLinearProject?: unknown };\n if (typeof selection.enabled !== 'boolean') return null;\n const sourceIds = sanitizeIdList(selection.sourceIds ?? null);\n if (sourceIds === undefined) return null;\n const repositoryByLinearProject =\n integrationId === 'linear' ? sanitizeLinearRepositoryMap(selection.repositoryByLinearProject) : undefined;\n if (\n integrationId === 'linear' &&\n selection.repositoryByLinearProject !== undefined &&\n repositoryByLinearProject === undefined\n )\n return null;\n config[integrationId] = {\n enabled: selection.enabled,\n sourceIds,\n ...(repositoryByLinearProject ? { repositoryByLinearProject } : {}),\n };\n }\n return config;\n}\n\nfunction encodeCursor(cursors: Record<string, string>): string | null {\n return Object.keys(cursors).length > 0 ? Buffer.from(JSON.stringify(cursors)).toString('base64url') : null;\n}\n\nfunction decodeCursor(value: string | undefined): Record<string, string> | null {\n if (!value) return {};\n try {\n const parsed = JSON.parse(Buffer.from(value, 'base64url').toString('utf8')) as unknown;\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) return null;\n const entries = Object.entries(parsed);\n if (entries.some(([key, cursor]) => !key || typeof cursor !== 'string')) return null;\n return Object.fromEntries(entries) as Record<string, string>;\n } catch {\n return null;\n }\n}\n\nexport class IntakeRoutes extends Route<IntakeRoutesDeps> {\n async #resolveTenant(c: Context): Promise<{ orgId: string; userId: string } | { response: Response }> {\n await this.deps.auth.ensureUser(c);\n const tenant = this.deps.auth.tenant(c);\n if (!tenant) return { response: c.json({ error: 'unauthorized' }, 401) };\n if (!tenant.orgId) {\n return {\n response: c.json(\n { error: 'organization_required', message: 'Intake configuration requires an organization.' },\n 403,\n ),\n };\n }\n return { orgId: tenant.orgId, userId: tenant.userId };\n }\n\n routes(): ApiRoute[] {\n const { audit, intake, projects, integrations = [], boardRegistry, workItems, githubRepositories } = this.deps;\n const integrationIds = integrations.map(integration => integration.id);\n\n return [\n registerApiRoute('/web/intake/config', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n await intake.ensureReady();\n const config = await intake.getConfig({ orgId: tenant.orgId, integrationIds });\n return c.json({ config });\n },\n }),\n registerApiRoute('/web/intake/config', {\n method: 'PUT',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n\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 config = parseIntakeConfig(body);\n if (!config) {\n return c.json({ error: 'invalid_config' }, 400);\n }\n\n const registeredConfig: IntakeConfig = Object.create(null);\n for (const [integrationId, selection] of Object.entries(config)) {\n if (integrationIds.includes(integrationId)) {\n registeredConfig[integrationId] = selection;\n continue;\n }\n if (selection.enabled || selection.sourceIds?.length) {\n return c.json({ error: 'invalid_config' }, 400);\n }\n }\n\n await intake.ensureReady();\n await intake.saveConfig({ orgId: tenant.orgId, config: registeredConfig });\n await audit.emit({\n context: loose(c),\n input: {\n action: 'factory.intake.config_updated',\n targets: [{ type: 'intake_config', id: tenant.orgId }],\n metadata: Object.fromEntries(\n Object.entries(registeredConfig).map(([integrationId, selection]) => [\n integrationId,\n { enabled: selection.enabled, sources: selection.sourceIds?.length ?? null },\n ]),\n ),\n },\n });\n return c.json({ config: registeredConfig });\n },\n }),\n registerApiRoute('/web/intake/bindings', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n await intake.ensureReady();\n return c.json({ bindings: await intake.listBindings({ orgId: tenant.orgId }) });\n },\n }),\n registerApiRoute('/web/intake/bindings', {\n method: 'PUT',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n\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 binding = parseIntakeBinding(body);\n if (!binding || !integrationIds.includes(binding.integrationId)) {\n return c.json({ error: 'invalid_binding' }, 400);\n }\n if (binding.factoryProjectId && projects) {\n const project = await projects.get({ orgId: tenant.orgId, id: binding.factoryProjectId });\n if (!project) return c.json({ error: 'factory_project_not_found' }, 404);\n }\n if (binding.board !== null && !boardRegistry?.has(binding.board)) {\n return c.json({ error: 'invalid_board', message: `Board '${binding.board}' is not installed.` }, 422);\n }\n\n await intake.ensureReady();\n let auditFactoryProjectId = binding.factoryProjectId;\n let relocated: { moved: number; skipped: number } | null = null;\n if (binding.factoryProjectId === null) {\n const previousBinding = await intake.clearBinding({\n orgId: tenant.orgId,\n integrationId: binding.integrationId,\n sourceId: binding.sourceId,\n });\n auditFactoryProjectId = previousBinding?.factoryProjectId ?? null;\n } else {\n const previousBinding = await intake.getBinding({\n orgId: tenant.orgId,\n integrationId: binding.integrationId,\n sourceId: binding.sourceId,\n });\n await intake.setBinding({\n orgId: tenant.orgId,\n userId: tenant.userId,\n integrationId: binding.integrationId,\n sourceId: binding.sourceId,\n factoryProjectId: binding.factoryProjectId,\n board: binding.board,\n });\n // A source that stays in the same project but is pointed at a different board\n // takes its resting cards along. Bindings that predate persisted boards have a\n // null board yet still fed Work/Review, so relocation compares each card's\n // effective board rather than trusting the previous binding value.\n const nextBoard = binding.board;\n const integration = integrations.find(candidate => candidate.id === binding.integrationId);\n if (\n workItems &&\n boardRegistry &&\n integration &&\n previousBinding?.factoryProjectId === binding.factoryProjectId &&\n nextBoard !== null &&\n previousBinding.board !== nextBoard\n ) {\n try {\n const config = await intake.getConfig({\n orgId: tenant.orgId,\n integrationIds: [binding.integrationId],\n });\n const configuredSourceIds = config[binding.integrationId]?.sourceIds ?? [];\n const attributionSourceIds = configuredSourceIds.includes(binding.sourceId)\n ? configuredSourceIds\n : [...configuredSourceIds, binding.sourceId];\n relocated = await relocateSourceCards({\n workItems,\n integration,\n boardRegistry,\n orgId: tenant.orgId,\n userId: tenant.userId,\n factoryProjectId: binding.factoryProjectId,\n sourceId: binding.sourceId,\n attributionSourceIds,\n targetBoard: nextBoard,\n });\n } catch (error) {\n // The binding is saved; the cards can be moved by hand if the provider was unreachable.\n console.error(`[factory] intake rebind could not relocate ${binding.integrationId} cards:`, error);\n }\n }\n }\n await audit.emit({\n context: loose(c),\n input: {\n action: 'factory.intake.binding_updated',\n ...(auditFactoryProjectId ? { factoryProjectId: auditFactoryProjectId } : {}),\n targets: [{ type: 'intake_source', id: `${binding.integrationId}:${binding.sourceId}` }],\n metadata: {\n factoryProjectId: binding.factoryProjectId,\n board: binding.board,\n ...(relocated ? { relocated } : {}),\n },\n },\n });\n return c.json({\n bindings: await intake.listBindings({ orgId: tenant.orgId }),\n ...(relocated ? { relocated } : {}),\n });\n },\n }),\n registerApiRoute('/web/intake/label-routes', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n const factoryProjectId = c.req.query('factoryProjectId');\n await intake.ensureReady();\n return c.json({\n routes: await intake.listLabelRoutes({\n orgId: tenant.orgId,\n ...(factoryProjectId ? { factoryProjectId } : {}),\n }),\n });\n },\n }),\n registerApiRoute('/web/intake/label-routes', {\n method: 'PUT',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n\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 route = parseIntakeLabelRoute(body);\n if (!route || !integrationIds.includes(route.integrationId)) {\n return c.json({ error: 'invalid_label_route' }, 400);\n }\n if (projects) {\n const project = await projects.get({ orgId: tenant.orgId, id: route.factoryProjectId });\n if (!project) return c.json({ error: 'factory_project_not_found' }, 404);\n }\n if (route.board !== null && !boardRegistry?.has(route.board)) {\n return c.json({ error: 'invalid_board', message: `Board '${route.board}' is not installed.` }, 422);\n }\n\n await intake.ensureReady();\n const scope = {\n orgId: tenant.orgId,\n factoryProjectId: route.factoryProjectId,\n integrationId: route.integrationId,\n };\n const previous = (await intake.listLabelRoutes(scope)).find(existing => existing.label === route.label);\n if (route.board === null) {\n await intake.clearLabelRoute({ ...scope, label: route.label });\n } else {\n await intake.setLabelRoute({ ...scope, label: route.label, board: route.board, userId: tenant.userId });\n }\n\n // Cards already carrying the label follow the route: onto the new board, or — when the\n // label is no longer routed anywhere — to the repository binding's board or Work.\n let relocated: { moved: number; skipped: number } | null = null;\n if (workItems && boardRegistry && (previous?.board ?? null) !== route.board) {\n try {\n relocated = await relocateLabeledCards({\n workItems,\n boardRegistry,\n orgId: tenant.orgId,\n userId: tenant.userId,\n factoryProjectId: route.factoryProjectId,\n integrationId: route.integrationId,\n label: route.label,\n routes: await intake.listLabelRoutes(scope),\n intake,\n githubRepositories,\n });\n } catch (error) {\n // The route is saved; the cards can be moved by hand.\n console.error(`[factory] intake label route could not relocate ${route.integrationId} cards:`, error);\n }\n }\n await audit.emit({\n context: loose(c),\n input: {\n action: 'factory.intake.label_route_updated',\n factoryProjectId: route.factoryProjectId,\n targets: [{ type: 'intake_label_route', id: `${route.integrationId}:${route.label}` }],\n metadata: { board: route.board, ...(relocated ? { relocated } : {}) },\n },\n });\n return c.json({\n routes: await intake.listLabelRoutes({ orgId: tenant.orgId, factoryProjectId: route.factoryProjectId }),\n ...(relocated ? { relocated } : {}),\n });\n },\n }),\n registerApiRoute('/web/intake/sources', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n const { pages, failures } = await settleByIntegration(\n integrations.map(integration => ({\n integrationId: integration.id,\n read: () => integration.intake.listSources(tenant),\n })),\n );\n return c.json({\n sources: pages.flatMap(({ integrationId, value }) => value.map(source => ({ integrationId, ...source }))),\n failures,\n });\n },\n }),\n registerApiRoute('/web/intake/items', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n const cursors = decodeCursor(c.req.query('cursor'));\n if (!cursors) return c.json({ error: 'invalid_cursor' }, 400);\n\n await intake.ensureReady();\n const config = await intake.getConfig({ orgId: tenant.orgId, integrationIds });\n const { pages, failures } = await settleByIntegration(\n integrations.flatMap(integration => {\n const selection = config[integration.id];\n if (!selection?.enabled || !selection.sourceIds?.length) return [];\n const sourceIds = selection.sourceIds;\n const cursor = cursors[integration.id];\n return [\n {\n integrationId: integration.id,\n read: () => integration.intake.listItems({ ...tenant, sourceIds, ...(cursor ? { cursor } : {}) }),\n },\n ];\n }),\n );\n\n const items: AggregatedIntakeItem[] = [];\n const nextCursors: Record<string, string> = {};\n for (const { integrationId, value } of pages) {\n items.push(\n ...value.items.map(item => {\n const { source, ...candidate } = item;\n return { ...candidate, integrationId, externalSource: { integrationId, ...source } };\n }),\n );\n if (value.nextCursor) nextCursors[integrationId] = value.nextCursor;\n }\n // Keep the cursor an unavailable integration came in with, so the next page resumes there instead of replaying it.\n for (const { integrationId } of failures) {\n const cursor = cursors[integrationId];\n if (cursor) nextCursors[integrationId] = cursor;\n }\n return c.json({ items, nextCursor: encodeCursor(nextCursors), failures });\n },\n }),\n ];\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"intake.js","names":[],"sources":["../../src/routes/intake.ts"],"sourcesContent":["import type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\nimport { z } from 'zod';\n\nimport type { BoardRegistry } from '../boards/index.js';\nimport { cardLabels, moveCardToBoard } from '../boards/relocate.js';\nimport type { Intake, IntakeItem } from '../capabilities/intake.js';\nimport type { AuditEmitter } from '../storage/domains/audit/domain.js';\nimport { normalizeIntakeLabel, resolveIntakeLabelRoute } from '../storage/domains/intake/base.js';\nimport type { IntakeConfig, IntakeLabelRoute, IntakeStorage } from '../storage/domains/intake/base.js';\nimport type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport type { RouteDependencies } from './route.js';\nimport { Route } from './route.js';\n\nexport interface IntakeIntegration {\n id: string;\n intake: Pick<Intake, 'listSources' | 'listItems'>;\n}\n\ninterface AggregatedIntakeItem extends Omit<IntakeItem, 'source'> {\n integrationId: string;\n externalSource: {\n integrationId: string;\n type: string;\n externalId: string;\n url?: string;\n };\n}\n\nexport interface IntakeRoutesDeps extends RouteDependencies {\n audit: AuditEmitter;\n /** Intake selection domain handle. */\n intake: IntakeStorage;\n /** Factory project domain handle, used to validate binding targets. */\n projects?: { get(input: { orgId: string; id: string }): Promise<unknown | null> };\n integrations?: IntakeIntegration[];\n /** Installed boards, used to validate binding targets. Absent means only legacy routing is accepted. */\n boardRegistry?: BoardRegistry;\n /** Work items domain handle; when present, rebinding a source to another board moves its resting cards. */\n workItems?: Pick<WorkItemsStorage, 'list' | 'update' | 'supersedeDecisionsForWorkItem'>;\n /** GitHub repositories, used to match a card to the repository binding its source id names. */\n githubRepositories?: Pick<SourceControlStorageHandle['repositories'], 'get'>;\n}\n\n/** Upper bound on source pages read while relocating cards, so a huge source cannot stall the request. */\nconst REBIND_MAX_PAGES = 20;\n/**\n * One deadline for the whole rebind read, not one per page: the binding is already saved when we\n * get here, so the caller is only waiting on relocation and a slow provider must not hold the\n * response for pages × timeout.\n */\nconst REBIND_READ_BUDGET_MS = 30_000;\n\n/**\n * Keys under which an intake item may be persisted as a work item. Providers key items by their\n * own id, while materialization uses the human identifier (`linear:MAS-44`), so both are accepted.\n */\nfunction intakeItemSourceKeys(integrationId: string, item: IntakeItem): string[] {\n const identifier = item.metadata?.identifier;\n return typeof identifier === 'string' && identifier.length > 0\n ? [item.source.externalId, `${integrationId}:${identifier}`]\n : [item.source.externalId];\n}\n\n/**\n * Move the cards that came from a rebound source onto its new board's initial phase. Terminal cards\n * stay put (they are history that belongs where it finished), as do cards with a session attached to\n * a current stage (a run owns them). Everything else moves, including cards parked in a working\n * phase that never started a run — auto-ingested issues sit in Work › Triage that way.\n */\nasync function relocateSourceCards({\n workItems,\n integration,\n boardRegistry,\n orgId,\n userId,\n factoryProjectId,\n sourceId,\n targetBoard,\n attributionSourceIds,\n}: {\n workItems: Pick<WorkItemsStorage, 'list' | 'update' | 'supersedeDecisionsForWorkItem'>;\n integration: IntakeIntegration;\n boardRegistry: BoardRegistry;\n orgId: string;\n userId: string;\n factoryProjectId: string;\n sourceId: string;\n targetBoard: string;\n attributionSourceIds: string[];\n}): Promise<{ moved: number; skipped: number }> {\n if (!boardRegistry.has(targetBoard)) return { moved: 0, skipped: 0 };\n\n const sourceKeys = new Set<string>();\n let cursor: string | undefined;\n const deadline = Date.now() + REBIND_READ_BUDGET_MS;\n for (let page = 0; page < REBIND_MAX_PAGES; page += 1) {\n const result = await withTimeout(\n integration.id,\n () =>\n integration.intake.listItems({\n orgId,\n userId,\n sourceIds: [sourceId],\n attributionSourceIds,\n cursor,\n }),\n deadline - Date.now(),\n );\n for (const item of result.items) {\n if (item.sourceId !== sourceId) continue;\n for (const key of intakeItemSourceKeys(integration.id, item)) sourceKeys.add(key);\n }\n if (!result.nextCursor) break;\n cursor = result.nextCursor;\n }\n if (sourceKeys.size === 0) return { moved: 0, skipped: 0 };\n\n let moved = 0;\n let skipped = 0;\n const items = await workItems.list({ orgId, factoryProjectId });\n for (const item of items) {\n const source = item.externalSource;\n if (!source || source.integrationId !== integration.id || !sourceKeys.has(source.externalId)) continue;\n const outcome = await moveCardToBoard({ workItems, boardRegistry, userId, item, targetBoard });\n if (outcome === 'moved') moved += 1;\n else if (outcome === 'skipped') skipped += 1;\n }\n return { moved, skipped };\n}\n\n/**\n * After a label route changes, every issue card in the project that carries `label` is re-routed\n * under the project's current routes: to the board its labels now select, or — when none of them is\n * routed any more — to its GitHub repository binding's board, falling back to Work.\n */\nasync function relocateLabeledCards({\n workItems,\n boardRegistry,\n orgId,\n userId,\n factoryProjectId,\n integrationId,\n label,\n routes,\n intake,\n githubRepositories,\n}: {\n workItems: Pick<WorkItemsStorage, 'list' | 'update' | 'supersedeDecisionsForWorkItem'>;\n boardRegistry: BoardRegistry;\n orgId: string;\n userId: string;\n factoryProjectId: string;\n integrationId: string;\n label: string;\n routes: readonly IntakeLabelRoute[];\n intake: Pick<IntakeStorage, 'listBindings'>;\n githubRepositories?: Pick<SourceControlStorageHandle['repositories'], 'get'>;\n}): Promise<{ moved: number; skipped: number }> {\n const changed = normalizeIntakeLabel(label);\n const repositoryBoards =\n integrationId === 'github'\n ? await githubRepositoryBoards({ intake, githubRepositories, boardRegistry, orgId, factoryProjectId })\n : [];\n let moved = 0;\n let skipped = 0;\n for (const item of await workItems.list({ orgId, factoryProjectId })) {\n const source = item.externalSource;\n if (!source || source.integrationId !== integrationId || source.type !== 'issue') continue;\n const labels = cardLabels(item);\n if (!labels.some(candidate => normalizeIntakeLabel(candidate) === changed)) continue;\n const targetBoard =\n resolveIntakeLabelRoute(routes, labels)?.board ??\n repositoryBoards.find(candidate => cardInRepository(item, candidate))?.board ??\n 'work';\n const outcome = await moveCardToBoard({ workItems, boardRegistry, userId, item, targetBoard });\n if (outcome === 'moved') moved += 1;\n else if (outcome === 'skipped') skipped += 1;\n }\n return { moved, skipped };\n}\n\ninterface GithubRepositoryBoard {\n board: string;\n slug: string;\n externalId?: string;\n}\n\n/**\n * The project's GitHub repository bindings that name an installed board. A binding's source id is\n * the internal repository id or the repository slug, so ids are resolved to the slug and external id\n * a card carries.\n */\nasync function githubRepositoryBoards({\n intake,\n githubRepositories,\n boardRegistry,\n orgId,\n factoryProjectId,\n}: {\n intake: Pick<IntakeStorage, 'listBindings'>;\n githubRepositories?: Pick<SourceControlStorageHandle['repositories'], 'get'>;\n boardRegistry: BoardRegistry;\n orgId: string;\n factoryProjectId: string;\n}): Promise<GithubRepositoryBoard[]> {\n const boards: GithubRepositoryBoard[] = [];\n for (const binding of await intake.listBindings({ orgId, integrationId: 'github' })) {\n if (binding.factoryProjectId !== factoryProjectId || !binding.board || !boardRegistry.has(binding.board)) continue;\n const repository = await githubRepositories?.get({ orgId, id: binding.sourceId });\n boards.push(\n repository\n ? { board: binding.board, slug: repository.slug.toLowerCase(), externalId: repository.externalId }\n : { board: binding.board, slug: binding.sourceId.toLowerCase() },\n );\n }\n return boards;\n}\n\nfunction cardInRepository(\n item: { metadata?: Record<string, unknown> | null; externalSource?: { url?: string } | null },\n repository: GithubRepositoryBoard,\n): boolean {\n const repositoryId = item.metadata?.githubRepositoryId;\n if (\n repository.externalId !== undefined &&\n repositoryId !== undefined &&\n String(repositoryId) === repository.externalId\n )\n return true;\n const match = /^https?:\\/\\/[^/]+\\/(.+)\\/(?:issues|pull)\\/\\d+(?:[/?#]|$)/.exec(item.externalSource?.url ?? '');\n return match?.[1]?.toLowerCase() === repository.slug;\n}\n\n/** One integration that failed while the rest of the aggregation succeeded. */\nexport interface IntakeIntegrationFailure {\n integrationId: string;\n message: string;\n}\n\ntype SettledIntegration<T> = { integrationId: string; value: T } | IntakeIntegrationFailure;\n\nconst PROVIDER_READ_TIMEOUT_MS = 15_000;\n\n/** The Intake contract takes no abort signal, so a slow read is abandoned, not cancelled. */\nfunction withTimeout<T>(\n integrationId: string,\n read: () => Promise<T>,\n timeoutMs: number = PROVIDER_READ_TIMEOUT_MS,\n): Promise<T> {\n return new Promise((resolve, reject) => {\n const budget = Math.max(0, timeoutMs);\n const timer = setTimeout(\n () => reject(new Error(`${integrationId} did not answer within ${Math.round(budget / 1000)}s`)),\n budget,\n );\n read()\n .then(resolve, reject)\n .finally(() => clearTimeout(timer));\n });\n}\n\n/**\n * Read every integration concurrently and isolate the ones that throw or hang, so a single\n * unreachable provider degrades to a per-source error instead of failing the listing.\n */\nasync function settleByIntegration<T>(\n requests: Array<{ integrationId: string; read: () => Promise<T> }>,\n): Promise<{ pages: Array<{ integrationId: string; value: T }>; failures: IntakeIntegrationFailure[] }> {\n const settled = await Promise.all(\n requests.map(async ({ integrationId, read }): Promise<SettledIntegration<T>> => {\n try {\n return { integrationId, value: await withTimeout(integrationId, read) };\n } catch (error) {\n console.error(`[factory] intake integration ${integrationId} is unavailable:`, error);\n return { integrationId, message: error instanceof Error ? error.message : String(error) };\n }\n }),\n );\n const pages: Array<{ integrationId: string; value: T }> = [];\n const failures: IntakeIntegrationFailure[] = [];\n for (const entry of settled) {\n if ('value' in entry) pages.push(entry);\n else failures.push(entry);\n }\n return { pages, failures };\n}\n\nconst MAX_INTAKE_SOURCE_ID_LENGTH = 1024;\n\ninterface ParsedBinding {\n integrationId: string;\n sourceId: string;\n factoryProjectId: string | null;\n /** Installed board target; `null`/omitted keeps legacy source-type routing. */\n board: string | null;\n}\n\n/** Validate a binding request body, rejecting unknown shapes. */\nexport function parseIntakeBinding(body: unknown): ParsedBinding | null {\n if (typeof body !== 'object' || body === null || Array.isArray(body)) return null;\n const { integrationId, sourceId, factoryProjectId, board } = body as Record<string, unknown>;\n const isId = (value: unknown) => typeof value === 'string' && value.length > 0 && value.length <= 256;\n const isSourceId = (value: unknown) =>\n typeof value === 'string' && value.length > 0 && value.length <= MAX_INTAKE_SOURCE_ID_LENGTH;\n if (!isId(integrationId) || !isSourceId(sourceId)) return null;\n if (factoryProjectId !== null && !isId(factoryProjectId)) return null;\n if (board !== undefined && board !== null && !isId(board)) return null;\n return {\n integrationId: integrationId as string,\n sourceId: sourceId as string,\n factoryProjectId: factoryProjectId as string | null,\n board: (board as string | null | undefined) ?? null,\n };\n}\n\nconst identifier = z.string().min(1).max(256);\nconst labelRouteBodySchema = z.object({\n factoryProjectId: identifier,\n integrationId: identifier,\n label: z\n .string()\n .max(256)\n .transform(normalizeIntakeLabel)\n .refine(label => label.length > 0, 'label must not be blank'),\n /** Installed board target; `null` removes the route so the label falls back to Work. */\n board: identifier.nullish().transform(board => board ?? null),\n});\ntype ParsedLabelRoute = z.output<typeof labelRouteBodySchema>;\n\n/** Validate a label-route request body, rejecting unknown shapes. */\nexport function parseIntakeLabelRoute(body: unknown): ParsedLabelRoute | null {\n const parsed = labelRouteBodySchema.safeParse(body);\n return parsed.success ? parsed.data : null;\n}\n\nfunction loose(c: unknown): Context {\n return c as Context;\n}\n\nfunction sanitizeIdList(value: unknown): string[] | null | undefined {\n if (value === null) return null;\n if (!Array.isArray(value) || value.length > 200) return undefined;\n const ids = value.filter(\n (item): item is string => typeof item === 'string' && item.length > 0 && item.length <= MAX_INTAKE_SOURCE_ID_LENGTH,\n );\n return ids.length === value.length && new Set(ids).size === ids.length ? ids : undefined;\n}\n\nfunction sanitizeLinearRepositoryMap(value: unknown): Record<string, string> | undefined {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return undefined;\n const entries = Object.entries(value);\n if (entries.length > 200) return undefined;\n const mapping: Record<string, string> = Object.create(null);\n for (const [projectId, slug] of entries) {\n if (\n !projectId ||\n projectId.length > MAX_INTAKE_SOURCE_ID_LENGTH ||\n typeof slug !== 'string' ||\n !slug.trim() ||\n slug.length > 512\n ) {\n return undefined;\n }\n mapping[projectId] = slug;\n }\n return mapping;\n}\n\n/** Validate a request body into an intake config, rejecting unknown shapes. */\nexport function parseIntakeConfig(body: unknown): IntakeConfig | null {\n if (typeof body !== 'object' || body === null || Array.isArray(body)) return null;\n const entries = Object.entries(body);\n if (entries.length > 50) return null;\n\n // Null-prototype so an `__proto__` key lands as a real entry instead of silently\n // reassigning the prototype and disappearing from the validation below.\n const config: IntakeConfig = Object.create(null);\n for (const [integrationId, value] of entries) {\n if (\n !integrationId ||\n integrationId.length > 128 ||\n typeof value !== 'object' ||\n value === null ||\n Array.isArray(value)\n ) {\n return null;\n }\n const selection = value as { enabled?: unknown; sourceIds?: unknown; repositoryByLinearProject?: unknown };\n if (typeof selection.enabled !== 'boolean') return null;\n const sourceIds = sanitizeIdList(selection.sourceIds ?? null);\n if (sourceIds === undefined) return null;\n const repositoryByLinearProject =\n integrationId === 'linear' ? sanitizeLinearRepositoryMap(selection.repositoryByLinearProject) : undefined;\n if (\n integrationId === 'linear' &&\n selection.repositoryByLinearProject !== undefined &&\n repositoryByLinearProject === undefined\n )\n return null;\n config[integrationId] = {\n enabled: selection.enabled,\n sourceIds,\n ...(repositoryByLinearProject ? { repositoryByLinearProject } : {}),\n };\n }\n return config;\n}\n\nfunction encodeCursor(cursors: Record<string, string>): string | null {\n return Object.keys(cursors).length > 0 ? Buffer.from(JSON.stringify(cursors)).toString('base64url') : null;\n}\n\nfunction decodeCursor(value: string | undefined): Record<string, string> | null {\n if (!value) return {};\n try {\n const parsed = JSON.parse(Buffer.from(value, 'base64url').toString('utf8')) as unknown;\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) return null;\n const entries = Object.entries(parsed);\n if (entries.some(([key, cursor]) => !key || typeof cursor !== 'string')) return null;\n return Object.fromEntries(entries) as Record<string, string>;\n } catch {\n return null;\n }\n}\n\nexport class IntakeRoutes extends Route<IntakeRoutesDeps> {\n async #resolveTenant(c: Context): Promise<{ orgId: string; userId: string } | { response: Response }> {\n await this.deps.auth.ensureUser(c);\n const tenant = this.deps.auth.tenant(c);\n if (!tenant) return { response: c.json({ error: 'unauthorized' }, 401) };\n if (!tenant.orgId) {\n return {\n response: c.json(\n { error: 'organization_required', message: 'Intake configuration requires an organization.' },\n 403,\n ),\n };\n }\n return { orgId: tenant.orgId, userId: tenant.userId };\n }\n\n routes(): ApiRoute[] {\n const { audit, intake, projects, integrations = [], boardRegistry, workItems, githubRepositories } = this.deps;\n const integrationIds = integrations.map(integration => integration.id);\n\n return [\n registerApiRoute('/web/intake/config', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n await intake.ensureReady();\n const config = await intake.getConfig({ orgId: tenant.orgId, integrationIds });\n return c.json({ config });\n },\n }),\n registerApiRoute('/web/intake/config', {\n method: 'PUT',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n\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 config = parseIntakeConfig(body);\n if (!config) {\n return c.json({ error: 'invalid_config' }, 400);\n }\n\n const registeredConfig: IntakeConfig = Object.create(null);\n for (const [integrationId, selection] of Object.entries(config)) {\n if (integrationIds.includes(integrationId)) {\n registeredConfig[integrationId] = selection;\n continue;\n }\n if (selection.enabled || selection.sourceIds?.length) {\n return c.json({ error: 'invalid_config' }, 400);\n }\n }\n\n await intake.ensureReady();\n await intake.saveConfig({ orgId: tenant.orgId, config: registeredConfig });\n await audit.emit({\n context: loose(c),\n input: {\n action: 'factory.intake.config_updated',\n targets: [{ type: 'intake_config', id: tenant.orgId }],\n metadata: Object.fromEntries(\n Object.entries(registeredConfig).map(([integrationId, selection]) => [\n integrationId,\n { enabled: selection.enabled, sources: selection.sourceIds?.length ?? null },\n ]),\n ),\n },\n });\n return c.json({ config: registeredConfig });\n },\n }),\n registerApiRoute('/web/intake/bindings', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n await intake.ensureReady();\n return c.json({ bindings: await intake.listBindings({ orgId: tenant.orgId }) });\n },\n }),\n registerApiRoute('/web/intake/bindings', {\n method: 'PUT',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n\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 binding = parseIntakeBinding(body);\n if (!binding || !integrationIds.includes(binding.integrationId)) {\n return c.json({ error: 'invalid_binding' }, 400);\n }\n if (binding.factoryProjectId && projects) {\n const project = await projects.get({ orgId: tenant.orgId, id: binding.factoryProjectId });\n if (!project) return c.json({ error: 'factory_project_not_found' }, 404);\n }\n if (binding.board !== null && !boardRegistry?.has(binding.board)) {\n return c.json({ error: 'invalid_board', message: `Board '${binding.board}' is not installed.` }, 422);\n }\n\n await intake.ensureReady();\n let auditFactoryProjectId = binding.factoryProjectId;\n let relocated: { moved: number; skipped: number } | null = null;\n if (binding.factoryProjectId === null) {\n const previousBinding = await intake.clearBinding({\n orgId: tenant.orgId,\n integrationId: binding.integrationId,\n sourceId: binding.sourceId,\n });\n auditFactoryProjectId = previousBinding?.factoryProjectId ?? null;\n } else {\n const previousBinding = await intake.getBinding({\n orgId: tenant.orgId,\n integrationId: binding.integrationId,\n sourceId: binding.sourceId,\n });\n await intake.setBinding({\n orgId: tenant.orgId,\n userId: tenant.userId,\n integrationId: binding.integrationId,\n sourceId: binding.sourceId,\n factoryProjectId: binding.factoryProjectId,\n board: binding.board,\n });\n // A source that stays in the same project but is pointed at a different board\n // takes its resting cards along. Bindings that predate persisted boards have a\n // null board yet still fed Work/Review, so relocation compares each card's\n // effective board rather than trusting the previous binding value.\n const nextBoard = binding.board;\n const integration = integrations.find(candidate => candidate.id === binding.integrationId);\n if (\n workItems &&\n boardRegistry &&\n integration &&\n previousBinding?.factoryProjectId === binding.factoryProjectId &&\n nextBoard !== null &&\n previousBinding.board !== nextBoard\n ) {\n try {\n const config = await intake.getConfig({\n orgId: tenant.orgId,\n integrationIds: [binding.integrationId],\n });\n const configuredSourceIds = config[binding.integrationId]?.sourceIds ?? [];\n const attributionSourceIds = configuredSourceIds.includes(binding.sourceId)\n ? configuredSourceIds\n : [...configuredSourceIds, binding.sourceId];\n relocated = await relocateSourceCards({\n workItems,\n integration,\n boardRegistry,\n orgId: tenant.orgId,\n userId: tenant.userId,\n factoryProjectId: binding.factoryProjectId,\n sourceId: binding.sourceId,\n attributionSourceIds,\n targetBoard: nextBoard,\n });\n } catch (error) {\n // The binding is saved; the cards can be moved by hand if the provider was unreachable.\n console.error(`[factory] intake rebind could not relocate ${binding.integrationId} cards:`, error);\n }\n }\n }\n await audit.emit({\n context: loose(c),\n input: {\n action: 'factory.intake.binding_updated',\n ...(auditFactoryProjectId ? { factoryProjectId: auditFactoryProjectId } : {}),\n targets: [{ type: 'intake_source', id: `${binding.integrationId}:${binding.sourceId}` }],\n metadata: {\n factoryProjectId: binding.factoryProjectId,\n board: binding.board,\n ...(relocated ? { relocated } : {}),\n },\n },\n });\n return c.json({\n bindings: await intake.listBindings({ orgId: tenant.orgId }),\n ...(relocated ? { relocated } : {}),\n });\n },\n }),\n registerApiRoute('/web/intake/label-routes', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n const factoryProjectId = c.req.query('factoryProjectId');\n await intake.ensureReady();\n return c.json({\n routes: await intake.listLabelRoutes({\n orgId: tenant.orgId,\n ...(factoryProjectId ? { factoryProjectId } : {}),\n }),\n });\n },\n }),\n registerApiRoute('/web/intake/label-routes', {\n method: 'PUT',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n\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 route = parseIntakeLabelRoute(body);\n if (!route || !integrationIds.includes(route.integrationId)) {\n return c.json({ error: 'invalid_label_route' }, 400);\n }\n if (projects) {\n const project = await projects.get({ orgId: tenant.orgId, id: route.factoryProjectId });\n if (!project) return c.json({ error: 'factory_project_not_found' }, 404);\n }\n if (route.board !== null && !boardRegistry?.has(route.board)) {\n return c.json({ error: 'invalid_board', message: `Board '${route.board}' is not installed.` }, 422);\n }\n\n await intake.ensureReady();\n const scope = {\n orgId: tenant.orgId,\n factoryProjectId: route.factoryProjectId,\n integrationId: route.integrationId,\n };\n const previous = (await intake.listLabelRoutes(scope)).find(existing => existing.label === route.label);\n if (route.board === null) {\n await intake.clearLabelRoute({ ...scope, label: route.label });\n } else {\n await intake.setLabelRoute({ ...scope, label: route.label, board: route.board, userId: tenant.userId });\n }\n\n // Cards already carrying the label follow the route: onto the new board, or — when the\n // label is no longer routed anywhere — to the repository binding's board or Work.\n let relocated: { moved: number; skipped: number } | null = null;\n if (workItems && boardRegistry && (previous?.board ?? null) !== route.board) {\n try {\n relocated = await relocateLabeledCards({\n workItems,\n boardRegistry,\n orgId: tenant.orgId,\n userId: tenant.userId,\n factoryProjectId: route.factoryProjectId,\n integrationId: route.integrationId,\n label: route.label,\n routes: await intake.listLabelRoutes(scope),\n intake,\n githubRepositories,\n });\n } catch (error) {\n // The route is saved; the cards can be moved by hand.\n console.error(`[factory] intake label route could not relocate ${route.integrationId} cards:`, error);\n }\n }\n await audit.emit({\n context: loose(c),\n input: {\n action: 'factory.intake.label_route_updated',\n factoryProjectId: route.factoryProjectId,\n targets: [{ type: 'intake_label_route', id: `${route.integrationId}:${route.label}` }],\n metadata: { board: route.board, ...(relocated ? { relocated } : {}) },\n },\n });\n return c.json({\n routes: await intake.listLabelRoutes({ orgId: tenant.orgId, factoryProjectId: route.factoryProjectId }),\n ...(relocated ? { relocated } : {}),\n });\n },\n }),\n registerApiRoute('/web/intake/sources', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n const { pages, failures } = await settleByIntegration(\n integrations.map(integration => ({\n integrationId: integration.id,\n read: () => integration.intake.listSources(tenant),\n })),\n );\n return c.json({\n sources: pages.flatMap(({ integrationId, value }) => value.map(source => ({ integrationId, ...source }))),\n failures,\n });\n },\n }),\n registerApiRoute('/web/intake/items', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n const cursors = decodeCursor(c.req.query('cursor'));\n if (!cursors) return c.json({ error: 'invalid_cursor' }, 400);\n\n await intake.ensureReady();\n const config = await intake.getConfig({ orgId: tenant.orgId, integrationIds });\n const { pages, failures } = await settleByIntegration(\n integrations.flatMap(integration => {\n const selection = config[integration.id];\n if (!selection?.enabled || !selection.sourceIds?.length) return [];\n const sourceIds = selection.sourceIds;\n const cursor = cursors[integration.id];\n return [\n {\n integrationId: integration.id,\n read: () => integration.intake.listItems({ ...tenant, sourceIds, ...(cursor ? { cursor } : {}) }),\n },\n ];\n }),\n );\n\n const items: AggregatedIntakeItem[] = [];\n const nextCursors: Record<string, string> = {};\n for (const { integrationId, value } of pages) {\n items.push(\n ...value.items.map(item => {\n const { source, ...candidate } = item;\n return { ...candidate, integrationId, externalSource: { integrationId, ...source } };\n }),\n );\n if (value.nextCursor) nextCursors[integrationId] = value.nextCursor;\n }\n // Keep the cursor an unavailable integration came in with, so the next page resumes there instead of replaying it.\n for (const { integrationId } of failures) {\n const cursor = cursors[integrationId];\n if (cursor) nextCursors[integrationId] = cursor;\n }\n return c.json({ items, nextCursor: encodeCursor(nextCursors), failures });\n },\n }),\n ];\n }\n}\n"],"mappings":"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"}
|
package/dist/routes/knowledge.js
CHANGED
|
@@ -7,7 +7,7 @@ const PINNED_NODE_NAME = "pinned";
|
|
|
7
7
|
/** Hover-card budget for record text shipped in the graph payload. */
|
|
8
8
|
const RECORD_TEXT_LIMIT = 240;
|
|
9
9
|
function truncateRecordText(text) {
|
|
10
|
-
return text.length > RECORD_TEXT_LIMIT ? `${text.slice(0,
|
|
10
|
+
return text.length > RECORD_TEXT_LIMIT ? `${text.slice(0, 239)}…` : text;
|
|
11
11
|
}
|
|
12
12
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
13
13
|
/** Authorized visible events returned per activity page. */
|