@mastra/factory 0.23.0-alpha.3 → 0.23.0-alpha.5

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.
@@ -1 +1 @@
1
- {"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../../../src/integrations/slack/slack.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../../../src/integrations/slack/slack.ts"],"names":[],"mappings":"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"}
@@ -260,6 +260,7 @@ function createChannelSessionResolver(deps) {
260
260
  id: thread.resourceId,
261
261
  ownerId: controller.id,
262
262
  resourceId: thread.resourceId,
263
+ threadId: thread.id,
263
264
  requestContext,
264
265
  ...session?.factoryProjectId ? { tags: { factoryProjectId: session.factoryProjectId } } : {}
265
266
  });
@@ -1 +1 @@
1
- {"version":3,"file":"slack.js","names":[],"sources":["../../../src/integrations/slack/slack.ts"],"sourcesContent":["import { migratePersistedModelSelection } from '@mastra/core/agent-controller';\nimport { ChannelSessionRejectedError } from '@mastra/core/channels';\nimport type {\n ChannelHandler,\n ChannelHandlerContext,\n ChannelHandlers,\n ChannelSessionResolve,\n ChannelSessionStart,\n ResolveResourceId,\n ResolveThreadId,\n} from '@mastra/core/channels';\nimport type { Mastra } from '@mastra/core/mastra';\nimport { createSlackAdapter } from '@mastra/slack';\nimport type { SlackAdapterChannelConfig } from '@mastra/slack';\nimport { Card, CardText, Actions, LinkButton } from 'chat';\n\nimport {\n primeTenantCredentialsForRequestContext,\n primeTenantCredentialsForTenant,\n} from '../../routes/tenant-credentials.js';\nimport {\n createSourceControlSessionLookup,\n FactorySourceControlConflictError,\n hydrateFactorySession,\n resolveFactorySessionModel,\n resolveFactorySourceControl,\n resolvePrimaryEnvironmentRepository,\n} from '../../session/factory-session.js';\nimport { readRequestContextOrgId, seedSessionOrg } from '../../session/org-seed.js';\nimport type {\n ChannelAccountLink,\n ChannelAccountLinkKey,\n ChannelIdentityStorage,\n} from '../../storage/domains/channel-identity/base.js';\nimport type { FactoryActorExternalIdentity } from '../../storage/domains/comments/actor.js';\nimport { actorFromChannelAuthor } from '../../storage/domains/comments/actor.js';\nimport type { CommentsDomain } from '../../storage/domains/comments/domain.js';\nimport type { ModelDefaultsStorage } from '../../storage/domains/model-defaults/base.js';\nimport type { FactoryProjectsStorage } from '../../storage/domains/projects/base.js';\nimport type { SourceControlStorageHandle } from '../../storage/domains/source-control/base.js';\nimport type { ExternalWorkItemSource, WorkItemRow, WorkItemsStorage } from '../../storage/domains/work-items/base.js';\nimport type { FactoryChannelsConfig } from '../base.js';\nimport { prepareSessionRunContext } from '../subscription-session.js';\n\nimport { resolveEmojiShortcodes } from './emoji.js';\nimport { slackCommentSource } from './feed-publisher.js';\n\n// Derive the thread/message types from the core handler signature rather than\n// importing them from `chat` directly: mc-web can resolve a different `chat`\n// version than @mastra/core, and the two `Thread`/`Message` declarations are\n// structurally incompatible (private fields). Using the handler's own types\n// keeps everything on one version.\ntype HandlerThread = Parameters<ChannelHandler>[0];\ntype HandlerMessage = Parameters<ChannelHandler>[1];\n\nconst SLACK_REQUEST_TIMEOUT_MS = 15_000;\n\nclass SlackSessionStartError extends Error {}\n\n/** Dependencies the Slack channel handlers close over, injected from the web entry. */\ninterface SlackChannelDeps {\n /**\n * The factory's reverse-index store mapping a Slack sender to a Mastra\n * tenant. When provided, inbound messages from an unlinked sender are not\n * dispatched — the run only proceeds (with the sender's tenant stamped on\n * the request context) once they've linked their account. Unlinked senders\n * get an ephemeral \"connect your account\" card instead.\n */\n accountLinks?: ChannelIdentityStorage;\n /**\n * Factory projects domain. When provided (alongside `accountLinks`), a\n * linked sender's run must also resolve to a Factory project before it\n * dispatches: their link's default factory, else their tenant's only\n * factory (stamped back onto the link), else a \"pick a default factory\"\n * card posted in the thread and no run. Unset → no factory routing (runs\n * dispatch as before).\n */\n projects?: FactoryProjectsStorage;\n /**\n * Storage handle of the integration that owns source control\n * (`IntegrationContext.storage.sourceControlOwner`). Used to make new Slack\n * threads repo-backed: when the sender is linked and their factory has a\n * repository, the thread's resourceId becomes a Factory user-session id (repo\n * cloned on a `slack/{threadTs}` branch) instead of the chat-only\n * `channel:...` id. It also lets a started session read back the project it\n * belongs to. Nothing here is provider-specific — the connection is matched\n * by the handle's own `integrationId`. Absent (no source-control integration\n * registered) → chat-only sessions as before.\n */\n sourceControl?: SourceControlStorageHandle;\n /** Registered source-control partitions used to select the provider linked to each Factory project. */\n sourceControls?: readonly SourceControlStorageHandle[];\n /**\n * Model-defaults domain. When provided, a new repo-backed session starts on\n * the linked sender's default model before the factory's shared default. Read\n * once, when the thread's build model is first chosen; the choice is persisted\n * on the thread and never re-resolved. Unset → the factory default, as before.\n */\n modelDefaults?: ModelDefaultsStorage;\n /**\n * Factory work-items domain, used to find the card a Slack thread already\n * has so an `aside` can land on it. New threads never create cards.\n */\n workItems?: WorkItemsStorage;\n /**\n * Work-item feed. With it (and `workItems`), an `aside` lands as a comment on\n * the card the thread already has. Unset → asides stay ignored, as before.\n */\n feed?: Pick<CommentsDomain, 'createComment'>;\n /** Overrides applied to the Slack channel adapter entry. */\n adapterOptions?: SlackAdapterChannelConfig;\n}\n\nfunction configuredSourceControls(deps: SlackChannelDeps): readonly SourceControlStorageHandle[] {\n return deps.sourceControls ?? (deps.sourceControl ? [deps.sourceControl] : []);\n}\n\n/**\n * Read the Slack team id off a raw platform payload (Events API envelope or\n * slash-command body — both carry `team_id`), duck-typed to build the\n * workspace-scoped account-link key.\n */\nfunction rawTeamId(rawPayload: unknown): string | undefined {\n if (!rawPayload || typeof rawPayload !== 'object') return undefined;\n const raw = rawPayload as { team_id?: unknown; team?: unknown };\n if (typeof raw.team_id === 'string' && raw.team_id) return raw.team_id;\n if (typeof raw.team === 'string' && raw.team) return raw.team;\n if (raw.team && typeof raw.team === 'object') {\n const id = (raw.team as { id?: unknown }).id;\n if (typeof id === 'string' && id) return id;\n }\n return undefined;\n}\n\n/**\n * The Slack team id survives onto a normalized chat Message only on\n * `message.raw` (the Slack Events API envelope).\n */\nfunction slackTeamId(message: HandlerMessage): string | undefined {\n return rawTeamId(message.raw);\n}\n\n/**\n * Resolve the web-UI origin for links humans open in a browser (Connect card,\n * session deep links). Prefers `MASTRACODE_PUBLIC_URL` — the origin auth\n * cookies and OAuth redirect allow-lists are registered against — over the\n * channels tunnel, which only Slack's servers need to reach.\n */\nfunction webPublicUrl(): string | undefined {\n return process.env.MASTRACODE_PUBLIC_URL ?? process.env.MASTRACODE_CHANNELS_PUBLIC_URL;\n}\n\n/** Outcome of the sender-link gate for one inbound message. */\ntype LinkedSenderResult =\n /** Gating not configured — dispatch as before account linking existed. */\n | { status: 'ungated' }\n /** Sender unlinked — Connect card posted (when possible), do not dispatch. */\n | { status: 'blocked' }\n /** Sender linked — their tenant plus the sender key the link lives under. */\n | { status: 'linked'; link: ChannelAccountLink; key: ChannelAccountLinkKey };\n\n/**\n * Resolve the sender's account link, posting an ephemeral \"connect your\n * account\" card (visible only to the sender) linking into the web UI's\n * Slack-connect flow when they're unlinked.\n */\nexport async function resolveLinkedSender({\n thread,\n message,\n accountLinks,\n}: {\n thread: HandlerThread;\n message: HandlerMessage;\n accountLinks?: ChannelIdentityStorage;\n}): Promise<LinkedSenderResult> {\n if (!accountLinks) return { status: 'ungated' };\n const platform = thread.adapter.name;\n const externalUserId = message.author.userId;\n const externalTeamId = slackTeamId(message);\n // Without a team id we can't identify the workspace-scoped link; treat as\n // unlinked so a run never proceeds tenant-less.\n const key = externalTeamId ? { platform, externalTeamId, externalUserId } : undefined;\n const link = key ? await accountLinks.getAccountLink(key) : null;\n if (link && key) return { status: 'linked', link, key };\n\n const publicUrl = webPublicUrl();\n // A public origin is all the card needs. The link carries no identity: the\n // web app authenticates the visitor, then Slack's OIDC flow proves which\n // Slack account they control. Without an origin, still block, just no card.\n if (publicUrl) {\n await thread.postEphemeral(message.author, buildConnectCard(publicUrl, thread), { fallbackToDM: true });\n }\n return { status: 'blocked' };\n}\n\nconst retryHint = (thread: HandlerThread) => (thread.isDM ? 'message me again' : 'mention me again');\n\n/**\n * The \"connect your account\" card. The link is deliberately identity-free —\n * `/connect/slack` sends the visitor to Connections, where \"Connect Slack\"\n * runs the OIDC flow and Slack itself asserts the (team, user) pair.\n */\nfunction buildConnectCard(publicUrl: string, thread: HandlerThread) {\n return Card({\n title: 'Connect your account',\n children: [\n CardText(`Connect your account to use this agent, then ${retryHint(thread)}.`),\n Actions([\n LinkButton({\n url: `${publicUrl}/connect/slack`,\n label: 'Connect account',\n }),\n ]),\n ],\n });\n}\n\n/** Outcome of factory routing for one linked sender's inbound message. */\ntype FactoryRouteResult =\n /** Factory routing not configured — dispatch without a factory. */\n | { status: 'ungated' }\n /** No factory resolved — prompt card posted (when possible), do not dispatch. */\n | { status: 'blocked' }\n /** The Factory project this sender's runs route to. */\n | { status: 'resolved'; factoryProjectId: string };\n\n/**\n * Decide which Factory project a linked sender's run belongs to:\n *\n * 1. The link's `defaultFactoryProjectId`, when it still exists (a stale id —\n * deleted factory — falls through as if unset).\n * 2. Else, the tenant's only factory, stamped back onto the link so it shows\n * up (and stays editable) in Connected Accounts settings.\n * 3. Else — zero or several factories — a \"pick a default factory\" card\n * posted publicly in the thread so Slack notifies the sender, and the run is\n * blocked.\n */\nexport async function resolveFactoryForLink({\n thread,\n link,\n key,\n accountLinks,\n projects,\n}: {\n thread: HandlerThread;\n link: ChannelAccountLink;\n key: ChannelAccountLinkKey;\n accountLinks: ChannelIdentityStorage;\n projects?: FactoryProjectsStorage;\n}): Promise<FactoryRouteResult> {\n if (!projects) return { status: 'ungated' };\n // Factories are org-scoped; a personal account (no org) has none and lands\n // on the prompt below.\n const orgId = link.orgId ?? '';\n\n if (link.defaultFactoryProjectId) {\n const existing = await projects.get({ orgId, id: link.defaultFactoryProjectId });\n if (existing) {\n return { status: 'resolved', factoryProjectId: existing.id };\n }\n }\n\n const factories = orgId ? await projects.list({ orgId }) : [];\n if (factories.length === 1) {\n const only = factories[0]!;\n await accountLinks.setDefaultFactory({ ...key, userId: link.userId, factoryProjectId: only.id });\n return { status: 'resolved', factoryProjectId: only.id };\n }\n\n const publicUrl = webPublicUrl();\n if (publicUrl) {\n await thread.post(\n Card({\n title: 'Pick a default factory',\n children: [\n CardText(\n factories.length === 0\n ? `Your account has no factory yet. Create one in the web app, then ${retryHint(thread)}.`\n : `Your account has several factories. Pick which one Slack sessions should go to, then ${retryHint(thread)}.`,\n ),\n Actions([\n LinkButton({\n url: `${publicUrl}/settings/connections`,\n label: 'Open settings',\n }),\n ]),\n ],\n }),\n );\n }\n return { status: 'blocked' };\n}\n\n/**\n * Deterministic per-thread branch name: `slack/{threadTs}` with characters\n * outside the sandbox git-ref allow-list (`[A-Za-z0-9_./-]`, and `.` for\n * readability) mapped to `-`. `thread.id` is `{channelId}:{threadTs}`\n * (platform-prefixed on handler threads) — the trailing segment is the ts.\n *\n * Top-level DM and channel conversations use the empty-threadTs thread form,\n * so the trailing segment can be empty; a bare `slack/` is not a valid git\n * ref. Fall back to the last non-empty segment (the channel id): one\n * deterministic branch per top-level conversation.\n */\nfunction threadBranch(threadId: string): string {\n const segments = threadId.split(':');\n const tail = segments.findLast(segment => segment.length > 0) ?? threadId;\n return `slack/${tail.replace(/[^A-Za-z0-9_/-]/g, '-')}`;\n}\n\n/**\n * Resolve the resourceId for a NEW Slack channel thread. A linked sender whose\n * factory has a repository gets a Factory user-session id — the controller\n * session then materializes the repo sandbox via the factory's dynamic\n * workspace (clone + PAT), the session shows up in the web Sessions list, and\n * View Session deep-links land on the normal workspace route.\n *\n * A gated deployment refuses a thread it cannot place, rather than falling back\n * to a chat-only session: a repo-backed thread is the whole point of the\n * integration, and answering in a chat-only one would run the sender's request\n * in no project, on the SDK's built-in defaults. A linked sender whose project\n * cannot start a session gets an actionable error in Slack; the dispatch gate\n * handles unlinked or unrouted senders before this hook runs.\n * The hook only runs when a NEW thread is actually created (`getOrCreateThread`\n * resolves the owner lazily), so an established conversation is never touched:\n * it keeps its session, its model, and its history.\n *\n * The chat-only `channel:...` id survives for two cases where refusing would be\n * wrong: a deployment that cannot produce a repo-backed thread at all (no\n * account linking, no projects, or no source-control integration registered),\n * and a message carrying no sender id. In both, a `channel:` thread is the only\n * shape a session can take.\n *\n * Pure lookups only — cards for unlinked/unrouted senders are the dispatch\n * gate's job; this hook must never post.\n */\nexport function createChannelResourceIdResolver(deps: SlackChannelDeps): ResolveResourceId {\n const { accountLinks, projects } = deps;\n const sourceControls = configuredSourceControls(deps);\n return async ({ platform, thread, message }) => {\n // NOT the hook's `defaultResourceId`: configuring a custom resolver\n // bypasses AgentControllerChannels' own `channel:{thread.id}` derivation\n // (agent-controller-channels.ts `resolveChannelResourceId`), and the base\n // default is the per-USER memory key. Chat-only fallbacks must stay\n // per-thread, so reproduce the controller default here.\n const chatOnlyResourceId = `channel:${thread.id}`;\n // A deployment with no account linking, no projects, or no source-control\n // integration cannot produce a repo-backed thread at all, so a `channel:`\n // thread is the only shape it has. Every refusal below is a different thing\n // entirely: a sender the bot cannot place, in a deployment that can.\n if (!accountLinks || !projects || sourceControls.length === 0) return chatOnlyResourceId;\n // No sender id at all — a malformed message, not a sender the host turned\n // away. There is nothing to place, so give it the per-thread memory key.\n if (!message.author.userId) return chatOnlyResourceId;\n\n const externalTeamId = rawTeamId(message.raw);\n if (!externalTeamId) {\n throw new ChannelSessionRejectedError('No Slack workspace id on the message — the sender cannot be identified');\n }\n try {\n const link = await accountLinks.getAccountLink({\n platform,\n externalTeamId,\n externalUserId: message.author.userId,\n });\n if (!link) {\n throw new ChannelSessionRejectedError(\n `Slack sender ${message.author.userId} is not linked to a Factory account`,\n );\n }\n\n // Same chain as `resolveFactoryForLink`, minus prompts/stamping: the\n // dispatch gate has already run (and stamped a lone factory) by the\n // time a new thread is created, so this is a read-only re-resolve.\n const orgId = link.orgId ?? '';\n let factoryProjectId: string | undefined;\n if (link.defaultFactoryProjectId && (await projects.get({ orgId, id: link.defaultFactoryProjectId }))) {\n factoryProjectId = link.defaultFactoryProjectId;\n } else if (orgId) {\n const factories = await projects.list({ orgId });\n if (factories.length === 1) factoryProjectId = factories[0]!.id;\n }\n if (!factoryProjectId) {\n throw new ChannelSessionRejectedError(`Linked Slack sender ${message.author.userId} has no Factory project`);\n }\n\n // Linked and routed, so a repo-backed session is not a preference: with no\n // repository to work in there is no thread to start, and starting one\n // anyway would answer in a project the sender never picked, on the SDK's\n // built-in defaults.\n const sourceControl = await resolveFactorySourceControl({ sourceControls, orgId, factoryProjectId });\n if (!sourceControl) {\n const connections = await Promise.all(\n sourceControls.map(sourceControl => sourceControl.connections.list({ orgId, factoryProjectId })),\n );\n throw new SlackSessionStartError(\n connections.some(rows => rows.length > 0)\n ? 'Could not start a session: link a repository to this Factory project.'\n : 'Could not start a session: connect source control to this Factory project.',\n );\n }\n // The session's link is the factory's position-1 environment\n // repository (D1); the sandbox boots every environment repository.\n const repo = await resolvePrimaryEnvironmentRepository({ sourceControl, orgId, factoryProjectId });\n if (!repo.found) {\n throw new SlackSessionStartError(\n repo.reason === 'connection'\n ? 'Could not start a session: connect source control to this Factory project.'\n : 'Could not start a session: link a repository to this Factory project.',\n );\n }\n\n const branch = threadBranch(thread.id);\n // Attributed to the Slack sender, not to whoever connected the repository:\n // unlike an autonomous rule run, a Slack thread has a real interactive user.\n // Keyed by factory, not by link: the thread's session stays found when the\n // position-1 repository changes between two messages.\n const existing = await sourceControl.sessions.getForBranch({\n factoryProjectId,\n userId: link.userId,\n branch,\n });\n if (existing) return existing.sessionId;\n const session = await sourceControl.sessions.create({\n sessionId: globalThis.crypto.randomUUID(),\n projectRepositoryId: repo.projectRepositoryId,\n factoryProjectId,\n orgId,\n userId: link.userId,\n branch,\n baseBranch: repo.baseBranch,\n // DMs are the only private origin; channel threads are org-visible.\n visibility: thread.isDM ? 'private' : 'org',\n });\n return session.sessionId;\n } catch (error) {\n if (error instanceof ChannelSessionRejectedError || error instanceof SlackSessionStartError) throw error;\n if (error instanceof FactorySourceControlConflictError) {\n throw new SlackSessionStartError(\n 'Could not start a session: this Factory project has repositories from more than one source-control provider.',\n { cause: error },\n );\n }\n console.error('[slack] failed to start repo-backed session for thread', thread.id, error);\n throw new SlackSessionStartError('Could not start a session right now. Please try again later.', {\n cause: error,\n });\n }\n };\n}\n\n/**\n * Thread id for a NEW Slack channel thread. Repo-backed threads take the\n * user-session id AS their thread id, matching the web convention\n * (FactoryStartCoordinator seeds threads with threadId = sessionId) so\n * `/workspaces/{sessionId}/threads/{sessionId}` resolves Slack-created\n * sessions exactly like web-created ones — no `?resourceId=` override needed.\n * Chat-only threads keep the default random id: their `channel:...`\n * resourceId is a memory key, not a unique thread id.\n */\nexport const resolveChannelThreadId: ResolveThreadId = ({ resourceId, defaultThreadId }) =>\n resourceId.startsWith('channel:') ? defaultThreadId : resourceId;\n\n/** Create channel sessions with their Factory ownership present in initial controller state. */\nexport function createChannelSessionResolver(deps: SlackChannelDeps): ChannelSessionResolve {\n const sourceControlSessions = createSourceControlSessionLookup(configuredSourceControls(deps));\n return async ({ controller, thread, requestContext }) => {\n const sourceControl = thread.resourceId.startsWith('channel:')\n ? null\n : await sourceControlSessions.getSourceControlBySessionId(thread.resourceId);\n const session = sourceControl ? await sourceControl.sessions.getBySessionId(thread.resourceId) : null;\n return controller.createSession({\n id: thread.resourceId,\n ownerId: controller.id,\n resourceId: thread.resourceId,\n requestContext,\n ...(session?.factoryProjectId ? { tags: { factoryProjectId: session.factoryProjectId } } : {}),\n });\n };\n}\n\n/**\n * Apply the factory's configuration to a Slack-created session the first time\n * its thread reaches the controller.\n *\n * Without this a Slack session runs on the SDK's built-in mode default\n * (`openai/gpt-5.5`), so a factory configured for any other provider fails every\n * message with a missing-credentials error. The web kickoff has always applied\n * the factory default; this brings Slack to the same footing.\n *\n * Only repo-backed threads are configured. Their resourceId IS the Factory\n * session id, which the source-control rows turn back into a project — a\n * chat-only `channel:...` id names no project, so there is nothing to read.\n *\n * The model this session starts on is picked here, once, from the project's\n * effective model scope for a session: the Factory default or the durable session owner's\n * default, else the SDK's built-in model. The choice is persisted on the thread\n * as `currentModelId`, so it outlives the process that made it.\n *\n * The model resolution is skipped on a session that already has a model\n * persisted on the thread. That is the durable record of a deliberate choice —\n * either an earlier start or a user's own switch — and re-applying a preference\n * over it would undo the user's selection every time the process restarts.\n */\nexport function createChannelSessionStartHook(deps: SlackChannelDeps): ChannelSessionStart {\n const { projects, modelDefaults } = deps;\n const sourceControlSessions = createSourceControlSessionLookup(configuredSourceControls(deps));\n return async ({ session, thread, requestContext }) => {\n // Seed the tenant org above every guard below. `gateDispatch` stamps it on\n // the message's request context before the session exists, so this needs no\n // storage read — which matters, because the model lookups below deliberately\n // skip storage on a restarted session. A channel-only thread and a thread\n // whose dispatch was ungated both land here with no org and are marked\n // unresolved rather than being left to look like a local session.\n await seedSessionOrg(session, readRequestContextOrgId(requestContext));\n\n if (!projects) return;\n if (thread.resourceId.startsWith('channel:')) return;\n\n const sourceControl = await sourceControlSessions.getSourceControlBySessionId(thread.resourceId);\n if (!sourceControl) return;\n const sourceSession = await sourceControl.sessions.getBySessionId(thread.resourceId);\n if (!sourceSession?.factoryProjectId) return;\n const threadId = session.thread.getId();\n const binding =\n threadId && deps.workItems\n ? await deps.workItems.findRunBindingByThread({\n orgId: sourceSession.orgId,\n resourceId: thread.resourceId,\n sessionId: thread.resourceId,\n threadId,\n rejectAmbiguous: true,\n })\n : null;\n const owner = {\n factoryProjectId: binding?.factoryProjectId ?? sourceSession.factoryProjectId,\n orgId: sourceSession.orgId,\n userId: sourceSession.userId,\n };\n\n const sessionModel = await resolveFactorySessionModel({\n projects,\n modelDefaults,\n factoryProjectId: owner.factoryProjectId,\n orgId: owner.orgId,\n ownerUserId: owner.userId,\n context: binding ? 'run' : 'session',\n });\n\n // Repo-backed Slack sessions use the source-control row's durable owner,\n // never the sender, for both model and credential scope.\n await session.state.set({\n factoryProjectId: owner.factoryProjectId,\n factoryModelScope: sessionModel.modelScope,\n factoryOwnerUserId: owner.userId,\n });\n // Route the org through the seed helper rather than stamping it directly:\n // an ungated dispatch marked this session unresolved above, and owner\n // recovery is the resolution that has to clear that marker with it.\n await seedSessionOrg(session, owner.orgId);\n await primeTenantCredentialsForTenant({\n orgId: owner.orgId,\n userId: owner.userId,\n credentialScope: sessionModel.modelScope,\n });\n\n const modeId = session.mode.get();\n const activeThreadId = session.thread.getId();\n const persistedModelId = await migratePersistedModelSelection({\n getMetadata: async () =>\n activeThreadId\n ? (((await session.thread.getById({ threadId: activeThreadId }))?.metadata as\n Record<string, unknown> | undefined) ?? {})\n : {},\n modeId,\n set: (key: string, value: unknown) =>\n activeThreadId ? session.thread.setSettingOn({ threadId: activeThreadId, key, value }) : Promise.resolve(),\n threadId: activeThreadId ?? undefined,\n });\n if (persistedModelId) {\n // Subagent models live in session state only, so restore the ones this\n // thread pinned at its first start instead of re-resolving them.\n for (const agentType of SUBAGENT_TYPES) {\n const modelId = await session.thread.getSetting({ key: pinnedSubagentModelKey(agentType) });\n if (typeof modelId === 'string') await applySubagentModel(session, agentType, modelId);\n }\n } else {\n await hydrateFactorySession(session, {\n orgId: owner.orgId,\n modelScope: sessionModel.modelScope,\n ownerUserId: owner.userId,\n defaultModelId: sessionModel.modelId,\n });\n\n // `hydrateFactorySession` has already persisted either the scoped model\n // or the SDK mode default as this thread's model of record.\n const selectedModelId = sessionModel.modelId ?? session.mode.resolve().defaultModelId ?? session.model.get();\n\n // Pin subagent models on the thread like the main model so a restart\n // restores the selected scope's choice rather than re-resolving it.\n for (const agentType of SUBAGENT_TYPES) {\n if (!selectedModelId) continue;\n if (await applySubagentModel(session, agentType, selectedModelId)) {\n await session.thread.setSetting({ key: pinnedSubagentModelKey(agentType), value: selectedModelId });\n }\n }\n }\n };\n}\n\nconst SUBAGENT_TYPES = ['explore', 'plan', 'execute'] as const;\nconst pinnedSubagentModelKey = (agentType: string) => `slackSubagentModelId_${agentType}`;\n\n/** Best-effort: a subagent model that can't be applied must not fail the message. */\nasync function applySubagentModel(\n session: Parameters<ChannelSessionStart>[0]['session'],\n agentType: (typeof SUBAGENT_TYPES)[number],\n modelId: string,\n): Promise<boolean> {\n try {\n await session.subagents.model.set({ modelId, agentType });\n return true;\n } catch (error) {\n console.warn('[slack] Failed to apply the subagent model', {\n agentType,\n modelId,\n error: error instanceof Error ? error.message : String(error),\n });\n return false;\n }\n}\n\n/**\n * The internal Mastra thread the framework created for a channel conversation.\n * The handler's `thread.id` is the platform thread id (e.g. `slack:C123:ts`),\n * NOT the internal UUID — the mapping lives in the stored thread's channel\n * metadata.\n */\nasync function findInternalThread(mastra: Mastra | undefined, thread: HandlerThread) {\n const store = await mastra?.getStorage()?.getStore('memory');\n const { threads } = (await store?.listThreads({\n filter: {\n metadata: {\n channel_platform: thread.adapter.name,\n channel_externalThreadId: thread.id,\n channel_externalChannelId: thread.channelId,\n },\n },\n perPage: 1,\n })) ?? { threads: [] };\n return threads[0];\n}\n\nasync function prepareExistingSessionOwnerContext(\n thread: HandlerThread,\n deps: SlackChannelDeps,\n ctx: ChannelHandlerContext,\n options: { expectedOrgId?: string; requireInternalThread: boolean },\n): Promise<'ready' | 'organization-mismatch'> {\n const sourceControls = configuredSourceControls(deps);\n if (sourceControls.length === 0 && !options.requireInternalThread) return 'ready';\n\n const internalThread = await findInternalThread(ctx.mastra, thread);\n if (!internalThread) {\n if (options.requireInternalThread) {\n throw new Error(`Could not resolve the internal Slack thread for ${thread.id}.`);\n }\n return 'ready';\n }\n if (sourceControls.length === 0 || internalThread.resourceId.startsWith('channel:')) return 'ready';\n if (!options.expectedOrgId) {\n throw new Error(`Could not authorize the owner of Slack Factory session ${internalThread.resourceId}.`);\n }\n\n const preparation = await prepareSessionRunContext(\n ctx.requestContext,\n internalThread.resourceId,\n { sessions: createSourceControlSessionLookup(sourceControls) },\n { expectedOrgId: options.expectedOrgId },\n );\n if (preparation === 'organization-mismatch') return preparation;\n if (preparation === 'unavailable') {\n throw new Error(`Could not authorize the owner of Slack Factory session ${internalThread.resourceId}.`);\n }\n return 'ready';\n}\n\n/**\n * Build the \"new session\" handler for mention / direct-message events. A mention or\n * DM on a not-yet-subscribed thread starts a NEW session; once subscribed, later\n * events are follow-ups and don't re-announce.\n */\n/**\n * Run the account-link + factory-routing gates for one inbound message.\n * Returns `null` when the run must not dispatch (a prompt card was posted\n * where possible); otherwise the dispatch context — with `routed` present\n * only when a linked sender resolved to a factory.\n */\nasync function gateDispatch(\n thread: HandlerThread,\n message: HandlerMessage,\n deps: SlackChannelDeps,\n ctx: ChannelHandlerContext,\n options: { requireInternalThread: boolean } = { requireInternalThread: false },\n): Promise<{\n routed?: { link: ChannelAccountLink; factoryProjectId: string };\n} | null> {\n const { accountLinks, projects } = deps;\n const sender = await resolveLinkedSender({ thread, message, accountLinks });\n if (sender.status === 'blocked') return null;\n // Linked senders must also route to a Factory project before a run starts.\n if (sender.status === 'linked' && accountLinks) {\n // Stamp the tenant on the run's request context — the single seam\n // `resolveCredentialStore` reads to load this sender's model credentials.\n // This belongs to the link, not to the routing: a linked sender whose\n // factory routing comes back `ungated` still exits below and dispatches, so\n // stamping only in the routed branch would silently run them on default\n // credentials.\n ctx.requestContext.set('user', { id: sender.link.userId, organizationId: sender.link.orgId });\n const ownerContext = await prepareExistingSessionOwnerContext(thread, deps, ctx, {\n expectedOrgId: sender.link.orgId,\n requireInternalThread: options.requireInternalThread,\n });\n if (ownerContext === 'organization-mismatch') {\n await thread.post('This thread belongs to a Factory session in another organization.');\n return null;\n }\n // Credential resolution reads an in-memory snapshot that only priming\n // fills, so prime whichever tenant now owns the run (the sender, or the\n // existing session's owner) — otherwise a cold snapshot after a restart\n // fails the run until that user happens to hit the web UI.\n await primeTenantCredentialsForRequestContext(ctx.requestContext);\n\n const route = await resolveFactoryForLink({ thread, ...sender, accountLinks, projects });\n if (route.status === 'blocked') return null;\n if (route.status === 'resolved') {\n return { routed: { link: sender.link, factoryProjectId: route.factoryProjectId } };\n }\n }\n return {};\n}\n\n/**\n * A channel id and a `ts` name a thread only inside the workspace that issued\n * them, so the team scopes the card's key — without it two workspaces could\n * hold one key and an aside would land on another tenant's card.\n */\nexport function slackThreadSource(thread: HandlerThread, teamId?: string): ExternalWorkItemSource {\n return {\n integrationId: thread.adapter.name,\n type: 'slack-thread',\n ...(teamId ? { workspaceId: teamId } : {}),\n externalId: thread.id,\n };\n}\n\n/** Cards written before the key carried a team are still keyed bare. */\nasync function findThreadWorkItem(\n workItems: WorkItemsStorage,\n thread: HandlerThread,\n teamId?: string,\n): Promise<WorkItemRow | null> {\n const scoped = await workItems.getBySource(slackThreadSource(thread, teamId));\n if (scoped || !teamId) return scoped;\n return workItems.getBySource(slackThreadSource(thread));\n}\n\nfunction preDispatchErrorDetails(error: unknown): { message: string; cause?: { message: string } } {\n const readMessage = (value: unknown) => {\n const message =\n typeof value === 'string'\n ? value\n : value && typeof value === 'object' && 'message' in value\n ? value.message\n : undefined;\n return typeof message === 'string' && message.length > 0 ? message : undefined;\n };\n try {\n const message = readMessage(error) ?? 'Unknown error';\n const cause = error && typeof error === 'object' && 'cause' in error ? readMessage(error.cause) : undefined;\n return { message, ...(cause ? { cause: { message: cause } } : {}) };\n } catch {\n return { message: 'Error details unavailable' };\n }\n}\n\nasync function reportPreDispatchError(\n thread: HandlerThread,\n message: HandlerMessage,\n ctx: ChannelHandlerContext,\n error: unknown,\n): Promise<void> {\n try {\n if (error instanceof ChannelSessionRejectedError) return;\n } catch {\n // A thrown proxy can fail even the refusal check; still report the failure.\n }\n const correlation = {\n platform: thread.adapter.name,\n threadId: thread.id,\n messageId: message.id,\n authorId: message.author.userId,\n };\n const logger = typeof ctx.mastra?.getLogger === 'function' ? ctx.mastra.getLogger() : undefined;\n const logError = (line: string) => (logger ? logger.error(line) : console.error(line));\n logError(`[slack] Pre-dispatch failure ${JSON.stringify({ ...correlation, error: preDispatchErrorDetails(error) })}`);\n try {\n // The message id is the lookup key for the diagnostic above.\n await thread.post(`Couldn’t start processing your message. Please try again.\\n\\`messageId: ${message.id}\\``);\n } catch (deliveryError) {\n logError(\n `[slack] Failed to deliver pre-dispatch error reply ${JSON.stringify({ ...correlation, error: preDispatchErrorDetails(deliveryError) })}`,\n );\n }\n}\n\nfunction createNewSessionChatHandler(deps: SlackChannelDeps): ChannelHandler {\n return async (thread, message, defaultHandler, ctx) => {\n // Gate on the sender having linked their Slack account to a Mastra tenant.\n // Unlinked → post the ephemeral Connect card and stop; no session/run is\n // created (which would otherwise be tenant-less and fail credential\n // resolution). This handler is the only gate — core dispatches whatever\n // reaches it — so every slot that can start a run must call it.\n let gate: Awaited<ReturnType<typeof gateDispatch>>;\n let isNewSession: boolean;\n try {\n gate = await gateDispatch(thread, message, deps, ctx);\n if (!gate) return;\n\n // A mention on a not-yet-subscribed thread is a NEW session. The\n // default handler auto-subscribes, so once subscribed this is a\n // follow-up mention — don't re-announce.\n isNewSession = !(await thread.isSubscribed());\n } catch (error) {\n await reportPreDispatchError(thread, message, ctx, error);\n return;\n }\n\n // Run the framework handler first so the internal Mastra thread and\n // controller session are created before we build the deep link.\n await defaultHandler(thread, message);\n\n if (!isNewSession) return;\n\n // The internal thread names the session the announcement card links to.\n const internalThread = await findInternalThread(ctx.mastra, thread);\n if (!internalThread) {\n console.warn('[onMention] no internal thread found for', thread.id);\n return;\n }\n\n // When the sender routed to a factory we know exactly which workspace the\n // session belongs to — deep-link straight into it. A repo-backed thread's\n // resourceId IS the Factory user-session id, so the link lands on the same\n // route a web-started run navigates to; chat-only threads keep the literal\n // `channel` segment (the real resource rides the `?resourceId=` override).\n // Unrouted senders fall back to the factory-agnostic /threads/ redirect.\n // One predicate drives both the path segment and the query param, so the\n // two can never disagree about what the URL already carries.\n const isChatOnly = internalThread.resourceId.startsWith('channel:');\n const workspaceSegment = isChatOnly ? 'channel' : encodeURIComponent(internalThread.resourceId);\n const threadPath = gate.routed\n ? `/factories/${encodeURIComponent(gate.routed.factoryProjectId)}/workspaces/${workspaceSegment}/threads/${encodeURIComponent(internalThread.id)}`\n : `/threads/${internalThread.id}`;\n\n // The param is an override for a URL that can't otherwise name its\n // resource. A routed repo-backed thread already spells the resourceId out\n // as its workspace segment, so appending it again is duplication the app\n // ignores. Chat-only threads need it (their segment is the literal string\n // `channel`), and so does the unrouted fallback, which has no workspace\n // segment at all — `ChannelThreadRedirect` forwards the search through.\n // Undefined without a public origin, and the card is then skipped.\n const needsResourceParam = isChatOnly || !gate.routed;\n const deepLink = process.env.MASTRACODE_PUBLIC_URL\n ? needsResourceParam\n ? `${process.env.MASTRACODE_PUBLIC_URL}${threadPath}?resourceId=${encodeURIComponent(internalThread.resourceId)}`\n : `${process.env.MASTRACODE_PUBLIC_URL}${threadPath}`\n : undefined;\n\n // The announcement card is only useful with a public origin to deep-link\n // to — otherwise the link would be `undefined/threads/...`. Without one the\n // session still exists; we just skip the broken card.\n if (!deepLink) return;\n\n await thread.post(\n Card({\n title: 'New session started',\n children: [Actions([LinkButton({ url: deepLink, label: 'View session' })])],\n }),\n );\n };\n}\n\n/**\n * An `aside` never reaches the agent, but it is still part of the conversation:\n * record it on the card the thread already has. The link is resolved here rather\n * than through `gateDispatch`, which posts a Connect card an aside must not\n * trigger. Best-effort — nothing here may surface in the Slack thread.\n */\nasync function ingestAside(\n thread: HandlerThread,\n message: HandlerMessage,\n { feed, workItems, accountLinks }: SlackChannelDeps,\n): Promise<void> {\n if (!feed || !workItems) return;\n const body = resolveEmojiShortcodes(message.text.replace(/^aside\\b[,:]?\\s*/i, '').trim());\n if (!body) return;\n try {\n const teamId = slackTeamId(message);\n const workItem = await findThreadWorkItem(workItems, thread, teamId);\n if (!workItem) return;\n\n const external: FactoryActorExternalIdentity = {\n platform: thread.adapter.name,\n ...(teamId ? { teamId } : {}),\n messageId: message.id,\n userId: message.author.userId,\n userName: message.author.userName,\n fullName: message.author.fullName,\n isBot: message.author.isBot,\n };\n const link =\n accountLinks && teamId\n ? await accountLinks.getAccountLink({\n platform: external.platform,\n externalTeamId: teamId,\n externalUserId: external.userId,\n })\n : null;\n\n const result = await feed.createComment({\n orgId: workItem.orgId,\n workItemId: workItem.id,\n author: actorFromChannelAuthor(external, link),\n body,\n occurredAt: message.metadata.dateSent,\n externalSource: slackCommentSource(thread.id, message.id, teamId),\n });\n if (result.status !== 'created') {\n console.warn('[slack] aside not ingested', { thread: thread.id, messageTs: message.id, status: result.status });\n }\n } catch (error) {\n // Slack was acked before this handler ran, so a lost aside is never redelivered.\n console.warn('[slack] aside ingest failed', { thread: thread.id, messageTs: message.id, error });\n }\n}\n\nexport const createHandlers = (deps: SlackChannelDeps): ChannelHandlers => {\n const newSessionChatHandler = createNewSessionChatHandler(deps);\n\n return {\n onSubscribedMessage: async (thread, message, defaultHandler, ctx) => {\n // `aside` as its own leading word lets humans talk in a subscribed\n // thread without the bot replying. Word boundary so messages that\n // merely start with \"aside...\" (e.g. \"asides can wait\") still route.\n if (/^aside\\b/i.test(message.text)) return ingestAside(thread, message, deps);\n // A subscribed follow-up from an unlinked sender must not run either\n // (e.g. the link was removed mid-conversation), and it must still\n // resolve a factory (e.g. the default was cleared or its factory\n // deleted mid-conversation).\n try {\n const gate = await gateDispatch(thread, message, deps, ctx, { requireInternalThread: true });\n if (!gate) return;\n } catch (error) {\n await reportPreDispatchError(thread, message, ctx, error);\n return;\n }\n await defaultHandler(thread, message);\n },\n onMention: newSessionChatHandler,\n onDirectMessage: newSessionChatHandler,\n };\n};\n\n/** Slack app credentials, passed in explicitly rather than read from env here. */\ninterface SlackCredentials {\n clientId?: string;\n clientSecret?: string;\n signingSecret: string;\n botToken?: string;\n}\n\nexport function createSlackChannelsConfig(deps: SlackChannelDeps & { slack: SlackCredentials }): FactoryChannelsConfig {\n // Per HTTP attempt, not per call: the WebClient's own retry policy still\n // waits out a 429. Without it a dead socket hangs a post forever.\n const adapter = createSlackAdapter({ ...deps.slack, webClientOptions: { timeout: SLACK_REQUEST_TIMEOUT_MS } });\n const slack =\n deps.adapterOptions?.streaming === false\n ? { adapter, ...deps.adapterOptions }\n : { adapter, ...deps.adapterOptions, streaming: deps.adapterOptions?.streaming ?? true };\n\n return {\n adapters: { slack },\n handlers: createHandlers(deps),\n // New linked+repo-backed threads own a Factory user-session id as their\n // resourceId, which is what makes the controller session repo-backed.\n resolveResourceId: createChannelResourceIdResolver(deps),\n resolveThreadId: resolveChannelThreadId,\n resolveSession: createChannelSessionResolver(deps),\n // Those sessions are created by the channel machinery, not the web kickoff,\n // so this is where they pick up the factory's configuration.\n onSessionStart: createChannelSessionStartHook(deps),\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"slack.js","names":[],"sources":["../../../src/integrations/slack/slack.ts"],"sourcesContent":["import { migratePersistedModelSelection } from '@mastra/core/agent-controller';\nimport { ChannelSessionRejectedError } from '@mastra/core/channels';\nimport type {\n ChannelHandler,\n ChannelHandlerContext,\n ChannelHandlers,\n ChannelSessionResolve,\n ChannelSessionStart,\n ResolveResourceId,\n ResolveThreadId,\n} from '@mastra/core/channels';\nimport type { Mastra } from '@mastra/core/mastra';\nimport { createSlackAdapter } from '@mastra/slack';\nimport type { SlackAdapterChannelConfig } from '@mastra/slack';\nimport { Card, CardText, Actions, LinkButton } from 'chat';\n\nimport {\n primeTenantCredentialsForRequestContext,\n primeTenantCredentialsForTenant,\n} from '../../routes/tenant-credentials.js';\nimport {\n createSourceControlSessionLookup,\n FactorySourceControlConflictError,\n hydrateFactorySession,\n resolveFactorySessionModel,\n resolveFactorySourceControl,\n resolvePrimaryEnvironmentRepository,\n} from '../../session/factory-session.js';\nimport { readRequestContextOrgId, seedSessionOrg } from '../../session/org-seed.js';\nimport type {\n ChannelAccountLink,\n ChannelAccountLinkKey,\n ChannelIdentityStorage,\n} from '../../storage/domains/channel-identity/base.js';\nimport type { FactoryActorExternalIdentity } from '../../storage/domains/comments/actor.js';\nimport { actorFromChannelAuthor } from '../../storage/domains/comments/actor.js';\nimport type { CommentsDomain } from '../../storage/domains/comments/domain.js';\nimport type { ModelDefaultsStorage } from '../../storage/domains/model-defaults/base.js';\nimport type { FactoryProjectsStorage } from '../../storage/domains/projects/base.js';\nimport type { SourceControlStorageHandle } from '../../storage/domains/source-control/base.js';\nimport type { ExternalWorkItemSource, WorkItemRow, WorkItemsStorage } from '../../storage/domains/work-items/base.js';\nimport type { FactoryChannelsConfig } from '../base.js';\nimport { prepareSessionRunContext } from '../subscription-session.js';\n\nimport { resolveEmojiShortcodes } from './emoji.js';\nimport { slackCommentSource } from './feed-publisher.js';\n\n// Derive the thread/message types from the core handler signature rather than\n// importing them from `chat` directly: mc-web can resolve a different `chat`\n// version than @mastra/core, and the two `Thread`/`Message` declarations are\n// structurally incompatible (private fields). Using the handler's own types\n// keeps everything on one version.\ntype HandlerThread = Parameters<ChannelHandler>[0];\ntype HandlerMessage = Parameters<ChannelHandler>[1];\n\nconst SLACK_REQUEST_TIMEOUT_MS = 15_000;\n\nclass SlackSessionStartError extends Error {}\n\n/** Dependencies the Slack channel handlers close over, injected from the web entry. */\ninterface SlackChannelDeps {\n /**\n * The factory's reverse-index store mapping a Slack sender to a Mastra\n * tenant. When provided, inbound messages from an unlinked sender are not\n * dispatched — the run only proceeds (with the sender's tenant stamped on\n * the request context) once they've linked their account. Unlinked senders\n * get an ephemeral \"connect your account\" card instead.\n */\n accountLinks?: ChannelIdentityStorage;\n /**\n * Factory projects domain. When provided (alongside `accountLinks`), a\n * linked sender's run must also resolve to a Factory project before it\n * dispatches: their link's default factory, else their tenant's only\n * factory (stamped back onto the link), else a \"pick a default factory\"\n * card posted in the thread and no run. Unset → no factory routing (runs\n * dispatch as before).\n */\n projects?: FactoryProjectsStorage;\n /**\n * Storage handle of the integration that owns source control\n * (`IntegrationContext.storage.sourceControlOwner`). Used to make new Slack\n * threads repo-backed: when the sender is linked and their factory has a\n * repository, the thread's resourceId becomes a Factory user-session id (repo\n * cloned on a `slack/{threadTs}` branch) instead of the chat-only\n * `channel:...` id. It also lets a started session read back the project it\n * belongs to. Nothing here is provider-specific — the connection is matched\n * by the handle's own `integrationId`. Absent (no source-control integration\n * registered) → chat-only sessions as before.\n */\n sourceControl?: SourceControlStorageHandle;\n /** Registered source-control partitions used to select the provider linked to each Factory project. */\n sourceControls?: readonly SourceControlStorageHandle[];\n /**\n * Model-defaults domain. When provided, a new repo-backed session starts on\n * the linked sender's default model before the factory's shared default. Read\n * once, when the thread's build model is first chosen; the choice is persisted\n * on the thread and never re-resolved. Unset → the factory default, as before.\n */\n modelDefaults?: ModelDefaultsStorage;\n /**\n * Factory work-items domain, used to find the card a Slack thread already\n * has so an `aside` can land on it. New threads never create cards.\n */\n workItems?: WorkItemsStorage;\n /**\n * Work-item feed. With it (and `workItems`), an `aside` lands as a comment on\n * the card the thread already has. Unset → asides stay ignored, as before.\n */\n feed?: Pick<CommentsDomain, 'createComment'>;\n /** Overrides applied to the Slack channel adapter entry. */\n adapterOptions?: SlackAdapterChannelConfig;\n}\n\nfunction configuredSourceControls(deps: SlackChannelDeps): readonly SourceControlStorageHandle[] {\n return deps.sourceControls ?? (deps.sourceControl ? [deps.sourceControl] : []);\n}\n\n/**\n * Read the Slack team id off a raw platform payload (Events API envelope or\n * slash-command body — both carry `team_id`), duck-typed to build the\n * workspace-scoped account-link key.\n */\nfunction rawTeamId(rawPayload: unknown): string | undefined {\n if (!rawPayload || typeof rawPayload !== 'object') return undefined;\n const raw = rawPayload as { team_id?: unknown; team?: unknown };\n if (typeof raw.team_id === 'string' && raw.team_id) return raw.team_id;\n if (typeof raw.team === 'string' && raw.team) return raw.team;\n if (raw.team && typeof raw.team === 'object') {\n const id = (raw.team as { id?: unknown }).id;\n if (typeof id === 'string' && id) return id;\n }\n return undefined;\n}\n\n/**\n * The Slack team id survives onto a normalized chat Message only on\n * `message.raw` (the Slack Events API envelope).\n */\nfunction slackTeamId(message: HandlerMessage): string | undefined {\n return rawTeamId(message.raw);\n}\n\n/**\n * Resolve the web-UI origin for links humans open in a browser (Connect card,\n * session deep links). Prefers `MASTRACODE_PUBLIC_URL` — the origin auth\n * cookies and OAuth redirect allow-lists are registered against — over the\n * channels tunnel, which only Slack's servers need to reach.\n */\nfunction webPublicUrl(): string | undefined {\n return process.env.MASTRACODE_PUBLIC_URL ?? process.env.MASTRACODE_CHANNELS_PUBLIC_URL;\n}\n\n/** Outcome of the sender-link gate for one inbound message. */\ntype LinkedSenderResult =\n /** Gating not configured — dispatch as before account linking existed. */\n | { status: 'ungated' }\n /** Sender unlinked — Connect card posted (when possible), do not dispatch. */\n | { status: 'blocked' }\n /** Sender linked — their tenant plus the sender key the link lives under. */\n | { status: 'linked'; link: ChannelAccountLink; key: ChannelAccountLinkKey };\n\n/**\n * Resolve the sender's account link, posting an ephemeral \"connect your\n * account\" card (visible only to the sender) linking into the web UI's\n * Slack-connect flow when they're unlinked.\n */\nexport async function resolveLinkedSender({\n thread,\n message,\n accountLinks,\n}: {\n thread: HandlerThread;\n message: HandlerMessage;\n accountLinks?: ChannelIdentityStorage;\n}): Promise<LinkedSenderResult> {\n if (!accountLinks) return { status: 'ungated' };\n const platform = thread.adapter.name;\n const externalUserId = message.author.userId;\n const externalTeamId = slackTeamId(message);\n // Without a team id we can't identify the workspace-scoped link; treat as\n // unlinked so a run never proceeds tenant-less.\n const key = externalTeamId ? { platform, externalTeamId, externalUserId } : undefined;\n const link = key ? await accountLinks.getAccountLink(key) : null;\n if (link && key) return { status: 'linked', link, key };\n\n const publicUrl = webPublicUrl();\n // A public origin is all the card needs. The link carries no identity: the\n // web app authenticates the visitor, then Slack's OIDC flow proves which\n // Slack account they control. Without an origin, still block, just no card.\n if (publicUrl) {\n await thread.postEphemeral(message.author, buildConnectCard(publicUrl, thread), { fallbackToDM: true });\n }\n return { status: 'blocked' };\n}\n\nconst retryHint = (thread: HandlerThread) => (thread.isDM ? 'message me again' : 'mention me again');\n\n/**\n * The \"connect your account\" card. The link is deliberately identity-free —\n * `/connect/slack` sends the visitor to Connections, where \"Connect Slack\"\n * runs the OIDC flow and Slack itself asserts the (team, user) pair.\n */\nfunction buildConnectCard(publicUrl: string, thread: HandlerThread) {\n return Card({\n title: 'Connect your account',\n children: [\n CardText(`Connect your account to use this agent, then ${retryHint(thread)}.`),\n Actions([\n LinkButton({\n url: `${publicUrl}/connect/slack`,\n label: 'Connect account',\n }),\n ]),\n ],\n });\n}\n\n/** Outcome of factory routing for one linked sender's inbound message. */\ntype FactoryRouteResult =\n /** Factory routing not configured — dispatch without a factory. */\n | { status: 'ungated' }\n /** No factory resolved — prompt card posted (when possible), do not dispatch. */\n | { status: 'blocked' }\n /** The Factory project this sender's runs route to. */\n | { status: 'resolved'; factoryProjectId: string };\n\n/**\n * Decide which Factory project a linked sender's run belongs to:\n *\n * 1. The link's `defaultFactoryProjectId`, when it still exists (a stale id —\n * deleted factory — falls through as if unset).\n * 2. Else, the tenant's only factory, stamped back onto the link so it shows\n * up (and stays editable) in Connected Accounts settings.\n * 3. Else — zero or several factories — a \"pick a default factory\" card\n * posted publicly in the thread so Slack notifies the sender, and the run is\n * blocked.\n */\nexport async function resolveFactoryForLink({\n thread,\n link,\n key,\n accountLinks,\n projects,\n}: {\n thread: HandlerThread;\n link: ChannelAccountLink;\n key: ChannelAccountLinkKey;\n accountLinks: ChannelIdentityStorage;\n projects?: FactoryProjectsStorage;\n}): Promise<FactoryRouteResult> {\n if (!projects) return { status: 'ungated' };\n // Factories are org-scoped; a personal account (no org) has none and lands\n // on the prompt below.\n const orgId = link.orgId ?? '';\n\n if (link.defaultFactoryProjectId) {\n const existing = await projects.get({ orgId, id: link.defaultFactoryProjectId });\n if (existing) {\n return { status: 'resolved', factoryProjectId: existing.id };\n }\n }\n\n const factories = orgId ? await projects.list({ orgId }) : [];\n if (factories.length === 1) {\n const only = factories[0]!;\n await accountLinks.setDefaultFactory({ ...key, userId: link.userId, factoryProjectId: only.id });\n return { status: 'resolved', factoryProjectId: only.id };\n }\n\n const publicUrl = webPublicUrl();\n if (publicUrl) {\n await thread.post(\n Card({\n title: 'Pick a default factory',\n children: [\n CardText(\n factories.length === 0\n ? `Your account has no factory yet. Create one in the web app, then ${retryHint(thread)}.`\n : `Your account has several factories. Pick which one Slack sessions should go to, then ${retryHint(thread)}.`,\n ),\n Actions([\n LinkButton({\n url: `${publicUrl}/settings/connections`,\n label: 'Open settings',\n }),\n ]),\n ],\n }),\n );\n }\n return { status: 'blocked' };\n}\n\n/**\n * Deterministic per-thread branch name: `slack/{threadTs}` with characters\n * outside the sandbox git-ref allow-list (`[A-Za-z0-9_./-]`, and `.` for\n * readability) mapped to `-`. `thread.id` is `{channelId}:{threadTs}`\n * (platform-prefixed on handler threads) — the trailing segment is the ts.\n *\n * Top-level DM and channel conversations use the empty-threadTs thread form,\n * so the trailing segment can be empty; a bare `slack/` is not a valid git\n * ref. Fall back to the last non-empty segment (the channel id): one\n * deterministic branch per top-level conversation.\n */\nfunction threadBranch(threadId: string): string {\n const segments = threadId.split(':');\n const tail = segments.findLast(segment => segment.length > 0) ?? threadId;\n return `slack/${tail.replace(/[^A-Za-z0-9_/-]/g, '-')}`;\n}\n\n/**\n * Resolve the resourceId for a NEW Slack channel thread. A linked sender whose\n * factory has a repository gets a Factory user-session id — the controller\n * session then materializes the repo sandbox via the factory's dynamic\n * workspace (clone + PAT), the session shows up in the web Sessions list, and\n * View Session deep-links land on the normal workspace route.\n *\n * A gated deployment refuses a thread it cannot place, rather than falling back\n * to a chat-only session: a repo-backed thread is the whole point of the\n * integration, and answering in a chat-only one would run the sender's request\n * in no project, on the SDK's built-in defaults. A linked sender whose project\n * cannot start a session gets an actionable error in Slack; the dispatch gate\n * handles unlinked or unrouted senders before this hook runs.\n * The hook only runs when a NEW thread is actually created (`getOrCreateThread`\n * resolves the owner lazily), so an established conversation is never touched:\n * it keeps its session, its model, and its history.\n *\n * The chat-only `channel:...` id survives for two cases where refusing would be\n * wrong: a deployment that cannot produce a repo-backed thread at all (no\n * account linking, no projects, or no source-control integration registered),\n * and a message carrying no sender id. In both, a `channel:` thread is the only\n * shape a session can take.\n *\n * Pure lookups only — cards for unlinked/unrouted senders are the dispatch\n * gate's job; this hook must never post.\n */\nexport function createChannelResourceIdResolver(deps: SlackChannelDeps): ResolveResourceId {\n const { accountLinks, projects } = deps;\n const sourceControls = configuredSourceControls(deps);\n return async ({ platform, thread, message }) => {\n // NOT the hook's `defaultResourceId`: configuring a custom resolver\n // bypasses AgentControllerChannels' own `channel:{thread.id}` derivation\n // (agent-controller-channels.ts `resolveChannelResourceId`), and the base\n // default is the per-USER memory key. Chat-only fallbacks must stay\n // per-thread, so reproduce the controller default here.\n const chatOnlyResourceId = `channel:${thread.id}`;\n // A deployment with no account linking, no projects, or no source-control\n // integration cannot produce a repo-backed thread at all, so a `channel:`\n // thread is the only shape it has. Every refusal below is a different thing\n // entirely: a sender the bot cannot place, in a deployment that can.\n if (!accountLinks || !projects || sourceControls.length === 0) return chatOnlyResourceId;\n // No sender id at all — a malformed message, not a sender the host turned\n // away. There is nothing to place, so give it the per-thread memory key.\n if (!message.author.userId) return chatOnlyResourceId;\n\n const externalTeamId = rawTeamId(message.raw);\n if (!externalTeamId) {\n throw new ChannelSessionRejectedError('No Slack workspace id on the message — the sender cannot be identified');\n }\n try {\n const link = await accountLinks.getAccountLink({\n platform,\n externalTeamId,\n externalUserId: message.author.userId,\n });\n if (!link) {\n throw new ChannelSessionRejectedError(\n `Slack sender ${message.author.userId} is not linked to a Factory account`,\n );\n }\n\n // Same chain as `resolveFactoryForLink`, minus prompts/stamping: the\n // dispatch gate has already run (and stamped a lone factory) by the\n // time a new thread is created, so this is a read-only re-resolve.\n const orgId = link.orgId ?? '';\n let factoryProjectId: string | undefined;\n if (link.defaultFactoryProjectId && (await projects.get({ orgId, id: link.defaultFactoryProjectId }))) {\n factoryProjectId = link.defaultFactoryProjectId;\n } else if (orgId) {\n const factories = await projects.list({ orgId });\n if (factories.length === 1) factoryProjectId = factories[0]!.id;\n }\n if (!factoryProjectId) {\n throw new ChannelSessionRejectedError(`Linked Slack sender ${message.author.userId} has no Factory project`);\n }\n\n // Linked and routed, so a repo-backed session is not a preference: with no\n // repository to work in there is no thread to start, and starting one\n // anyway would answer in a project the sender never picked, on the SDK's\n // built-in defaults.\n const sourceControl = await resolveFactorySourceControl({ sourceControls, orgId, factoryProjectId });\n if (!sourceControl) {\n const connections = await Promise.all(\n sourceControls.map(sourceControl => sourceControl.connections.list({ orgId, factoryProjectId })),\n );\n throw new SlackSessionStartError(\n connections.some(rows => rows.length > 0)\n ? 'Could not start a session: link a repository to this Factory project.'\n : 'Could not start a session: connect source control to this Factory project.',\n );\n }\n // The session's link is the factory's position-1 environment\n // repository (D1); the sandbox boots every environment repository.\n const repo = await resolvePrimaryEnvironmentRepository({ sourceControl, orgId, factoryProjectId });\n if (!repo.found) {\n throw new SlackSessionStartError(\n repo.reason === 'connection'\n ? 'Could not start a session: connect source control to this Factory project.'\n : 'Could not start a session: link a repository to this Factory project.',\n );\n }\n\n const branch = threadBranch(thread.id);\n // Attributed to the Slack sender, not to whoever connected the repository:\n // unlike an autonomous rule run, a Slack thread has a real interactive user.\n // Keyed by factory, not by link: the thread's session stays found when the\n // position-1 repository changes between two messages.\n const existing = await sourceControl.sessions.getForBranch({\n factoryProjectId,\n userId: link.userId,\n branch,\n });\n if (existing) return existing.sessionId;\n const session = await sourceControl.sessions.create({\n sessionId: globalThis.crypto.randomUUID(),\n projectRepositoryId: repo.projectRepositoryId,\n factoryProjectId,\n orgId,\n userId: link.userId,\n branch,\n baseBranch: repo.baseBranch,\n // DMs are the only private origin; channel threads are org-visible.\n visibility: thread.isDM ? 'private' : 'org',\n });\n return session.sessionId;\n } catch (error) {\n if (error instanceof ChannelSessionRejectedError || error instanceof SlackSessionStartError) throw error;\n if (error instanceof FactorySourceControlConflictError) {\n throw new SlackSessionStartError(\n 'Could not start a session: this Factory project has repositories from more than one source-control provider.',\n { cause: error },\n );\n }\n console.error('[slack] failed to start repo-backed session for thread', thread.id, error);\n throw new SlackSessionStartError('Could not start a session right now. Please try again later.', {\n cause: error,\n });\n }\n };\n}\n\n/**\n * Thread id for a NEW Slack channel thread. Repo-backed threads take the\n * user-session id AS their thread id, matching the web convention\n * (FactoryStartCoordinator seeds threads with threadId = sessionId) so\n * `/workspaces/{sessionId}/threads/{sessionId}` resolves Slack-created\n * sessions exactly like web-created ones — no `?resourceId=` override needed.\n * Chat-only threads keep the default random id: their `channel:...`\n * resourceId is a memory key, not a unique thread id.\n */\nexport const resolveChannelThreadId: ResolveThreadId = ({ resourceId, defaultThreadId }) =>\n resourceId.startsWith('channel:') ? defaultThreadId : resourceId;\n\n/** Create channel sessions with their Factory ownership present in initial controller state. */\nexport function createChannelSessionResolver(deps: SlackChannelDeps): ChannelSessionResolve {\n const sourceControlSessions = createSourceControlSessionLookup(configuredSourceControls(deps));\n return async ({ controller, thread, requestContext }) => {\n const sourceControl = thread.resourceId.startsWith('channel:')\n ? null\n : await sourceControlSessions.getSourceControlBySessionId(thread.resourceId);\n const session = sourceControl ? await sourceControl.sessions.getBySessionId(thread.resourceId) : null;\n return controller.createSession({\n id: thread.resourceId,\n ownerId: controller.id,\n resourceId: thread.resourceId,\n // Bind the Slack-created thread itself. Without it the scoped session\n // auto-selects (or creates) another thread and is switched onto this one later.\n threadId: thread.id,\n requestContext,\n ...(session?.factoryProjectId ? { tags: { factoryProjectId: session.factoryProjectId } } : {}),\n });\n };\n}\n\n/**\n * Apply the factory's configuration to a Slack-created session the first time\n * its thread reaches the controller.\n *\n * Without this a Slack session runs on the SDK's built-in mode default\n * (`openai/gpt-5.5`), so a factory configured for any other provider fails every\n * message with a missing-credentials error. The web kickoff has always applied\n * the factory default; this brings Slack to the same footing.\n *\n * Only repo-backed threads are configured. Their resourceId IS the Factory\n * session id, which the source-control rows turn back into a project — a\n * chat-only `channel:...` id names no project, so there is nothing to read.\n *\n * The model this session starts on is picked here, once, from the project's\n * effective model scope for a session: the Factory default or the durable session owner's\n * default, else the SDK's built-in model. The choice is persisted on the thread\n * as `currentModelId`, so it outlives the process that made it.\n *\n * The model resolution is skipped on a session that already has a model\n * persisted on the thread. That is the durable record of a deliberate choice —\n * either an earlier start or a user's own switch — and re-applying a preference\n * over it would undo the user's selection every time the process restarts.\n */\nexport function createChannelSessionStartHook(deps: SlackChannelDeps): ChannelSessionStart {\n const { projects, modelDefaults } = deps;\n const sourceControlSessions = createSourceControlSessionLookup(configuredSourceControls(deps));\n return async ({ session, thread, requestContext }) => {\n // Seed the tenant org above every guard below. `gateDispatch` stamps it on\n // the message's request context before the session exists, so this needs no\n // storage read — which matters, because the model lookups below deliberately\n // skip storage on a restarted session. A channel-only thread and a thread\n // whose dispatch was ungated both land here with no org and are marked\n // unresolved rather than being left to look like a local session.\n await seedSessionOrg(session, readRequestContextOrgId(requestContext));\n\n if (!projects) return;\n if (thread.resourceId.startsWith('channel:')) return;\n\n const sourceControl = await sourceControlSessions.getSourceControlBySessionId(thread.resourceId);\n if (!sourceControl) return;\n const sourceSession = await sourceControl.sessions.getBySessionId(thread.resourceId);\n if (!sourceSession?.factoryProjectId) return;\n const threadId = session.thread.getId();\n const binding =\n threadId && deps.workItems\n ? await deps.workItems.findRunBindingByThread({\n orgId: sourceSession.orgId,\n resourceId: thread.resourceId,\n sessionId: thread.resourceId,\n threadId,\n rejectAmbiguous: true,\n })\n : null;\n const owner = {\n factoryProjectId: binding?.factoryProjectId ?? sourceSession.factoryProjectId,\n orgId: sourceSession.orgId,\n userId: sourceSession.userId,\n };\n\n const sessionModel = await resolveFactorySessionModel({\n projects,\n modelDefaults,\n factoryProjectId: owner.factoryProjectId,\n orgId: owner.orgId,\n ownerUserId: owner.userId,\n context: binding ? 'run' : 'session',\n });\n\n // Repo-backed Slack sessions use the source-control row's durable owner,\n // never the sender, for both model and credential scope.\n await session.state.set({\n factoryProjectId: owner.factoryProjectId,\n factoryModelScope: sessionModel.modelScope,\n factoryOwnerUserId: owner.userId,\n });\n // Route the org through the seed helper rather than stamping it directly:\n // an ungated dispatch marked this session unresolved above, and owner\n // recovery is the resolution that has to clear that marker with it.\n await seedSessionOrg(session, owner.orgId);\n await primeTenantCredentialsForTenant({\n orgId: owner.orgId,\n userId: owner.userId,\n credentialScope: sessionModel.modelScope,\n });\n\n const modeId = session.mode.get();\n const activeThreadId = session.thread.getId();\n const persistedModelId = await migratePersistedModelSelection({\n getMetadata: async () =>\n activeThreadId\n ? (((await session.thread.getById({ threadId: activeThreadId }))?.metadata as\n Record<string, unknown> | undefined) ?? {})\n : {},\n modeId,\n set: (key: string, value: unknown) =>\n activeThreadId ? session.thread.setSettingOn({ threadId: activeThreadId, key, value }) : Promise.resolve(),\n threadId: activeThreadId ?? undefined,\n });\n if (persistedModelId) {\n // Subagent models live in session state only, so restore the ones this\n // thread pinned at its first start instead of re-resolving them.\n for (const agentType of SUBAGENT_TYPES) {\n const modelId = await session.thread.getSetting({ key: pinnedSubagentModelKey(agentType) });\n if (typeof modelId === 'string') await applySubagentModel(session, agentType, modelId);\n }\n } else {\n await hydrateFactorySession(session, {\n orgId: owner.orgId,\n modelScope: sessionModel.modelScope,\n ownerUserId: owner.userId,\n defaultModelId: sessionModel.modelId,\n });\n\n // `hydrateFactorySession` has already persisted either the scoped model\n // or the SDK mode default as this thread's model of record.\n const selectedModelId = sessionModel.modelId ?? session.mode.resolve().defaultModelId ?? session.model.get();\n\n // Pin subagent models on the thread like the main model so a restart\n // restores the selected scope's choice rather than re-resolving it.\n for (const agentType of SUBAGENT_TYPES) {\n if (!selectedModelId) continue;\n if (await applySubagentModel(session, agentType, selectedModelId)) {\n await session.thread.setSetting({ key: pinnedSubagentModelKey(agentType), value: selectedModelId });\n }\n }\n }\n };\n}\n\nconst SUBAGENT_TYPES = ['explore', 'plan', 'execute'] as const;\nconst pinnedSubagentModelKey = (agentType: string) => `slackSubagentModelId_${agentType}`;\n\n/** Best-effort: a subagent model that can't be applied must not fail the message. */\nasync function applySubagentModel(\n session: Parameters<ChannelSessionStart>[0]['session'],\n agentType: (typeof SUBAGENT_TYPES)[number],\n modelId: string,\n): Promise<boolean> {\n try {\n await session.subagents.model.set({ modelId, agentType });\n return true;\n } catch (error) {\n console.warn('[slack] Failed to apply the subagent model', {\n agentType,\n modelId,\n error: error instanceof Error ? error.message : String(error),\n });\n return false;\n }\n}\n\n/**\n * The internal Mastra thread the framework created for a channel conversation.\n * The handler's `thread.id` is the platform thread id (e.g. `slack:C123:ts`),\n * NOT the internal UUID — the mapping lives in the stored thread's channel\n * metadata.\n */\nasync function findInternalThread(mastra: Mastra | undefined, thread: HandlerThread) {\n const store = await mastra?.getStorage()?.getStore('memory');\n const { threads } = (await store?.listThreads({\n filter: {\n metadata: {\n channel_platform: thread.adapter.name,\n channel_externalThreadId: thread.id,\n channel_externalChannelId: thread.channelId,\n },\n },\n perPage: 1,\n })) ?? { threads: [] };\n return threads[0];\n}\n\nasync function prepareExistingSessionOwnerContext(\n thread: HandlerThread,\n deps: SlackChannelDeps,\n ctx: ChannelHandlerContext,\n options: { expectedOrgId?: string; requireInternalThread: boolean },\n): Promise<'ready' | 'organization-mismatch'> {\n const sourceControls = configuredSourceControls(deps);\n if (sourceControls.length === 0 && !options.requireInternalThread) return 'ready';\n\n const internalThread = await findInternalThread(ctx.mastra, thread);\n if (!internalThread) {\n if (options.requireInternalThread) {\n throw new Error(`Could not resolve the internal Slack thread for ${thread.id}.`);\n }\n return 'ready';\n }\n if (sourceControls.length === 0 || internalThread.resourceId.startsWith('channel:')) return 'ready';\n if (!options.expectedOrgId) {\n throw new Error(`Could not authorize the owner of Slack Factory session ${internalThread.resourceId}.`);\n }\n\n const preparation = await prepareSessionRunContext(\n ctx.requestContext,\n internalThread.resourceId,\n { sessions: createSourceControlSessionLookup(sourceControls) },\n { expectedOrgId: options.expectedOrgId },\n );\n if (preparation === 'organization-mismatch') return preparation;\n if (preparation === 'unavailable') {\n throw new Error(`Could not authorize the owner of Slack Factory session ${internalThread.resourceId}.`);\n }\n return 'ready';\n}\n\n/**\n * Build the \"new session\" handler for mention / direct-message events. A mention or\n * DM on a not-yet-subscribed thread starts a NEW session; once subscribed, later\n * events are follow-ups and don't re-announce.\n */\n/**\n * Run the account-link + factory-routing gates for one inbound message.\n * Returns `null` when the run must not dispatch (a prompt card was posted\n * where possible); otherwise the dispatch context — with `routed` present\n * only when a linked sender resolved to a factory.\n */\nasync function gateDispatch(\n thread: HandlerThread,\n message: HandlerMessage,\n deps: SlackChannelDeps,\n ctx: ChannelHandlerContext,\n options: { requireInternalThread: boolean } = { requireInternalThread: false },\n): Promise<{\n routed?: { link: ChannelAccountLink; factoryProjectId: string };\n} | null> {\n const { accountLinks, projects } = deps;\n const sender = await resolveLinkedSender({ thread, message, accountLinks });\n if (sender.status === 'blocked') return null;\n // Linked senders must also route to a Factory project before a run starts.\n if (sender.status === 'linked' && accountLinks) {\n // Stamp the tenant on the run's request context — the single seam\n // `resolveCredentialStore` reads to load this sender's model credentials.\n // This belongs to the link, not to the routing: a linked sender whose\n // factory routing comes back `ungated` still exits below and dispatches, so\n // stamping only in the routed branch would silently run them on default\n // credentials.\n ctx.requestContext.set('user', { id: sender.link.userId, organizationId: sender.link.orgId });\n const ownerContext = await prepareExistingSessionOwnerContext(thread, deps, ctx, {\n expectedOrgId: sender.link.orgId,\n requireInternalThread: options.requireInternalThread,\n });\n if (ownerContext === 'organization-mismatch') {\n await thread.post('This thread belongs to a Factory session in another organization.');\n return null;\n }\n // Credential resolution reads an in-memory snapshot that only priming\n // fills, so prime whichever tenant now owns the run (the sender, or the\n // existing session's owner) — otherwise a cold snapshot after a restart\n // fails the run until that user happens to hit the web UI.\n await primeTenantCredentialsForRequestContext(ctx.requestContext);\n\n const route = await resolveFactoryForLink({ thread, ...sender, accountLinks, projects });\n if (route.status === 'blocked') return null;\n if (route.status === 'resolved') {\n return { routed: { link: sender.link, factoryProjectId: route.factoryProjectId } };\n }\n }\n return {};\n}\n\n/**\n * A channel id and a `ts` name a thread only inside the workspace that issued\n * them, so the team scopes the card's key — without it two workspaces could\n * hold one key and an aside would land on another tenant's card.\n */\nexport function slackThreadSource(thread: HandlerThread, teamId?: string): ExternalWorkItemSource {\n return {\n integrationId: thread.adapter.name,\n type: 'slack-thread',\n ...(teamId ? { workspaceId: teamId } : {}),\n externalId: thread.id,\n };\n}\n\n/** Cards written before the key carried a team are still keyed bare. */\nasync function findThreadWorkItem(\n workItems: WorkItemsStorage,\n thread: HandlerThread,\n teamId?: string,\n): Promise<WorkItemRow | null> {\n const scoped = await workItems.getBySource(slackThreadSource(thread, teamId));\n if (scoped || !teamId) return scoped;\n return workItems.getBySource(slackThreadSource(thread));\n}\n\nfunction preDispatchErrorDetails(error: unknown): { message: string; cause?: { message: string } } {\n const readMessage = (value: unknown) => {\n const message =\n typeof value === 'string'\n ? value\n : value && typeof value === 'object' && 'message' in value\n ? value.message\n : undefined;\n return typeof message === 'string' && message.length > 0 ? message : undefined;\n };\n try {\n const message = readMessage(error) ?? 'Unknown error';\n const cause = error && typeof error === 'object' && 'cause' in error ? readMessage(error.cause) : undefined;\n return { message, ...(cause ? { cause: { message: cause } } : {}) };\n } catch {\n return { message: 'Error details unavailable' };\n }\n}\n\nasync function reportPreDispatchError(\n thread: HandlerThread,\n message: HandlerMessage,\n ctx: ChannelHandlerContext,\n error: unknown,\n): Promise<void> {\n try {\n if (error instanceof ChannelSessionRejectedError) return;\n } catch {\n // A thrown proxy can fail even the refusal check; still report the failure.\n }\n const correlation = {\n platform: thread.adapter.name,\n threadId: thread.id,\n messageId: message.id,\n authorId: message.author.userId,\n };\n const logger = typeof ctx.mastra?.getLogger === 'function' ? ctx.mastra.getLogger() : undefined;\n const logError = (line: string) => (logger ? logger.error(line) : console.error(line));\n logError(`[slack] Pre-dispatch failure ${JSON.stringify({ ...correlation, error: preDispatchErrorDetails(error) })}`);\n try {\n // The message id is the lookup key for the diagnostic above.\n await thread.post(`Couldn’t start processing your message. Please try again.\\n\\`messageId: ${message.id}\\``);\n } catch (deliveryError) {\n logError(\n `[slack] Failed to deliver pre-dispatch error reply ${JSON.stringify({ ...correlation, error: preDispatchErrorDetails(deliveryError) })}`,\n );\n }\n}\n\nfunction createNewSessionChatHandler(deps: SlackChannelDeps): ChannelHandler {\n return async (thread, message, defaultHandler, ctx) => {\n // Gate on the sender having linked their Slack account to a Mastra tenant.\n // Unlinked → post the ephemeral Connect card and stop; no session/run is\n // created (which would otherwise be tenant-less and fail credential\n // resolution). This handler is the only gate — core dispatches whatever\n // reaches it — so every slot that can start a run must call it.\n let gate: Awaited<ReturnType<typeof gateDispatch>>;\n let isNewSession: boolean;\n try {\n gate = await gateDispatch(thread, message, deps, ctx);\n if (!gate) return;\n\n // A mention on a not-yet-subscribed thread is a NEW session. The\n // default handler auto-subscribes, so once subscribed this is a\n // follow-up mention — don't re-announce.\n isNewSession = !(await thread.isSubscribed());\n } catch (error) {\n await reportPreDispatchError(thread, message, ctx, error);\n return;\n }\n\n // Run the framework handler first so the internal Mastra thread and\n // controller session are created before we build the deep link.\n await defaultHandler(thread, message);\n\n if (!isNewSession) return;\n\n // The internal thread names the session the announcement card links to.\n const internalThread = await findInternalThread(ctx.mastra, thread);\n if (!internalThread) {\n console.warn('[onMention] no internal thread found for', thread.id);\n return;\n }\n\n // When the sender routed to a factory we know exactly which workspace the\n // session belongs to — deep-link straight into it. A repo-backed thread's\n // resourceId IS the Factory user-session id, so the link lands on the same\n // route a web-started run navigates to; chat-only threads keep the literal\n // `channel` segment (the real resource rides the `?resourceId=` override).\n // Unrouted senders fall back to the factory-agnostic /threads/ redirect.\n // One predicate drives both the path segment and the query param, so the\n // two can never disagree about what the URL already carries.\n const isChatOnly = internalThread.resourceId.startsWith('channel:');\n const workspaceSegment = isChatOnly ? 'channel' : encodeURIComponent(internalThread.resourceId);\n const threadPath = gate.routed\n ? `/factories/${encodeURIComponent(gate.routed.factoryProjectId)}/workspaces/${workspaceSegment}/threads/${encodeURIComponent(internalThread.id)}`\n : `/threads/${internalThread.id}`;\n\n // The param is an override for a URL that can't otherwise name its\n // resource. A routed repo-backed thread already spells the resourceId out\n // as its workspace segment, so appending it again is duplication the app\n // ignores. Chat-only threads need it (their segment is the literal string\n // `channel`), and so does the unrouted fallback, which has no workspace\n // segment at all — `ChannelThreadRedirect` forwards the search through.\n // Undefined without a public origin, and the card is then skipped.\n const needsResourceParam = isChatOnly || !gate.routed;\n const deepLink = process.env.MASTRACODE_PUBLIC_URL\n ? needsResourceParam\n ? `${process.env.MASTRACODE_PUBLIC_URL}${threadPath}?resourceId=${encodeURIComponent(internalThread.resourceId)}`\n : `${process.env.MASTRACODE_PUBLIC_URL}${threadPath}`\n : undefined;\n\n // The announcement card is only useful with a public origin to deep-link\n // to — otherwise the link would be `undefined/threads/...`. Without one the\n // session still exists; we just skip the broken card.\n if (!deepLink) return;\n\n await thread.post(\n Card({\n title: 'New session started',\n children: [Actions([LinkButton({ url: deepLink, label: 'View session' })])],\n }),\n );\n };\n}\n\n/**\n * An `aside` never reaches the agent, but it is still part of the conversation:\n * record it on the card the thread already has. The link is resolved here rather\n * than through `gateDispatch`, which posts a Connect card an aside must not\n * trigger. Best-effort — nothing here may surface in the Slack thread.\n */\nasync function ingestAside(\n thread: HandlerThread,\n message: HandlerMessage,\n { feed, workItems, accountLinks }: SlackChannelDeps,\n): Promise<void> {\n if (!feed || !workItems) return;\n const body = resolveEmojiShortcodes(message.text.replace(/^aside\\b[,:]?\\s*/i, '').trim());\n if (!body) return;\n try {\n const teamId = slackTeamId(message);\n const workItem = await findThreadWorkItem(workItems, thread, teamId);\n if (!workItem) return;\n\n const external: FactoryActorExternalIdentity = {\n platform: thread.adapter.name,\n ...(teamId ? { teamId } : {}),\n messageId: message.id,\n userId: message.author.userId,\n userName: message.author.userName,\n fullName: message.author.fullName,\n isBot: message.author.isBot,\n };\n const link =\n accountLinks && teamId\n ? await accountLinks.getAccountLink({\n platform: external.platform,\n externalTeamId: teamId,\n externalUserId: external.userId,\n })\n : null;\n\n const result = await feed.createComment({\n orgId: workItem.orgId,\n workItemId: workItem.id,\n author: actorFromChannelAuthor(external, link),\n body,\n occurredAt: message.metadata.dateSent,\n externalSource: slackCommentSource(thread.id, message.id, teamId),\n });\n if (result.status !== 'created') {\n console.warn('[slack] aside not ingested', { thread: thread.id, messageTs: message.id, status: result.status });\n }\n } catch (error) {\n // Slack was acked before this handler ran, so a lost aside is never redelivered.\n console.warn('[slack] aside ingest failed', { thread: thread.id, messageTs: message.id, error });\n }\n}\n\nexport const createHandlers = (deps: SlackChannelDeps): ChannelHandlers => {\n const newSessionChatHandler = createNewSessionChatHandler(deps);\n\n return {\n onSubscribedMessage: async (thread, message, defaultHandler, ctx) => {\n // `aside` as its own leading word lets humans talk in a subscribed\n // thread without the bot replying. Word boundary so messages that\n // merely start with \"aside...\" (e.g. \"asides can wait\") still route.\n if (/^aside\\b/i.test(message.text)) return ingestAside(thread, message, deps);\n // A subscribed follow-up from an unlinked sender must not run either\n // (e.g. the link was removed mid-conversation), and it must still\n // resolve a factory (e.g. the default was cleared or its factory\n // deleted mid-conversation).\n try {\n const gate = await gateDispatch(thread, message, deps, ctx, { requireInternalThread: true });\n if (!gate) return;\n } catch (error) {\n await reportPreDispatchError(thread, message, ctx, error);\n return;\n }\n await defaultHandler(thread, message);\n },\n onMention: newSessionChatHandler,\n onDirectMessage: newSessionChatHandler,\n };\n};\n\n/** Slack app credentials, passed in explicitly rather than read from env here. */\ninterface SlackCredentials {\n clientId?: string;\n clientSecret?: string;\n signingSecret: string;\n botToken?: string;\n}\n\nexport function createSlackChannelsConfig(deps: SlackChannelDeps & { slack: SlackCredentials }): FactoryChannelsConfig {\n // Per HTTP attempt, not per call: the WebClient's own retry policy still\n // waits out a 429. Without it a dead socket hangs a post forever.\n const adapter = createSlackAdapter({ ...deps.slack, webClientOptions: { timeout: SLACK_REQUEST_TIMEOUT_MS } });\n const slack =\n deps.adapterOptions?.streaming === false\n ? { adapter, ...deps.adapterOptions }\n : { adapter, ...deps.adapterOptions, streaming: deps.adapterOptions?.streaming ?? true };\n\n return {\n adapters: { slack },\n handlers: createHandlers(deps),\n // New linked+repo-backed threads own a Factory user-session id as their\n // resourceId, which is what makes the controller session repo-backed.\n resolveResourceId: createChannelResourceIdResolver(deps),\n resolveThreadId: resolveChannelThreadId,\n resolveSession: createChannelSessionResolver(deps),\n // Those sessions are created by the channel machinery, not the web kickoff,\n // so this is where they pick up the factory's configuration.\n onSessionStart: createChannelSessionStartHook(deps),\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../src/routes/fs.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../src/routes/fs.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AASpD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAC;AAC/E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACtF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,8DAA8D;IAC9D,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,4EAA4E;IAC5E,OAAO,EAAE,cAAc,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,wBAAwB;IACvC,2CAA2C;IAC3C,aAAa,EAAE,MAAM,CAAC;IACtB,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,sBAAsB,EAAE,CAAC;CACnC;AAED,MAAM,WAAW,aAAa;IAC5B,2CAA2C;IAC3C,aAAa,EAAE,MAAM,CAAC;IACtB,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,GAAG,aAAa,CAAC;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,qBAAqB;IACpC,uCAAuC;IACvC,aAAa,EAAE,MAAM,CAAC;IACtB,8DAA8D;IAC9D,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/B;;;;OAIG;IACH,YAAY,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACxD;AAED,MAAM,MAAM,qBAAqB,GAC7B,UAAU,GACV,OAAO,GACP,SAAS,GACT,SAAS,GACT,QAAQ,GACR,WAAW,GACX,YAAY,CAAC;AAEjB,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,qBAAqB,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,0BAA0B;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,uFAAuF;IACvF,MAAM,EAAE,MAAM,CAAC;IACf,+EAA+E;IAC/E,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,OAAO,CAAC;IACnB,4GAA4G;IAC5G,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,YAAY,CAAC,EAAE,0BAA0B,EAAE,CAAC;CAC7C;AAED,MAAM,WAAW,aAAa;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,MAAM,aAAa,GAAG,sBAAsB,CAAC;AAEnD,MAAM,WAAW,eAAe;IAC9B,2CAA2C;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,wCAAwC;IACxC,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,aAAa,EAAE,CAAC;CAC1B;AAkDD,2EAA2E;AAC3E,wBAAgB,aAAa,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAEnD;AA0ED;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAuCnG;AAqCD,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,MAAM,EACZ,aAAa,EAAE,MAAM,EACrB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,wBAAwB,CAAC,CAkBnC;AAED,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CA4CjH;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAOjG;AAUD,iFAAiF;AACjF,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,SAAS,CAAC;IAChB,QAAQ,EAAE;QAAE,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAA;KAAE,CAAC;IACtF,UAAU,EAAE,IAAI,CAAC,iBAAiB,EAAE,WAAW,CAAC,CAAC;CAClD;AA0BD,wBAAsB,0BAA0B,CAC9C,UAAU,EAAE,IAAI,CAAC,iBAAiB,EAAE,WAAW,CAAC,EAChD,OAAO,EAAE,oBAAoB,EAC7B,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,qBAAqB,CAAC,CAkBhC;AAiFD,iFAAiF;AACjF,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,oBAAoB,EAC7B,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,wBAAwB,CAAC,CAuCnC;AAED,kIAAkI;AAClI,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,oBAAoB,EAC7B,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE;IAAE,mBAAmB,CAAC,EAAE,OAAO,CAAA;CAAO,GAC9C,OAAO,CAAC,aAAa,CAAC,CAyBxB;AAYD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,EAAE,CAoBvE;AACD,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,EAAE,CAiB9E;AAED,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,MAAM;gBAEV,MAAM;gBAAc,MAAM;aAAW,OAAO;GA+BzF;AA8ED,wBAAsB,2BAA2B,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CA4C1G;AAiBD,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,oBAAoB,EAC7B,IAAI,EAAE,MAAM,EACZ,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,aAAa,CAAC,CAyExB;AAED,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,gBAAgB,CAUrE;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,aAAa,CAAA;CAAO,GAAG,QAAQ,EAAE,CA2LpG"}
package/dist/routes/fs.js CHANGED
@@ -369,7 +369,10 @@ async function listSessionRenderedPath(session, renderedRoot) {
369
369
  };
370
370
  if (!handle) return empty;
371
371
  const quotedRoot = `'${rootPath.replace(/'/g, `'\\''`)}'`;
372
- const result = await handle.sandbox.executeCommand("sh", ["-c", `test -d ${quotedRoot} && find ${quotedRoot} -mindepth 1 -printf '%y\\t%s\\t%T@\\t%p\\n' 2>/dev/null || true`], { timeout: 3e4 });
372
+ const result = await handle.sandbox.executeCommand("sh", ["-c", `test -d ${quotedRoot} && find ${quotedRoot} -mindepth 1 -printf '%y\\t%s\\t%T@\\t%p\\n' 2>/dev/null || true`], {
373
+ timeout: 3e4,
374
+ maxRetainedBytes: Infinity
375
+ });
373
376
  if (result.exitCode !== 0) return empty;
374
377
  const entries = [];
375
378
  for (const line of result.stdout.split("\n")) {
@@ -548,7 +551,10 @@ async function listRepositoryChanges(sandbox, workdir, baseBranch) {
548
551
  "--porcelain=v1",
549
552
  "-z",
550
553
  "--untracked-files=all"
551
- ], { timeout: 3e4 }),
554
+ ], {
555
+ timeout: 3e4,
556
+ maxRetainedBytes: Infinity
557
+ }),
552
558
  sandbox.executeCommand("git", [
553
559
  "-C",
554
560
  workdir,
@@ -557,14 +563,20 @@ async function listRepositoryChanges(sandbox, workdir, baseBranch) {
557
563
  "-z",
558
564
  "--find-renames",
559
565
  comparisonBase
560
- ], { timeout: 3e4 }),
566
+ ], {
567
+ timeout: 3e4,
568
+ maxRetainedBytes: Infinity
569
+ }),
561
570
  sandbox.executeCommand("sh", [
562
571
  "-c",
563
572
  WORKSPACE_NUMSTAT_SCRIPT,
564
573
  "mastracode-numstat",
565
574
  workdir,
566
575
  comparisonBase
567
- ], { timeout: 3e4 })
576
+ ], {
577
+ timeout: 3e4,
578
+ maxRetainedBytes: Infinity
579
+ })
568
580
  ]);
569
581
  if (statusResult.exitCode !== 0) return {
570
582
  available: false,
@@ -692,7 +704,10 @@ async function readSessionWorkspaceDiff(session, path, previousPath) {
692
704
  "--exclude-standard",
693
705
  "--",
694
706
  target.relative
695
- ], { timeout: 3e4 });
707
+ ], {
708
+ timeout: 3e4,
709
+ maxRetainedBytes: Infinity
710
+ });
696
711
  if (untracked.exitCode === 0 && untracked.stdout.trim()) {
697
712
  result = await executeBoundedGitDiff(handle.sandbox, [
698
713
  "-C",
@@ -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.\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 },\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 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 }),\n sandbox.executeCommand('git', ['-C', workdir, 'diff', '--name-status', '-z', '--find-renames', comparisonBase], {\n timeout: 30_000,\n }),\n sandbox.executeCommand('sh', ['-c', WORKSPACE_NUMSTAT_SCRIPT, 'mastracode-numstat', workdir, comparisonBase], {\n timeout: 30_000,\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 },\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":"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"}
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":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/factory",
3
- "version": "0.23.0-alpha.3",
3
+ "version": "0.23.0-alpha.5",
4
4
  "description": "Mastra Software Factory module: the server core behind the Mastra Software Factory — storage domains, integrations, and surfaces for agent-powered software delivery",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -57,11 +57,11 @@
57
57
  "hono": "^4.13.7",
58
58
  "posthog-node": "^5.46.1",
59
59
  "zod": "^4.6.4",
60
+ "@mastra/auth-studio": "1.3.9",
60
61
  "@mastra/auth-workos": "1.6.6",
61
- "@mastra/code-sdk": "1.14.0-alpha.3",
62
+ "@mastra/code-sdk": "1.14.0-alpha.5",
62
63
  "@mastra/slack": "1.7.2",
63
- "@mastra/core": "1.78.0-alpha.3",
64
- "@mastra/auth-studio": "1.3.9"
64
+ "@mastra/core": "1.78.0-alpha.5"
65
65
  },
66
66
  "devDependencies": {
67
67
  "@types/node": "22.20.1",
@@ -70,12 +70,12 @@
70
70
  "typescript": "^6.0.3",
71
71
  "typescript-eslint": "^8.57.0",
72
72
  "vitest": "4.1.11",
73
- "@mastra/libsql": "1.26.2-alpha.0",
74
73
  "@internal/lint": "0.0.143",
75
- "@mastra/server": "1.78.0-alpha.3",
74
+ "@mastra/libsql": "1.26.2-alpha.0",
76
75
  "@mastra/pg": "1.31.2-alpha.1",
77
- "@internal/types-builder": "0.0.118",
78
- "@internal/workspace": "0.0.15"
76
+ "@mastra/server": "1.78.0-alpha.5",
77
+ "@internal/workspace": "0.0.15",
78
+ "@internal/types-builder": "0.0.118"
79
79
  },
80
80
  "engines": {
81
81
  "node": ">=22.19.0"