@mastra/factory 0.16.1-alpha.0 → 0.17.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export { BoardDefinitionError, createBoardRegistry, defaultBoards, defineBoard,
2
2
  export type { BoardDefinition, BoardPhaseDefinition, BoardRegistry, BoardTransition, BoardTransitionPolicy, BoardTransitionPolicyContext, BoardTransitionPolicyResult, InstalledBoard, ReviewBoardPhase, WorkBoardPhase, } from './boards/index.js';
3
3
  export { MastraFactory } from './factory.js';
4
4
  export type { MastraArgs, MastraFactoryConfig, MastraFactorySandboxConfig, FactorySandboxStart } from './factory.js';
5
+ export type { GithubRuleOverrides } from './integrations/github/default-rules.js';
5
6
  export type { FactorySandboxContext, SessionSetupGate, SessionSetupRun } from './sandbox/session-sandbox.js';
6
7
  export { ChannelIdentityStorage } from './storage/domains/channel-identity/base.js';
7
8
  export type { ChannelAccountLink, ChannelAccountLinkEntry, ChannelAccountLinkKey, ChannelAccountLinkNames, } from './storage/domains/channel-identity/base.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,aAAa,EACb,WAAW,EACX,WAAW,EACX,SAAS,GACV,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,eAAe,EACf,oBAAoB,EACpB,aAAa,EACb,eAAe,EACf,qBAAqB,EACrB,4BAA4B,EAC5B,2BAA2B,EAC3B,cAAc,EACd,gBAAgB,EAChB,cAAc,GACf,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,YAAY,EAAE,UAAU,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACrH,YAAY,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC7G,OAAO,EAAE,sBAAsB,EAAE,MAAM,4CAA4C,CAAC;AACpF,YAAY,EACV,kBAAkB,EAClB,uBAAuB,EACvB,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,4CAA4C,CAAC;AACpD,OAAO,EAAE,sBAAsB,EAAE,MAAM,oCAAoC,CAAC;AAC5E,YAAY,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AACxE,YAAY,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAC7F,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,EAAE,6BAA6B,EAAE,sCAAsC,EAAE,MAAM,wBAAwB,CAAC;AAC/G,YAAY,EACV,sBAAsB,EACtB,uBAAuB,EACvB,6BAA6B,EAC7B,0BAA0B,GAC3B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AACnD,YAAY,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAInD,YAAY,EACV,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,aAAa,EACb,WAAW,EACX,WAAW,EACX,SAAS,GACV,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,eAAe,EACf,oBAAoB,EACpB,aAAa,EACb,eAAe,EACf,qBAAqB,EACrB,4BAA4B,EAC5B,2BAA2B,EAC3B,cAAc,EACd,gBAAgB,EAChB,cAAc,GACf,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,YAAY,EAAE,UAAU,EAAE,mBAAmB,EAAE,0BAA0B,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAGrH,YAAY,EAAE,mBAAmB,EAAE,MAAM,wCAAwC,CAAC;AAClF,YAAY,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC7G,OAAO,EAAE,sBAAsB,EAAE,MAAM,4CAA4C,CAAC;AACpF,YAAY,EACV,kBAAkB,EAClB,uBAAuB,EACvB,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,4CAA4C,CAAC;AACpD,OAAO,EAAE,sBAAsB,EAAE,MAAM,oCAAoC,CAAC;AAC5E,YAAY,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,EAAE,gBAAgB,EAAE,MAAM,sCAAsC,CAAC;AACxE,YAAY,EAAE,mBAAmB,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAC7F,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACnE,OAAO,EAAE,6BAA6B,EAAE,sCAAsC,EAAE,MAAM,wBAAwB,CAAC;AAC/G,YAAY,EACV,sBAAsB,EACtB,uBAAuB,EACvB,6BAA6B,EAC7B,0BAA0B,GAC3B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AACnD,YAAY,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAInD,YAAY,EACV,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,wBAAwB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"session-subscriptions.d.ts","sourceRoot":"","sources":["../../../src/integrations/github/session-subscriptions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAWnE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAyI1D,wBAAsB,oCAAoC,CACxD,cAAc,EAAE,cAAc,EAC9B,WAAW,EAAE,MAAM,GAAG,MAAM,EAC5B,MAAM,EAAE,mBAAmB,GAAG,eAAe,EAC7C,MAAM,EAAE,iBAAiB,+BAY1B;AAED,wBAAsB,wCAAwC,CAC5D,cAAc,EAAE,cAAc,EAC9B,WAAW,EAAE,MAAM,GAAG,MAAM,EAC5B,MAAM,EAAE,iBAAiB,mBAM1B;AAED,wBAAsB,0BAA0B,CAC9C,cAAc,EAAE,cAAc,EAC9B,KAAK,EAAE;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAC5C,MAAM,EAAE,iBAAiB,uEAa1B;AAED,wBAAsB,kBAAkB,CAAC,cAAc,EAAE,cAAc,EAAE,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBjH;AAED,wBAAgB,6BAA6B,CAAC,cAAc,EAAE,cAAc,EAAE,MAAM,EAAE,iBAAiB;;;;;;;;;;;;;;;;;EA0CtG;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,sBASA"}
1
+ {"version":3,"file":"session-subscriptions.d.ts","sourceRoot":"","sources":["../../../src/integrations/github/session-subscriptions.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAWnE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAyI1D,wBAAsB,oCAAoC,CACxD,cAAc,EAAE,cAAc,EAC9B,WAAW,EAAE,MAAM,GAAG,MAAM,EAC5B,MAAM,EAAE,mBAAmB,GAAG,eAAe,EAC7C,MAAM,EAAE,iBAAiB,+BAY1B;AAED,wBAAsB,wCAAwC,CAC5D,cAAc,EAAE,cAAc,EAC9B,WAAW,EAAE,MAAM,GAAG,MAAM,EAC5B,MAAM,EAAE,iBAAiB,mBAM1B;AAED,wBAAsB,0BAA0B,CAC9C,cAAc,EAAE,cAAc,EAC9B,KAAK,EAAE;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAC5C,MAAM,EAAE,iBAAiB,uEAa1B;AAED,wBAAsB,kBAAkB,CAAC,cAAc,EAAE,cAAc,EAAE,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBjH;AAED,wBAAgB,6BAA6B,CAAC,cAAc,EAAE,cAAc,EAAE,MAAM,EAAE,iBAAiB;;;;;;;;;;;;;;;;;EA0CtG;AAID,wBAAgB,uBAAuB,CAAC,OAAO,EAAE;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,sBAgBA"}
@@ -205,8 +205,14 @@ function createGithubSubscriptionTools(requestContext, github) {
205
205
  })
206
206
  };
207
207
  }
208
+ const GITHUB_PULL_REQUEST_URL = /^https:\/\/github\.com\/[^\s/]+\/[^\s/]+\/pull\/\d+\/?$/;
208
209
  function parseCreatedPullRequest(context) {
209
- if (context.toolName !== "execute_command" || context.error) return void 0;
210
+ if (context.error) return void 0;
211
+ if (context.toolName === "source_control_create_change_request") {
212
+ const url = context.output?.url;
213
+ return typeof url === "string" && GITHUB_PULL_REQUEST_URL.test(url) ? url.replace(/\/$/, "") : void 0;
214
+ }
215
+ if (context.toolName !== "execute_command") return void 0;
210
216
  const command = context.input?.command;
211
217
  if (typeof command !== "string" || !runsPullRequestCreate(command)) return void 0;
212
218
  const output = context.output;
@@ -1 +1 @@
1
- {"version":3,"file":"session-subscriptions.js","names":[],"sources":["../../../src/integrations/github/session-subscriptions.ts"],"sourcesContent":["import type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport type { RequestContext } from '@mastra/core/request-context';\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\nimport { getFactoryAuthOrgId, getFactoryAuthUserFromContext, getFactoryAuthUserId } from '../../auth.js';\nimport { runsPullRequestCreate } from '../../session/shell-commands.js';\nimport type {\n ProjectRepository,\n ProjectSourceControlConnection,\n SourceControlInstallation,\n SourceControlRepository,\n} from '../../storage/domains/source-control/base.js';\nimport type { GithubIntegration } from './integration.js';\nimport { getGithubPat } from './pat.js';\nimport { subscribeToPullRequest, unsubscribeFromPullRequest } from './subscriptions.js';\nimport { getRegisteredGithubPatKind, injectGithubToken } from './token-refresh.js';\n\ntype RepositorySessionState = { factoryProjectId?: string; projectRepositoryId?: string };\n\n/**\n * The host-authenticated user placed on the request context under the `user`\n * key, read through the host's own normalizer. A local mirror of that shape\n * used to live here; it silently missed the provider shapes the normalizer\n * knows about, which turned every subscription tool into a no-op for those\n * users instead of an error anybody could see.\n */\nfunction sessionUserId(requestContext: RequestContext): string | undefined {\n return getFactoryAuthUserId(getFactoryAuthUserFromContext(requestContext));\n}\n\nfunction sessionOrgId(requestContext: RequestContext): string | undefined {\n return getFactoryAuthOrgId(getFactoryAuthUserFromContext(requestContext));\n}\n\nconst pullRequestInputSchema = z.object({\n pullRequest: z.union([z.number().int().positive(), z.string().min(1)]),\n});\n\nconst TRIAGE_COMMENT_MARKER = '<!-- mastra-factory-triage -->';\nconst triageCommentInputSchema = z.object({\n issueNumber: z.number().int().positive(),\n body: z.string().startsWith(TRIAGE_COMMENT_MARKER),\n});\n\nconst triageCommentLocks = new Map<string, Promise<void>>();\n\nasync function serializeTriageComment<T>(key: string, operation: () => Promise<T>): Promise<T> {\n const previous = triageCommentLocks.get(key) ?? Promise.resolve();\n let release: (() => void) | undefined;\n const current = new Promise<void>(resolve => {\n release = resolve;\n });\n const queued = previous.then(() => current);\n triageCommentLocks.set(key, queued);\n await previous;\n try {\n return await operation();\n } finally {\n release!();\n if (triageCommentLocks.get(key) === queued) triageCommentLocks.delete(key);\n }\n}\n\ninterface SessionTarget {\n context: AgentControllerRequestContext<RepositorySessionState>;\n projectRepository: ProjectRepository;\n connection: ProjectSourceControlConnection;\n installation: SourceControlInstallation;\n repository: SourceControlRepository;\n orgId: string;\n userId: string;\n}\n\nfunction parsePullRequest(value: number | string, expectedRepo: string): number {\n if (typeof value === 'number') return value;\n if (/^\\d+$/.test(value)) return Number(value);\n const match = value.match(/^https:\\/\\/github\\.com\\/([^/]+\\/[^/]+)\\/pull\\/(\\d+)\\/?$/i);\n if (!match || match[1]!.toLowerCase() !== expectedRepo.toLowerCase()) {\n throw new Error(`Pull request must belong to ${expectedRepo}.`);\n }\n return Number(match[2]);\n}\n\n/**\n * Whether the current request comes from a session that GitHub subscriptions\n * can ever apply to: an authenticated org user on a GitHub-project session\n * with an active thread. Mirrors the gate in `resolveSessionTarget` without\n * throwing, for passive callers that should no-op instead of erroring.\n */\nfunction isGithubProjectSession(requestContext: RequestContext): boolean {\n const context = requestContext.get('controller') as AgentControllerRequestContext<RepositorySessionState> | undefined;\n return Boolean(\n context?.threadId &&\n context.getState().projectRepositoryId &&\n sessionOrgId(requestContext) &&\n sessionUserId(requestContext),\n );\n}\n\nasync function resolveSessionTarget(requestContext: RequestContext, github: GithubIntegration): Promise<SessionTarget> {\n const context = requestContext.get('controller') as AgentControllerRequestContext<RepositorySessionState> | undefined;\n const orgId = sessionOrgId(requestContext);\n const userId = sessionUserId(requestContext);\n const projectRepositoryId = context?.getState().projectRepositoryId;\n if (!context || !context.threadId || !projectRepositoryId || !orgId || !userId) {\n throw new Error('GitHub subscriptions require an authenticated repository session with an active thread.');\n }\n\n const projectRepository = await github.sourceControlStorage.projectRepositories.get({\n orgId,\n id: projectRepositoryId,\n });\n if (!projectRepository) throw new Error('Project repository not found for this organization.');\n const connection = await github.sourceControlStorage.connections.get({ orgId, id: projectRepository.connectionId });\n if (!connection) throw new Error('Source-control connection not found for this organization.');\n const repository = await github.sourceControlStorage.repositories.get({ orgId, id: projectRepository.repositoryId });\n if (!repository) throw new Error('Repository not found for this organization.');\n const installation = await github.sourceControlStorage.installations.get({ orgId, id: connection.installationId });\n if (!installation) throw new Error('Source-control installation not found for this organization.');\n return { context, projectRepository, connection, installation, repository, orgId, userId };\n}\n\nasync function verifyPullRequest(target: SessionTarget, pullRequest: number, github: GithubIntegration) {\n const [owner, repo] = target.repository.slug.split('/');\n if (!owner || !repo) throw new Error('GitHub repository is invalid.');\n const octokit = github.getInstallationOctokit(Number(target.installation.externalId));\n const { data } = await octokit.pulls.get({ owner, repo, pull_number: pullRequest });\n if (String(data.base.repo.id) !== target.repository.externalId)\n throw new Error('Pull request repository does not match the active project repository.');\n}\n\nasync function subscriptionInput(target: SessionTarget, pullRequestNumber: number) {\n return {\n orgId: target.orgId,\n installationExternalId: target.installation.externalId,\n projectRepositoryId: target.projectRepository.id,\n repositoryExternalId: target.repository.externalId,\n repositorySlug: target.repository.slug,\n changeRequestId: String(pullRequestNumber),\n sessionId: target.context.session.id,\n ownerId: target.context.session.ownerId,\n resourceId: target.connection.factoryProjectId,\n threadId: target.context.threadId!,\n sessionScope: target.context.scope,\n source: 'explicit-tool' as const,\n subscribedByUserId: target.userId,\n };\n}\n\nexport async function subscribeCurrentSessionToPullRequest(\n requestContext: RequestContext,\n pullRequest: number | string,\n source: 'auto-gh-pr-create' | 'explicit-tool',\n github: GithubIntegration,\n) {\n // The auto path observes every successful `gh pr create` in every session,\n // including local and non-GitHub-project sessions where subscriptions can\n // never apply. Skip silently there; only the explicit tool should surface\n // \"this session cannot subscribe\" as an error.\n if (source === 'auto-gh-pr-create' && !isGithubProjectSession(requestContext)) return undefined;\n const target = await resolveSessionTarget(requestContext, github);\n const number = parsePullRequest(pullRequest, target.repository.slug);\n await verifyPullRequest(target, number, github);\n await subscribeToPullRequest({ ...(await subscriptionInput(target, number)), source }, github.integrationStorage);\n return number;\n}\n\nexport async function unsubscribeCurrentSessionFromPullRequest(\n requestContext: RequestContext,\n pullRequest: number | string,\n github: GithubIntegration,\n) {\n const target = await resolveSessionTarget(requestContext, github);\n const number = parsePullRequest(pullRequest, target.repository.slug);\n await unsubscribeFromPullRequest(await subscriptionInput(target, number), github.integrationStorage);\n return number;\n}\n\nexport async function upsertFactoryTriageComment(\n requestContext: RequestContext,\n input: { issueNumber: number; body: string },\n github: GithubIntegration,\n) {\n const target = await resolveSessionTarget(requestContext, github);\n const installationId = Number(target.installation.externalId);\n if (!Number.isSafeInteger(installationId) || installationId <= 0) throw new Error('GitHub installation is invalid.');\n return serializeTriageComment(`${installationId}:${target.repository.externalId}:${input.issueNumber}`, () =>\n github.upsertFactoryTriageComment({\n installationId,\n repository: target.repository.slug,\n issueNumber: input.issueNumber,\n body: input.body,\n }),\n );\n}\n\nexport async function refreshGithubToken(requestContext: RequestContext, github: GithubIntegration): Promise<void> {\n const target = await resolveSessionTarget(requestContext, github);\n // `GH_TOKEN` feeds the `gh` CLI, so a configured org PAT wins over a minted\n // installation token (which 403s on integration-restricted endpoints). The\n // workspace records which PAT kind the sandbox was provisioned with, so a\n // review-board sandbox keeps its reviewer token on refresh.\n const pat = await getGithubPat(\n () => github.integrationStorage,\n target.orgId,\n getRegisteredGithubPatKind(requestContext),\n );\n if (pat) {\n injectGithubToken(requestContext, pat);\n return;\n }\n const access = await github.versionControl.getRepositoryAccess({\n orgId: target.orgId,\n repositoryId: target.repository.id,\n });\n const token = access.authorization?.token;\n if (!token) throw new Error('Repository access did not include a bearer token for the Factory session.');\n injectGithubToken(requestContext, token);\n}\n\nexport function createGithubSubscriptionTools(requestContext: RequestContext, github: GithubIntegration) {\n if (!isGithubProjectSession(requestContext)) return {};\n\n return {\n github_refresh_token: createTool({\n id: 'github_refresh_token',\n description:\n 'Refresh GitHub CLI authentication in the active Factory sandbox. Use this after a gh command fails because authentication is expired, invalid, or missing. It installs a fresh GH_TOKEN for subsequent sandbox commands. After this tool succeeds, retry the failed gh command. Takes no arguments and never returns the token.',\n inputSchema: z.object({}),\n execute: async () => {\n await refreshGithubToken(requestContext, github);\n return { refreshed: true };\n },\n }),\n github_upsert_factory_triage_comment: createTool({\n id: 'github_upsert_factory_triage_comment',\n description:\n 'Create or update this Factory App’s canonical triage handoff comment on an issue in the active repository. Use this for every marked pending or final Factory triage handoff; never use gh to create or edit that handoff.',\n inputSchema: triageCommentInputSchema,\n execute: async input => upsertFactoryTriageComment(requestContext, input, github),\n }),\n github_subscribe_pr: createTool({\n id: 'github_subscribe_pr',\n description:\n 'Subscribe this thread to GitHub pull request activity. You usually do not need this tool: successful gh pr create commands subscribe automatically. Use it for an existing PR or to recover when automatic subscription did not occur. Closed or merged PRs are unsubscribed automatically. Accepts a PR number or canonical URL for the active project.',\n inputSchema: pullRequestInputSchema,\n execute: async ({ pullRequest }) => {\n const number = await subscribeCurrentSessionToPullRequest(requestContext, pullRequest, 'explicit-tool', github);\n return { subscribed: true, pullRequestNumber: number };\n },\n }),\n github_unsubscribe_pr: createTool({\n id: 'github_unsubscribe_pr',\n description:\n 'Manually unsubscribe this thread from GitHub pull request activity. You usually do not need this tool because closed or merged PRs are unsubscribed automatically. Use it to stop notifications before then. Accepts a PR number or canonical URL for the active project.',\n inputSchema: pullRequestInputSchema,\n execute: async ({ pullRequest }) => {\n const number = await unsubscribeCurrentSessionFromPullRequest(requestContext, pullRequest, github);\n return { subscribed: false, pullRequestNumber: number };\n },\n }),\n };\n}\n\nexport function parseCreatedPullRequest(context: {\n toolName: string;\n input: unknown;\n output?: unknown;\n error?: unknown;\n}) {\n if (context.toolName !== 'execute_command' || context.error) return undefined;\n const command = (context.input as { command?: unknown } | undefined)?.command;\n if (typeof command !== 'string' || !runsPullRequestCreate(command)) return undefined;\n const output = context.output as { stdout?: unknown; result?: unknown } | undefined;\n const stdout = typeof context.output === 'string' ? context.output : (output?.stdout ?? output?.result);\n if (typeof stdout !== 'string') return undefined;\n const urls = stdout.match(/https:\\/\\/github\\.com\\/[^\\s/]+\\/[^\\s/]+\\/pull\\/\\d+/g) ?? [];\n return urls.length === 1 ? urls[0] : undefined;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"session-subscriptions.js","names":[],"sources":["../../../src/integrations/github/session-subscriptions.ts"],"sourcesContent":["import type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport type { RequestContext } from '@mastra/core/request-context';\nimport { createTool } from '@mastra/core/tools';\nimport { z } from 'zod';\nimport { getFactoryAuthOrgId, getFactoryAuthUserFromContext, getFactoryAuthUserId } from '../../auth.js';\nimport { runsPullRequestCreate } from '../../session/shell-commands.js';\nimport type {\n ProjectRepository,\n ProjectSourceControlConnection,\n SourceControlInstallation,\n SourceControlRepository,\n} from '../../storage/domains/source-control/base.js';\nimport type { GithubIntegration } from './integration.js';\nimport { getGithubPat } from './pat.js';\nimport { subscribeToPullRequest, unsubscribeFromPullRequest } from './subscriptions.js';\nimport { getRegisteredGithubPatKind, injectGithubToken } from './token-refresh.js';\n\ntype RepositorySessionState = { factoryProjectId?: string; projectRepositoryId?: string };\n\n/**\n * The host-authenticated user placed on the request context under the `user`\n * key, read through the host's own normalizer. A local mirror of that shape\n * used to live here; it silently missed the provider shapes the normalizer\n * knows about, which turned every subscription tool into a no-op for those\n * users instead of an error anybody could see.\n */\nfunction sessionUserId(requestContext: RequestContext): string | undefined {\n return getFactoryAuthUserId(getFactoryAuthUserFromContext(requestContext));\n}\n\nfunction sessionOrgId(requestContext: RequestContext): string | undefined {\n return getFactoryAuthOrgId(getFactoryAuthUserFromContext(requestContext));\n}\n\nconst pullRequestInputSchema = z.object({\n pullRequest: z.union([z.number().int().positive(), z.string().min(1)]),\n});\n\nconst TRIAGE_COMMENT_MARKER = '<!-- mastra-factory-triage -->';\nconst triageCommentInputSchema = z.object({\n issueNumber: z.number().int().positive(),\n body: z.string().startsWith(TRIAGE_COMMENT_MARKER),\n});\n\nconst triageCommentLocks = new Map<string, Promise<void>>();\n\nasync function serializeTriageComment<T>(key: string, operation: () => Promise<T>): Promise<T> {\n const previous = triageCommentLocks.get(key) ?? Promise.resolve();\n let release: (() => void) | undefined;\n const current = new Promise<void>(resolve => {\n release = resolve;\n });\n const queued = previous.then(() => current);\n triageCommentLocks.set(key, queued);\n await previous;\n try {\n return await operation();\n } finally {\n release!();\n if (triageCommentLocks.get(key) === queued) triageCommentLocks.delete(key);\n }\n}\n\ninterface SessionTarget {\n context: AgentControllerRequestContext<RepositorySessionState>;\n projectRepository: ProjectRepository;\n connection: ProjectSourceControlConnection;\n installation: SourceControlInstallation;\n repository: SourceControlRepository;\n orgId: string;\n userId: string;\n}\n\nfunction parsePullRequest(value: number | string, expectedRepo: string): number {\n if (typeof value === 'number') return value;\n if (/^\\d+$/.test(value)) return Number(value);\n const match = value.match(/^https:\\/\\/github\\.com\\/([^/]+\\/[^/]+)\\/pull\\/(\\d+)\\/?$/i);\n if (!match || match[1]!.toLowerCase() !== expectedRepo.toLowerCase()) {\n throw new Error(`Pull request must belong to ${expectedRepo}.`);\n }\n return Number(match[2]);\n}\n\n/**\n * Whether the current request comes from a session that GitHub subscriptions\n * can ever apply to: an authenticated org user on a GitHub-project session\n * with an active thread. Mirrors the gate in `resolveSessionTarget` without\n * throwing, for passive callers that should no-op instead of erroring.\n */\nfunction isGithubProjectSession(requestContext: RequestContext): boolean {\n const context = requestContext.get('controller') as AgentControllerRequestContext<RepositorySessionState> | undefined;\n return Boolean(\n context?.threadId &&\n context.getState().projectRepositoryId &&\n sessionOrgId(requestContext) &&\n sessionUserId(requestContext),\n );\n}\n\nasync function resolveSessionTarget(requestContext: RequestContext, github: GithubIntegration): Promise<SessionTarget> {\n const context = requestContext.get('controller') as AgentControllerRequestContext<RepositorySessionState> | undefined;\n const orgId = sessionOrgId(requestContext);\n const userId = sessionUserId(requestContext);\n const projectRepositoryId = context?.getState().projectRepositoryId;\n if (!context || !context.threadId || !projectRepositoryId || !orgId || !userId) {\n throw new Error('GitHub subscriptions require an authenticated repository session with an active thread.');\n }\n\n const projectRepository = await github.sourceControlStorage.projectRepositories.get({\n orgId,\n id: projectRepositoryId,\n });\n if (!projectRepository) throw new Error('Project repository not found for this organization.');\n const connection = await github.sourceControlStorage.connections.get({ orgId, id: projectRepository.connectionId });\n if (!connection) throw new Error('Source-control connection not found for this organization.');\n const repository = await github.sourceControlStorage.repositories.get({ orgId, id: projectRepository.repositoryId });\n if (!repository) throw new Error('Repository not found for this organization.');\n const installation = await github.sourceControlStorage.installations.get({ orgId, id: connection.installationId });\n if (!installation) throw new Error('Source-control installation not found for this organization.');\n return { context, projectRepository, connection, installation, repository, orgId, userId };\n}\n\nasync function verifyPullRequest(target: SessionTarget, pullRequest: number, github: GithubIntegration) {\n const [owner, repo] = target.repository.slug.split('/');\n if (!owner || !repo) throw new Error('GitHub repository is invalid.');\n const octokit = github.getInstallationOctokit(Number(target.installation.externalId));\n const { data } = await octokit.pulls.get({ owner, repo, pull_number: pullRequest });\n if (String(data.base.repo.id) !== target.repository.externalId)\n throw new Error('Pull request repository does not match the active project repository.');\n}\n\nasync function subscriptionInput(target: SessionTarget, pullRequestNumber: number) {\n return {\n orgId: target.orgId,\n installationExternalId: target.installation.externalId,\n projectRepositoryId: target.projectRepository.id,\n repositoryExternalId: target.repository.externalId,\n repositorySlug: target.repository.slug,\n changeRequestId: String(pullRequestNumber),\n sessionId: target.context.session.id,\n ownerId: target.context.session.ownerId,\n resourceId: target.connection.factoryProjectId,\n threadId: target.context.threadId!,\n sessionScope: target.context.scope,\n source: 'explicit-tool' as const,\n subscribedByUserId: target.userId,\n };\n}\n\nexport async function subscribeCurrentSessionToPullRequest(\n requestContext: RequestContext,\n pullRequest: number | string,\n source: 'auto-gh-pr-create' | 'explicit-tool',\n github: GithubIntegration,\n) {\n // The auto path observes every successful `gh pr create` in every session,\n // including local and non-GitHub-project sessions where subscriptions can\n // never apply. Skip silently there; only the explicit tool should surface\n // \"this session cannot subscribe\" as an error.\n if (source === 'auto-gh-pr-create' && !isGithubProjectSession(requestContext)) return undefined;\n const target = await resolveSessionTarget(requestContext, github);\n const number = parsePullRequest(pullRequest, target.repository.slug);\n await verifyPullRequest(target, number, github);\n await subscribeToPullRequest({ ...(await subscriptionInput(target, number)), source }, github.integrationStorage);\n return number;\n}\n\nexport async function unsubscribeCurrentSessionFromPullRequest(\n requestContext: RequestContext,\n pullRequest: number | string,\n github: GithubIntegration,\n) {\n const target = await resolveSessionTarget(requestContext, github);\n const number = parsePullRequest(pullRequest, target.repository.slug);\n await unsubscribeFromPullRequest(await subscriptionInput(target, number), github.integrationStorage);\n return number;\n}\n\nexport async function upsertFactoryTriageComment(\n requestContext: RequestContext,\n input: { issueNumber: number; body: string },\n github: GithubIntegration,\n) {\n const target = await resolveSessionTarget(requestContext, github);\n const installationId = Number(target.installation.externalId);\n if (!Number.isSafeInteger(installationId) || installationId <= 0) throw new Error('GitHub installation is invalid.');\n return serializeTriageComment(`${installationId}:${target.repository.externalId}:${input.issueNumber}`, () =>\n github.upsertFactoryTriageComment({\n installationId,\n repository: target.repository.slug,\n issueNumber: input.issueNumber,\n body: input.body,\n }),\n );\n}\n\nexport async function refreshGithubToken(requestContext: RequestContext, github: GithubIntegration): Promise<void> {\n const target = await resolveSessionTarget(requestContext, github);\n // `GH_TOKEN` feeds the `gh` CLI, so a configured org PAT wins over a minted\n // installation token (which 403s on integration-restricted endpoints). The\n // workspace records which PAT kind the sandbox was provisioned with, so a\n // review-board sandbox keeps its reviewer token on refresh.\n const pat = await getGithubPat(\n () => github.integrationStorage,\n target.orgId,\n getRegisteredGithubPatKind(requestContext),\n );\n if (pat) {\n injectGithubToken(requestContext, pat);\n return;\n }\n const access = await github.versionControl.getRepositoryAccess({\n orgId: target.orgId,\n repositoryId: target.repository.id,\n });\n const token = access.authorization?.token;\n if (!token) throw new Error('Repository access did not include a bearer token for the Factory session.');\n injectGithubToken(requestContext, token);\n}\n\nexport function createGithubSubscriptionTools(requestContext: RequestContext, github: GithubIntegration) {\n if (!isGithubProjectSession(requestContext)) return {};\n\n return {\n github_refresh_token: createTool({\n id: 'github_refresh_token',\n description:\n 'Refresh GitHub CLI authentication in the active Factory sandbox. Use this after a gh command fails because authentication is expired, invalid, or missing. It installs a fresh GH_TOKEN for subsequent sandbox commands. After this tool succeeds, retry the failed gh command. Takes no arguments and never returns the token.',\n inputSchema: z.object({}),\n execute: async () => {\n await refreshGithubToken(requestContext, github);\n return { refreshed: true };\n },\n }),\n github_upsert_factory_triage_comment: createTool({\n id: 'github_upsert_factory_triage_comment',\n description:\n 'Create or update this Factory App’s canonical triage handoff comment on an issue in the active repository. Use this for every marked pending or final Factory triage handoff; never use gh to create or edit that handoff.',\n inputSchema: triageCommentInputSchema,\n execute: async input => upsertFactoryTriageComment(requestContext, input, github),\n }),\n github_subscribe_pr: createTool({\n id: 'github_subscribe_pr',\n description:\n 'Subscribe this thread to GitHub pull request activity. You usually do not need this tool: successful gh pr create commands subscribe automatically. Use it for an existing PR or to recover when automatic subscription did not occur. Closed or merged PRs are unsubscribed automatically. Accepts a PR number or canonical URL for the active project.',\n inputSchema: pullRequestInputSchema,\n execute: async ({ pullRequest }) => {\n const number = await subscribeCurrentSessionToPullRequest(requestContext, pullRequest, 'explicit-tool', github);\n return { subscribed: true, pullRequestNumber: number };\n },\n }),\n github_unsubscribe_pr: createTool({\n id: 'github_unsubscribe_pr',\n description:\n 'Manually unsubscribe this thread from GitHub pull request activity. You usually do not need this tool because closed or merged PRs are unsubscribed automatically. Use it to stop notifications before then. Accepts a PR number or canonical URL for the active project.',\n inputSchema: pullRequestInputSchema,\n execute: async ({ pullRequest }) => {\n const number = await unsubscribeCurrentSessionFromPullRequest(requestContext, pullRequest, github);\n return { subscribed: false, pullRequestNumber: number };\n },\n }),\n };\n}\n\nconst GITHUB_PULL_REQUEST_URL = /^https:\\/\\/github\\.com\\/[^\\s/]+\\/[^\\s/]+\\/pull\\/\\d+\\/?$/;\n\nexport function parseCreatedPullRequest(context: {\n toolName: string;\n input: unknown;\n output?: unknown;\n error?: unknown;\n}) {\n if (context.error) return undefined;\n // The provider-neutral change-request tool reports the created PR directly;\n // subscribe on it exactly as on a successful `gh pr create`.\n if (context.toolName === 'source_control_create_change_request') {\n const url = (context.output as { url?: unknown } | undefined)?.url;\n return typeof url === 'string' && GITHUB_PULL_REQUEST_URL.test(url) ? url.replace(/\\/$/, '') : undefined;\n }\n if (context.toolName !== 'execute_command') return undefined;\n const command = (context.input as { command?: unknown } | undefined)?.command;\n if (typeof command !== 'string' || !runsPullRequestCreate(command)) return undefined;\n const output = context.output as { stdout?: unknown; result?: unknown } | undefined;\n const stdout = typeof context.output === 'string' ? context.output : (output?.stdout ?? output?.result);\n if (typeof stdout !== 'string') return undefined;\n const urls = stdout.match(/https:\\/\\/github\\.com\\/[^\\s/]+\\/[^\\s/]+\\/pull\\/\\d+/g) ?? [];\n return urls.length === 1 ? urls[0] : undefined;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"subscriptions.d.ts","sourceRoot":"","sources":["../../../src/integrations/github/subscriptions.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"subscriptions.d.ts","sourceRoot":"","sources":["../../../src/integrations/github/subscriptions.ts"],"names":[],"mappings":"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"}
@@ -40,7 +40,9 @@ async function unsubscribeFromPullRequest(input, storage) {
40
40
  await Promise.all(rows.filter((row) => sameSession(row, input)).map((row) => storage.subscriptions.delete(row.id)));
41
41
  }
42
42
  async function listPullRequestSubscriptionsForThread(input, storage) {
43
- return (await storage.subscriptions.listByThread(input.resourceId, input.threadId)).filter((row) => row.orgId === input.orgId && row.resourceId === input.resourceId && row.threadId === input.threadId && (row.sessionScope ?? "") === (input.sessionScope ?? ""));
43
+ const matching = (await storage.subscriptions.listByThread(input.resourceId, input.threadId)).filter((row) => row.orgId === input.orgId && row.resourceId === input.resourceId && row.threadId === input.threadId && (row.sessionScope ?? "") === (input.sessionScope ?? ""));
44
+ if (matching.length > 0 || input.sessionScope) return matching;
45
+ return (await storage.subscriptions.listBySession(input.resourceId)).filter((row) => row.orgId === input.orgId && row.threadId === input.threadId && !row.sessionScope);
44
46
  }
45
47
  async function listPullRequestSubscriptions(input, storage) {
46
48
  return (await storage.subscriptions.listByTarget(changeRequestTargetKey(input))).filter((row) => row.orgId === input.orgId && row.status === "open");
@@ -1 +1 @@
1
- {"version":3,"file":"subscriptions.js","names":[],"sources":["../../../src/integrations/github/subscriptions.ts"],"sourcesContent":["import type { IntegrationStorageHandle, IntegrationSubscription } from '../../storage/domains/integrations/base.js';\n\nexport type GithubSignalSubscriptionSource = 'auto-gh-pr-create' | 'factory-pr-create' | 'explicit-tool';\nexport type GithubSignalSubscriptionStatus = 'open' | 'closed' | 'merged';\n\nexport interface GithubSignalSubscriptionData {\n installationExternalId: string;\n projectRepositoryId: string;\n repositoryExternalId: string;\n repositorySlug: string;\n changeRequestId: string;\n ownerId: string;\n source: GithubSignalSubscriptionSource;\n subscribedByUserId: string | null;\n}\n\nexport type GithubSignalSubscriptionRow = IntegrationSubscription<GithubSignalSubscriptionData>;\nexport type GithubSubscriptionStorage = IntegrationStorageHandle<\n Record<string, unknown>,\n Record<string, unknown>,\n GithubSignalSubscriptionData\n>;\n\nexport interface SubscribeToPullRequestInput {\n orgId: string;\n installationExternalId: string;\n projectRepositoryId: string;\n repositoryExternalId: string;\n repositorySlug: string;\n changeRequestId: string;\n sessionId: string;\n ownerId: string;\n resourceId: string;\n threadId: string;\n sessionScope?: string;\n source: GithubSignalSubscriptionSource;\n subscribedByUserId?: string;\n}\n\nexport interface ThreadSubscriptionTarget {\n orgId: string;\n resourceId: string;\n threadId: string;\n sessionScope?: string;\n}\n\nexport interface PullRequestSubscriptionTarget {\n orgId: string;\n installationExternalId: string;\n repositoryExternalId: string;\n changeRequestId: string;\n}\n\nexport type GithubWebhookPullRequestTarget = Omit<PullRequestSubscriptionTarget, 'orgId'>;\n\nexport function changeRequestTargetKey(input: GithubWebhookPullRequestTarget): string {\n return `change-request:${input.installationExternalId}:${input.repositoryExternalId}:${input.changeRequestId}`;\n}\n\nfunction sameSession(row: GithubSignalSubscriptionRow, input: SubscribeToPullRequestInput): boolean {\n return (\n row.orgId === input.orgId &&\n row.sessionId === input.sessionId &&\n row.resourceId === input.resourceId &&\n row.threadId === input.threadId &&\n (row.sessionScope ?? '') === (input.sessionScope ?? '')\n );\n}\n\nexport async function subscribeToPullRequest(\n input: SubscribeToPullRequestInput,\n storage: GithubSubscriptionStorage,\n): Promise<GithubSignalSubscriptionRow> {\n const targetKey = changeRequestTargetKey(input);\n const existing = (await storage.subscriptions.listByTarget(targetKey)).find(row => sameSession(row, input));\n if (existing) {\n if (existing.status !== 'open') await storage.subscriptions.updateStatus(existing.id, 'open');\n return { ...existing, status: 'open' };\n }\n\n return storage.subscriptions.create({\n orgId: input.orgId,\n targetKey,\n sessionId: input.sessionId,\n resourceId: input.resourceId,\n threadId: input.threadId,\n sessionScope: input.sessionScope ?? '',\n status: 'open',\n data: {\n installationExternalId: input.installationExternalId,\n projectRepositoryId: input.projectRepositoryId,\n repositoryExternalId: input.repositoryExternalId,\n repositorySlug: input.repositorySlug,\n changeRequestId: input.changeRequestId,\n ownerId: input.ownerId,\n source: input.source,\n subscribedByUserId: input.subscribedByUserId ?? null,\n },\n });\n}\n\nexport async function unsubscribeFromPullRequest(\n input: SubscribeToPullRequestInput,\n storage: GithubSubscriptionStorage,\n): Promise<void> {\n const rows = await storage.subscriptions.listByTarget(changeRequestTargetKey(input));\n await Promise.all(rows.filter(row => sameSession(row, input)).map(row => storage.subscriptions.delete(row.id)));\n}\n\nexport async function listPullRequestSubscriptionsForThread(\n input: ThreadSubscriptionTarget,\n storage: GithubSubscriptionStorage,\n): Promise<GithubSignalSubscriptionRow[]> {\n const rows = await storage.subscriptions.listByThread(input.resourceId, input.threadId);\n return rows.filter(\n row =>\n row.orgId === input.orgId &&\n row.resourceId === input.resourceId &&\n row.threadId === input.threadId &&\n (row.sessionScope ?? '') === (input.sessionScope ?? ''),\n );\n}\n\nexport async function listPullRequestSubscriptions(\n input: PullRequestSubscriptionTarget,\n storage: GithubSubscriptionStorage,\n): Promise<GithubSignalSubscriptionRow[]> {\n const rows = await storage.subscriptions.listByTarget(changeRequestTargetKey(input));\n return rows.filter(row => row.orgId === input.orgId && row.status === 'open');\n}\n\nexport async function listPullRequestSubscriptionsForWebhook(\n input: GithubWebhookPullRequestTarget,\n options: { includeTerminal?: boolean } | undefined,\n storage: GithubSubscriptionStorage,\n): Promise<GithubSignalSubscriptionRow[]> {\n const rows = await storage.subscriptions.listByTarget(changeRequestTargetKey(input));\n return options?.includeTerminal ? rows : rows.filter(row => row.status === 'open');\n}\n\nexport function retirePullRequestSubscription(\n id: string,\n status: GithubSignalSubscriptionStatus,\n storage: GithubSubscriptionStorage,\n): Promise<void> {\n return storage.subscriptions.updateStatus(id, status);\n}\n\nexport async function retirePullRequestSubscriptions(\n input: PullRequestSubscriptionTarget,\n storage: GithubSubscriptionStorage,\n): Promise<void> {\n const rows = await listPullRequestSubscriptions(input, storage);\n await Promise.all(rows.map(row => storage.subscriptions.updateStatus(row.id, 'closed')));\n}\n"],"mappings":";AAuDA,SAAgB,uBAAuB,OAA+C;CACpF,OAAO,kBAAkB,MAAM,uBAAuB,GAAG,MAAM,qBAAqB,GAAG,MAAM;AAC/F;AAEA,SAAS,YAAY,KAAkC,OAA6C;CAClG,OACE,IAAI,UAAU,MAAM,SACpB,IAAI,cAAc,MAAM,aACxB,IAAI,eAAe,MAAM,cACzB,IAAI,aAAa,MAAM,aACtB,IAAI,gBAAgB,SAAS,MAAM,gBAAgB;AAExD;AAEA,eAAsB,uBACpB,OACA,SACsC;CACtC,MAAM,YAAY,uBAAuB,KAAK;CAC9C,MAAM,YAAY,MAAM,QAAQ,cAAc,aAAa,SAAS,EAAA,CAAG,MAAK,QAAO,YAAY,KAAK,KAAK,CAAC;CAC1G,IAAI,UAAU;EACZ,IAAI,SAAS,WAAW,QAAQ,MAAM,QAAQ,cAAc,aAAa,SAAS,IAAI,MAAM;EAC5F,OAAO;GAAE,GAAG;GAAU,QAAQ;EAAO;CACvC;CAEA,OAAO,QAAQ,cAAc,OAAO;EAClC,OAAO,MAAM;EACb;EACA,WAAW,MAAM;EACjB,YAAY,MAAM;EAClB,UAAU,MAAM;EAChB,cAAc,MAAM,gBAAgB;EACpC,QAAQ;EACR,MAAM;GACJ,wBAAwB,MAAM;GAC9B,qBAAqB,MAAM;GAC3B,sBAAsB,MAAM;GAC5B,gBAAgB,MAAM;GACtB,iBAAiB,MAAM;GACvB,SAAS,MAAM;GACf,QAAQ,MAAM;GACd,oBAAoB,MAAM,sBAAsB;EAClD;CACF,CAAC;AACH;AAEA,eAAsB,2BACpB,OACA,SACe;CACf,MAAM,OAAO,MAAM,QAAQ,cAAc,aAAa,uBAAuB,KAAK,CAAC;CACnF,MAAM,QAAQ,IAAI,KAAK,QAAO,QAAO,YAAY,KAAK,KAAK,CAAC,CAAC,CAAC,KAAI,QAAO,QAAQ,cAAc,OAAO,IAAI,EAAE,CAAC,CAAC;AAChH;AAEA,eAAsB,sCACpB,OACA,SACwC;CAExC,QAAO,MADY,QAAQ,cAAc,aAAa,MAAM,YAAY,MAAM,QAAQ,EAAA,CAC1E,QACV,QACE,IAAI,UAAU,MAAM,SACpB,IAAI,eAAe,MAAM,cACzB,IAAI,aAAa,MAAM,aACtB,IAAI,gBAAgB,SAAS,MAAM,gBAAgB,GACxD;AACF;AAEA,eAAsB,6BACpB,OACA,SACwC;CAExC,QAAO,MADY,QAAQ,cAAc,aAAa,uBAAuB,KAAK,CAAC,EAAA,CACvE,QAAO,QAAO,IAAI,UAAU,MAAM,SAAS,IAAI,WAAW,MAAM;AAC9E;AAEA,eAAsB,uCACpB,OACA,SACA,SACwC;CACxC,MAAM,OAAO,MAAM,QAAQ,cAAc,aAAa,uBAAuB,KAAK,CAAC;CACnF,OAAO,SAAS,kBAAkB,OAAO,KAAK,QAAO,QAAO,IAAI,WAAW,MAAM;AACnF;AAEA,SAAgB,8BACd,IACA,QACA,SACe;CACf,OAAO,QAAQ,cAAc,aAAa,IAAI,MAAM;AACtD;AAEA,eAAsB,+BACpB,OACA,SACe;CACf,MAAM,OAAO,MAAM,6BAA6B,OAAO,OAAO;CAC9D,MAAM,QAAQ,IAAI,KAAK,KAAI,QAAO,QAAQ,cAAc,aAAa,IAAI,IAAI,QAAQ,CAAC,CAAC;AACzF"}
1
+ {"version":3,"file":"subscriptions.js","names":[],"sources":["../../../src/integrations/github/subscriptions.ts"],"sourcesContent":["import type { IntegrationStorageHandle, IntegrationSubscription } from '../../storage/domains/integrations/base.js';\n\nexport type GithubSignalSubscriptionSource = 'auto-gh-pr-create' | 'factory-pr-create' | 'explicit-tool';\nexport type GithubSignalSubscriptionStatus = 'open' | 'closed' | 'merged';\n\nexport interface GithubSignalSubscriptionData {\n installationExternalId: string;\n projectRepositoryId: string;\n repositoryExternalId: string;\n repositorySlug: string;\n changeRequestId: string;\n ownerId: string;\n source: GithubSignalSubscriptionSource;\n subscribedByUserId: string | null;\n}\n\nexport type GithubSignalSubscriptionRow = IntegrationSubscription<GithubSignalSubscriptionData>;\nexport type GithubSubscriptionStorage = IntegrationStorageHandle<\n Record<string, unknown>,\n Record<string, unknown>,\n GithubSignalSubscriptionData\n>;\n\nexport interface SubscribeToPullRequestInput {\n orgId: string;\n installationExternalId: string;\n projectRepositoryId: string;\n repositoryExternalId: string;\n repositorySlug: string;\n changeRequestId: string;\n sessionId: string;\n ownerId: string;\n resourceId: string;\n threadId: string;\n sessionScope?: string;\n source: GithubSignalSubscriptionSource;\n subscribedByUserId?: string;\n}\n\nexport interface ThreadSubscriptionTarget {\n orgId: string;\n resourceId: string;\n threadId: string;\n sessionScope?: string;\n}\n\nexport interface PullRequestSubscriptionTarget {\n orgId: string;\n installationExternalId: string;\n repositoryExternalId: string;\n changeRequestId: string;\n}\n\nexport type GithubWebhookPullRequestTarget = Omit<PullRequestSubscriptionTarget, 'orgId'>;\n\nexport function changeRequestTargetKey(input: GithubWebhookPullRequestTarget): string {\n return `change-request:${input.installationExternalId}:${input.repositoryExternalId}:${input.changeRequestId}`;\n}\n\nfunction sameSession(row: GithubSignalSubscriptionRow, input: SubscribeToPullRequestInput): boolean {\n return (\n row.orgId === input.orgId &&\n row.sessionId === input.sessionId &&\n row.resourceId === input.resourceId &&\n row.threadId === input.threadId &&\n (row.sessionScope ?? '') === (input.sessionScope ?? '')\n );\n}\n\nexport async function subscribeToPullRequest(\n input: SubscribeToPullRequestInput,\n storage: GithubSubscriptionStorage,\n): Promise<GithubSignalSubscriptionRow> {\n const targetKey = changeRequestTargetKey(input);\n const existing = (await storage.subscriptions.listByTarget(targetKey)).find(row => sameSession(row, input));\n if (existing) {\n if (existing.status !== 'open') await storage.subscriptions.updateStatus(existing.id, 'open');\n return { ...existing, status: 'open' };\n }\n\n return storage.subscriptions.create({\n orgId: input.orgId,\n targetKey,\n sessionId: input.sessionId,\n resourceId: input.resourceId,\n threadId: input.threadId,\n sessionScope: input.sessionScope ?? '',\n status: 'open',\n data: {\n installationExternalId: input.installationExternalId,\n projectRepositoryId: input.projectRepositoryId,\n repositoryExternalId: input.repositoryExternalId,\n repositorySlug: input.repositorySlug,\n changeRequestId: input.changeRequestId,\n ownerId: input.ownerId,\n source: input.source,\n subscribedByUserId: input.subscribedByUserId ?? null,\n },\n });\n}\n\nexport async function unsubscribeFromPullRequest(\n input: SubscribeToPullRequestInput,\n storage: GithubSubscriptionStorage,\n): Promise<void> {\n const rows = await storage.subscriptions.listByTarget(changeRequestTargetKey(input));\n await Promise.all(rows.filter(row => sameSession(row, input)).map(row => storage.subscriptions.delete(row.id)));\n}\n\nexport async function listPullRequestSubscriptionsForThread(\n input: ThreadSubscriptionTarget,\n storage: GithubSubscriptionStorage,\n): Promise<GithubSignalSubscriptionRow[]> {\n const rows = await storage.subscriptions.listByThread(input.resourceId, input.threadId);\n const matching = rows.filter(\n row =>\n row.orgId === input.orgId &&\n row.resourceId === input.resourceId &&\n row.threadId === input.threadId &&\n (row.sessionScope ?? '') === (input.sessionScope ?? ''),\n );\n if (matching.length > 0 || input.sessionScope) return matching;\n // A user session is addressed by its own id (its thread is its session), but\n // its subscriptions were recorded under the Factory project that owns the\n // repository. Answer those rows for the session that created them.\n const owned = await storage.subscriptions.listBySession(input.resourceId);\n return owned.filter(row => row.orgId === input.orgId && row.threadId === input.threadId && !row.sessionScope);\n}\n\nexport async function listPullRequestSubscriptions(\n input: PullRequestSubscriptionTarget,\n storage: GithubSubscriptionStorage,\n): Promise<GithubSignalSubscriptionRow[]> {\n const rows = await storage.subscriptions.listByTarget(changeRequestTargetKey(input));\n return rows.filter(row => row.orgId === input.orgId && row.status === 'open');\n}\n\nexport async function listPullRequestSubscriptionsForWebhook(\n input: GithubWebhookPullRequestTarget,\n options: { includeTerminal?: boolean } | undefined,\n storage: GithubSubscriptionStorage,\n): Promise<GithubSignalSubscriptionRow[]> {\n const rows = await storage.subscriptions.listByTarget(changeRequestTargetKey(input));\n return options?.includeTerminal ? rows : rows.filter(row => row.status === 'open');\n}\n\nexport function retirePullRequestSubscription(\n id: string,\n status: GithubSignalSubscriptionStatus,\n storage: GithubSubscriptionStorage,\n): Promise<void> {\n return storage.subscriptions.updateStatus(id, status);\n}\n\nexport async function retirePullRequestSubscriptions(\n input: PullRequestSubscriptionTarget,\n storage: GithubSubscriptionStorage,\n): Promise<void> {\n const rows = await listPullRequestSubscriptions(input, storage);\n await Promise.all(rows.map(row => storage.subscriptions.updateStatus(row.id, 'closed')));\n}\n"],"mappings":";AAuDA,SAAgB,uBAAuB,OAA+C;CACpF,OAAO,kBAAkB,MAAM,uBAAuB,GAAG,MAAM,qBAAqB,GAAG,MAAM;AAC/F;AAEA,SAAS,YAAY,KAAkC,OAA6C;CAClG,OACE,IAAI,UAAU,MAAM,SACpB,IAAI,cAAc,MAAM,aACxB,IAAI,eAAe,MAAM,cACzB,IAAI,aAAa,MAAM,aACtB,IAAI,gBAAgB,SAAS,MAAM,gBAAgB;AAExD;AAEA,eAAsB,uBACpB,OACA,SACsC;CACtC,MAAM,YAAY,uBAAuB,KAAK;CAC9C,MAAM,YAAY,MAAM,QAAQ,cAAc,aAAa,SAAS,EAAA,CAAG,MAAK,QAAO,YAAY,KAAK,KAAK,CAAC;CAC1G,IAAI,UAAU;EACZ,IAAI,SAAS,WAAW,QAAQ,MAAM,QAAQ,cAAc,aAAa,SAAS,IAAI,MAAM;EAC5F,OAAO;GAAE,GAAG;GAAU,QAAQ;EAAO;CACvC;CAEA,OAAO,QAAQ,cAAc,OAAO;EAClC,OAAO,MAAM;EACb;EACA,WAAW,MAAM;EACjB,YAAY,MAAM;EAClB,UAAU,MAAM;EAChB,cAAc,MAAM,gBAAgB;EACpC,QAAQ;EACR,MAAM;GACJ,wBAAwB,MAAM;GAC9B,qBAAqB,MAAM;GAC3B,sBAAsB,MAAM;GAC5B,gBAAgB,MAAM;GACtB,iBAAiB,MAAM;GACvB,SAAS,MAAM;GACf,QAAQ,MAAM;GACd,oBAAoB,MAAM,sBAAsB;EAClD;CACF,CAAC;AACH;AAEA,eAAsB,2BACpB,OACA,SACe;CACf,MAAM,OAAO,MAAM,QAAQ,cAAc,aAAa,uBAAuB,KAAK,CAAC;CACnF,MAAM,QAAQ,IAAI,KAAK,QAAO,QAAO,YAAY,KAAK,KAAK,CAAC,CAAC,CAAC,KAAI,QAAO,QAAQ,cAAc,OAAO,IAAI,EAAE,CAAC,CAAC;AAChH;AAEA,eAAsB,sCACpB,OACA,SACwC;CAExC,MAAM,YAAW,MADE,QAAQ,cAAc,aAAa,MAAM,YAAY,MAAM,QAAQ,EAAA,CAChE,QACpB,QACE,IAAI,UAAU,MAAM,SACpB,IAAI,eAAe,MAAM,cACzB,IAAI,aAAa,MAAM,aACtB,IAAI,gBAAgB,SAAS,MAAM,gBAAgB,GACxD;CACA,IAAI,SAAS,SAAS,KAAK,MAAM,cAAc,OAAO;CAKtD,QAAO,MADa,QAAQ,cAAc,cAAc,MAAM,UAAU,EAAA,CAC3D,QAAO,QAAO,IAAI,UAAU,MAAM,SAAS,IAAI,aAAa,MAAM,YAAY,CAAC,IAAI,YAAY;AAC9G;AAEA,eAAsB,6BACpB,OACA,SACwC;CAExC,QAAO,MADY,QAAQ,cAAc,aAAa,uBAAuB,KAAK,CAAC,EAAA,CACvE,QAAO,QAAO,IAAI,UAAU,MAAM,SAAS,IAAI,WAAW,MAAM;AAC9E;AAEA,eAAsB,uCACpB,OACA,SACA,SACwC;CACxC,MAAM,OAAO,MAAM,QAAQ,cAAc,aAAa,uBAAuB,KAAK,CAAC;CACnF,OAAO,SAAS,kBAAkB,OAAO,KAAK,QAAO,QAAO,IAAI,WAAW,MAAM;AACnF;AAEA,SAAgB,8BACd,IACA,QACA,SACe;CACf,OAAO,QAAQ,cAAc,aAAa,IAAI,MAAM;AACtD;AAEA,eAAsB,+BACpB,OACA,SACe;CACf,MAAM,OAAO,MAAM,6BAA6B,OAAO,OAAO;CAC9D,MAAM,QAAQ,IAAI,KAAK,KAAI,QAAO,QAAQ,cAAc,aAAa,IAAI,IAAI,QAAQ,CAAC,CAAC;AACzF"}
@@ -1 +1 @@
1
- {"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../../../src/integrations/slack/integration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AAE/D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,6CAA6C,CAAC;AACzF,OAAO,KAAK,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAMhG;;;;;GAKG;AACH,MAAM,WAAW,sBAAsB;IACrC,2DAA2D;IAC3D,aAAa,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;;;;;;;OAaG;IACH,cAAc,CAAC,EAAE,yBAAyB,CAAC;CAC5C;AA8BD,qBAAa,gBAAiB,YAAW,kBAAkB;;IACzD,QAAQ,CAAC,EAAE,WAAW;IACtB;;;;OAIG;IACH,QAAQ,CAAC,yBAAyB,QAAQ;gBAU9B,MAAM,EAAE,sBAAsB;IAS1C,QAAQ,CAAC,GAAG,EAAE,kBAAkB,GAAG,qBAAqB;IA2BxD,aAAa,CAAC,GAAG,EAAE,kBAAkB,GAAG,qBAAqB;IAI7D,MAAM,CAAC,GAAG,EAAE,kBAAkB,GAAG,QAAQ,EAAE;IAc3C,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CASvC"}
1
+ {"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../../../src/integrations/slack/integration.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AAE/D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,6CAA6C,CAAC;AACzF,OAAO,KAAK,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAMhG;;;;;GAKG;AACH,MAAM,WAAW,sBAAsB;IACrC,2DAA2D;IAC3D,aAAa,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;;;;;;;OAaG;IACH,cAAc,CAAC,EAAE,yBAAyB,CAAC;CAC5C;AAyBD,qBAAa,gBAAiB,YAAW,kBAAkB;;IACzD,QAAQ,CAAC,EAAE,WAAW;IACtB;;;;OAIG;IACH,QAAQ,CAAC,yBAAyB,QAAQ;gBAU9B,MAAM,EAAE,sBAAsB;IAS1C,QAAQ,CAAC,GAAG,EAAE,kBAAkB,GAAG,qBAAqB;IA2BxD,aAAa,CAAC,GAAG,EAAE,kBAAkB,GAAG,qBAAqB;IAI7D,MAAM,CAAC,GAAG,EAAE,kBAAkB,GAAG,QAAQ,EAAE;IAc3C,WAAW,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CASvC"}
@@ -2,18 +2,14 @@ import { createSlackConnectRoutes } from "./connect-route.js";
2
2
  import { SlackFeedPublisher } from "./feed-publisher.js";
3
3
  import { createSlackChannelsConfig } from "./slack.js";
4
4
  //#region src/integrations/slack/integration.ts
5
- const factoryTypingStatus = (chunk, ctx) => {
5
+ const factoryTypingStatus = (chunk) => {
6
6
  switch (chunk.type) {
7
- case "reasoning-delta": return `is thinking${nextDots(ctx.currentStatus)}`;
7
+ case "reasoning-delta": return "is thinking...";
8
8
  case "text-delta": return "is typing...";
9
- case "tool-call": return `is working${nextDots(ctx.currentStatus)}`;
9
+ case "tool-call": return "is working...";
10
10
  default: return;
11
11
  }
12
12
  };
13
- function nextDots(currentStatus) {
14
- const dotCount = currentStatus?.match(/\.*$/)?.[0].length ?? 1;
15
- return ".".repeat(dotCount >= 5 ? 2 : dotCount + 1);
16
- }
17
13
  /**
18
14
  * Drops keys the caller set to `undefined` so spreading the overrides cannot
19
15
  * erase the Factory defaults they sit on top of.
@@ -1 +1 @@
1
- {"version":3,"file":"integration.js","names":["#config","#repoBackedSessions"],"sources":["../../../src/integrations/slack/integration.ts"],"sourcesContent":["/**\n * `SlackIntegration` — Slack as a `FactoryIntegration`.\n *\n * Slack contributes two things to a factory: the chat channels that carry\n * inbound messages into agent runs (`channels()`), and the browser-facing\n * account-link routes that bind a Slack sender to a Mastra tenant (`routes()`).\n * Both used to be assembled by hand in the deploy entry, which had to reach\n * into the prepared agent controller by string key, resolve storage domains\n * itself, mint its own state signer, and splice routes onto the factory's\n * assembled server config. Implementing the integration interface instead means\n * the factory does all of that the same way it already does for GitHub and\n * Linear, and the entry's job shrinks to reading Slack's env vars once.\n *\n * Slack ships as a factory built-in alongside GitHub and Linear (it originally\n * lived in `mastracode/web` to keep `@mastra/slack`/`chat` out of this package;\n * the team reversed that so `create-factory` consumers get Slack channels out\n * of the box). The integration interface remains open — third parties still add\n * capabilities by implementing `FactoryIntegration` from outside the package.\n */\n\nimport type { TypingStatusFn } from '@mastra/core/channels';\nimport type { ApiRoute } from '@mastra/core/server';\nimport type { SlackAdapterChannelConfig } from '@mastra/slack';\n\nimport type { WorkItemFeedPublisher } from '../../storage/domains/comments/feed-sync.js';\nimport type { FactoryChannelsConfig, FactoryIntegration, IntegrationContext } from '../base.js';\n\nimport { createSlackConnectRoutes } from './connect-route.js';\nimport { SlackFeedPublisher } from './feed-publisher.js';\nimport { createSlackChannelsConfig } from './slack.js';\n\n/**\n * Slack app credentials, read from env ONCE by the deploy entry. `signingSecret`\n * is required because the Slack adapter validates it at construction — an\n * integration constructed without it would throw during `prepare()` rather than\n * reporting itself unconfigured.\n */\nexport interface SlackIntegrationConfig {\n /** Verifies inbound Slack request signatures. Required. */\n signingSecret: string;\n /** Bot token used to post replies and ephemeral cards. */\n botToken?: string;\n /**\n * OAuth client credentials. Required for account linking: \"Sign in with\n * Slack\" (OIDC) is the only flow that writes a link, so without these the\n * settings surface reports `canConnect: false` and no sender can link.\n */\n clientId?: string;\n clientSecret?: string;\n /**\n * HTTPS origin Slack redirects back to. Slack requires HTTPS, so locally this\n * is the tunnel origin rather than the app's own public URL.\n */\n oidcRedirectBaseUrl?: string;\n /** SPA origin the post-connect redirect returns to. */\n uiOrigin?: string;\n /**\n * Overrides for the Slack channel adapter entry.\n *\n * @example\n * ```ts\n * new SlackIntegration({\n * signingSecret,\n * adapterOptions: {\n * streaming: true,\n * toolDisplay: 'grouped',\n * },\n * });\n * ```\n */\n adapterOptions?: SlackAdapterChannelConfig;\n}\n\nconst factoryTypingStatus: TypingStatusFn = (chunk, ctx) => {\n switch (chunk.type) {\n case 'reasoning-delta':\n return `is thinking${nextDots(ctx.currentStatus)}`;\n case 'text-delta':\n return 'is typing...';\n case 'tool-call':\n return `is working${nextDots(ctx.currentStatus)}`;\n default:\n return undefined;\n }\n};\n\nfunction nextDots(currentStatus: string | undefined): string {\n const dotCount = currentStatus?.match(/\\.*$/)?.[0].length ?? 1;\n return '.'.repeat(dotCount >= 5 ? 2 : dotCount + 1);\n}\n\n/**\n * Drops keys the caller set to `undefined` so spreading the overrides cannot\n * erase the Factory defaults they sit on top of.\n */\nfunction adapterOverrides(options: SlackAdapterChannelConfig | undefined): SlackAdapterChannelConfig {\n return Object.fromEntries(\n Object.entries(options ?? {}).filter(([, value]) => value !== undefined),\n ) as SlackAdapterChannelConfig;\n}\n\nexport class SlackIntegration implements FactoryIntegration {\n readonly id = 'slack';\n /**\n * The OIDC connect flow round-trips a signed `state` through Slack, so the\n * replica handling the callback must be able to verify a state a different\n * replica signed.\n */\n readonly requiresStableStateSigner = true;\n\n readonly #config: SlackIntegrationConfig;\n /**\n * Whether `channels()` found a source-control owner on the context and wired\n * repo-backed sessions. Set at the channels() attach path, which runs once at\n * boot before diagnostics are served.\n */\n #repoBackedSessions = false;\n\n constructor(config: SlackIntegrationConfig) {\n if (!config.signingSecret) {\n throw new Error(\n \"SlackIntegration: 'signingSecret' is required — Slack cannot verify inbound requests without it.\",\n );\n }\n this.#config = config;\n }\n\n channels(ctx: IntegrationContext): FactoryChannelsConfig {\n // Repo-backed sessions select the source-control provider from the linked\n // repository on each Factory project.\n const sourceControls =\n ctx.storage.sourceControls ?? (ctx.storage.sourceControlOwner ? [ctx.storage.sourceControlOwner] : []);\n this.#repoBackedSessions = sourceControls.length > 0;\n return createSlackChannelsConfig({\n slack: {\n clientId: this.#config.clientId,\n clientSecret: this.#config.clientSecret,\n signingSecret: this.#config.signingSecret,\n botToken: this.#config.botToken,\n },\n accountLinks: ctx.storage.channelIdentity,\n projects: ctx.storage.projects,\n sourceControls,\n memorySettings: ctx.storage.memorySettings,\n workItems: ctx.runtime?.workItems,\n feed: ctx.feed,\n adapterOptions: {\n toolDisplay: 'hidden',\n typingStatus: factoryTypingStatus,\n ...adapterOverrides(this.#config.adapterOptions),\n },\n });\n }\n\n feedPublisher(ctx: IntegrationContext): WorkItemFeedPublisher {\n return new SlackFeedPublisher({ controller: ctx.controller });\n }\n\n routes(ctx: IntegrationContext): ApiRoute[] {\n const { clientId, clientSecret, oidcRedirectBaseUrl, uiOrigin } = this.#config;\n return createSlackConnectRoutes({\n auth: ctx.auth,\n accountLinks: ctx.storage.channelIdentity,\n tenantStateSigner: ctx.stateSigner,\n oidc:\n clientId && clientSecret && oidcRedirectBaseUrl\n ? { clientId, clientSecret, redirectBaseUrl: oidcRedirectBaseUrl, uiOrigin }\n : undefined,\n projects: ctx.storage.projects,\n });\n }\n\n diagnostics(): Record<string, unknown> {\n const { clientId, clientSecret, botToken, oidcRedirectBaseUrl } = this.#config;\n return {\n configured: true,\n botTokenConfigured: Boolean(botToken),\n oidcConfigured: Boolean(clientId && clientSecret && oidcRedirectBaseUrl),\n repoBackedSessions: this.#repoBackedSessions,\n };\n }\n}\n"],"mappings":";;;;AAyEA,MAAM,uBAAuC,OAAO,QAAQ;CAC1D,QAAQ,MAAM,MAAd;EACE,KAAK,mBACH,OAAO,cAAc,SAAS,IAAI,aAAa;EACjD,KAAK,cACH,OAAO;EACT,KAAK,aACH,OAAO,aAAa,SAAS,IAAI,aAAa;EAChD,SACE;CACJ;AACF;AAEA,SAAS,SAAS,eAA2C;CAC3D,MAAM,WAAW,eAAe,MAAM,MAAM,CAAC,GAAG,EAAE,CAAC,UAAU;CAC7D,OAAO,IAAI,OAAO,YAAY,IAAI,IAAI,WAAW,CAAC;AACpD;;;;;AAMA,SAAS,iBAAiB,SAA2E;CACnG,OAAO,OAAO,YACZ,OAAO,QAAQ,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,GAAG,WAAW,UAAU,KAAA,CAAS,CACzE;AACF;AAEA,IAAa,mBAAb,MAA4D;CAC1D,KAAc;;;;;;CAMd,4BAAqC;CAErC;;;;;;CAMA,sBAAsB;CAEtB,YAAY,QAAgC;EAC1C,IAAI,CAAC,OAAO,eACV,MAAM,IAAI,MACR,kGACF;EAEF,KAAKA,UAAU;CACjB;CAEA,SAAS,KAAgD;EAGvD,MAAM,iBACJ,IAAI,QAAQ,mBAAmB,IAAI,QAAQ,qBAAqB,CAAC,IAAI,QAAQ,kBAAkB,IAAI,CAAC;EACtG,KAAKC,sBAAsB,eAAe,SAAS;EACnD,OAAO,0BAA0B;GAC/B,OAAO;IACL,UAAU,KAAKD,QAAQ;IACvB,cAAc,KAAKA,QAAQ;IAC3B,eAAe,KAAKA,QAAQ;IAC5B,UAAU,KAAKA,QAAQ;GACzB;GACA,cAAc,IAAI,QAAQ;GAC1B,UAAU,IAAI,QAAQ;GACtB;GACA,gBAAgB,IAAI,QAAQ;GAC5B,WAAW,IAAI,SAAS;GACxB,MAAM,IAAI;GACV,gBAAgB;IACd,aAAa;IACb,cAAc;IACd,GAAG,iBAAiB,KAAKA,QAAQ,cAAc;GACjD;EACF,CAAC;CACH;CAEA,cAAc,KAAgD;EAC5D,OAAO,IAAI,mBAAmB,EAAE,YAAY,IAAI,WAAW,CAAC;CAC9D;CAEA,OAAO,KAAqC;EAC1C,MAAM,EAAE,UAAU,cAAc,qBAAqB,aAAa,KAAKA;EACvE,OAAO,yBAAyB;GAC9B,MAAM,IAAI;GACV,cAAc,IAAI,QAAQ;GAC1B,mBAAmB,IAAI;GACvB,MACE,YAAY,gBAAgB,sBACxB;IAAE;IAAU;IAAc,iBAAiB;IAAqB;GAAS,IACzE,KAAA;GACN,UAAU,IAAI,QAAQ;EACxB,CAAC;CACH;CAEA,cAAuC;EACrC,MAAM,EAAE,UAAU,cAAc,UAAU,wBAAwB,KAAKA;EACvE,OAAO;GACL,YAAY;GACZ,oBAAoB,QAAQ,QAAQ;GACpC,gBAAgB,QAAQ,YAAY,gBAAgB,mBAAmB;GACvE,oBAAoB,KAAKC;EAC3B;CACF;AACF"}
1
+ {"version":3,"file":"integration.js","names":["#config","#repoBackedSessions"],"sources":["../../../src/integrations/slack/integration.ts"],"sourcesContent":["/**\n * `SlackIntegration` — Slack as a `FactoryIntegration`.\n *\n * Slack contributes two things to a factory: the chat channels that carry\n * inbound messages into agent runs (`channels()`), and the browser-facing\n * account-link routes that bind a Slack sender to a Mastra tenant (`routes()`).\n * Both used to be assembled by hand in the deploy entry, which had to reach\n * into the prepared agent controller by string key, resolve storage domains\n * itself, mint its own state signer, and splice routes onto the factory's\n * assembled server config. Implementing the integration interface instead means\n * the factory does all of that the same way it already does for GitHub and\n * Linear, and the entry's job shrinks to reading Slack's env vars once.\n *\n * Slack ships as a factory built-in alongside GitHub and Linear (it originally\n * lived in `mastracode/web` to keep `@mastra/slack`/`chat` out of this package;\n * the team reversed that so `create-factory` consumers get Slack channels out\n * of the box). The integration interface remains open — third parties still add\n * capabilities by implementing `FactoryIntegration` from outside the package.\n */\n\nimport type { TypingStatusFn } from '@mastra/core/channels';\nimport type { ApiRoute } from '@mastra/core/server';\nimport type { SlackAdapterChannelConfig } from '@mastra/slack';\n\nimport type { WorkItemFeedPublisher } from '../../storage/domains/comments/feed-sync.js';\nimport type { FactoryChannelsConfig, FactoryIntegration, IntegrationContext } from '../base.js';\n\nimport { createSlackConnectRoutes } from './connect-route.js';\nimport { SlackFeedPublisher } from './feed-publisher.js';\nimport { createSlackChannelsConfig } from './slack.js';\n\n/**\n * Slack app credentials, read from env ONCE by the deploy entry. `signingSecret`\n * is required because the Slack adapter validates it at construction — an\n * integration constructed without it would throw during `prepare()` rather than\n * reporting itself unconfigured.\n */\nexport interface SlackIntegrationConfig {\n /** Verifies inbound Slack request signatures. Required. */\n signingSecret: string;\n /** Bot token used to post replies and ephemeral cards. */\n botToken?: string;\n /**\n * OAuth client credentials. Required for account linking: \"Sign in with\n * Slack\" (OIDC) is the only flow that writes a link, so without these the\n * settings surface reports `canConnect: false` and no sender can link.\n */\n clientId?: string;\n clientSecret?: string;\n /**\n * HTTPS origin Slack redirects back to. Slack requires HTTPS, so locally this\n * is the tunnel origin rather than the app's own public URL.\n */\n oidcRedirectBaseUrl?: string;\n /** SPA origin the post-connect redirect returns to. */\n uiOrigin?: string;\n /**\n * Overrides for the Slack channel adapter entry.\n *\n * @example\n * ```ts\n * new SlackIntegration({\n * signingSecret,\n * adapterOptions: {\n * streaming: true,\n * toolDisplay: 'grouped',\n * },\n * });\n * ```\n */\n adapterOptions?: SlackAdapterChannelConfig;\n}\n\nconst factoryTypingStatus: TypingStatusFn = chunk => {\n switch (chunk.type) {\n case 'reasoning-delta':\n return 'is thinking...';\n case 'text-delta':\n return 'is typing...';\n case 'tool-call':\n return 'is working...';\n default:\n return undefined;\n }\n};\n\n/**\n * Drops keys the caller set to `undefined` so spreading the overrides cannot\n * erase the Factory defaults they sit on top of.\n */\nfunction adapterOverrides(options: SlackAdapterChannelConfig | undefined): SlackAdapterChannelConfig {\n return Object.fromEntries(\n Object.entries(options ?? {}).filter(([, value]) => value !== undefined),\n ) as SlackAdapterChannelConfig;\n}\n\nexport class SlackIntegration implements FactoryIntegration {\n readonly id = 'slack';\n /**\n * The OIDC connect flow round-trips a signed `state` through Slack, so the\n * replica handling the callback must be able to verify a state a different\n * replica signed.\n */\n readonly requiresStableStateSigner = true;\n\n readonly #config: SlackIntegrationConfig;\n /**\n * Whether `channels()` found a source-control owner on the context and wired\n * repo-backed sessions. Set at the channels() attach path, which runs once at\n * boot before diagnostics are served.\n */\n #repoBackedSessions = false;\n\n constructor(config: SlackIntegrationConfig) {\n if (!config.signingSecret) {\n throw new Error(\n \"SlackIntegration: 'signingSecret' is required — Slack cannot verify inbound requests without it.\",\n );\n }\n this.#config = config;\n }\n\n channels(ctx: IntegrationContext): FactoryChannelsConfig {\n // Repo-backed sessions select the source-control provider from the linked\n // repository on each Factory project.\n const sourceControls =\n ctx.storage.sourceControls ?? (ctx.storage.sourceControlOwner ? [ctx.storage.sourceControlOwner] : []);\n this.#repoBackedSessions = sourceControls.length > 0;\n return createSlackChannelsConfig({\n slack: {\n clientId: this.#config.clientId,\n clientSecret: this.#config.clientSecret,\n signingSecret: this.#config.signingSecret,\n botToken: this.#config.botToken,\n },\n accountLinks: ctx.storage.channelIdentity,\n projects: ctx.storage.projects,\n sourceControls,\n memorySettings: ctx.storage.memorySettings,\n workItems: ctx.runtime?.workItems,\n feed: ctx.feed,\n adapterOptions: {\n toolDisplay: 'hidden',\n typingStatus: factoryTypingStatus,\n ...adapterOverrides(this.#config.adapterOptions),\n },\n });\n }\n\n feedPublisher(ctx: IntegrationContext): WorkItemFeedPublisher {\n return new SlackFeedPublisher({ controller: ctx.controller });\n }\n\n routes(ctx: IntegrationContext): ApiRoute[] {\n const { clientId, clientSecret, oidcRedirectBaseUrl, uiOrigin } = this.#config;\n return createSlackConnectRoutes({\n auth: ctx.auth,\n accountLinks: ctx.storage.channelIdentity,\n tenantStateSigner: ctx.stateSigner,\n oidc:\n clientId && clientSecret && oidcRedirectBaseUrl\n ? { clientId, clientSecret, redirectBaseUrl: oidcRedirectBaseUrl, uiOrigin }\n : undefined,\n projects: ctx.storage.projects,\n });\n }\n\n diagnostics(): Record<string, unknown> {\n const { clientId, clientSecret, botToken, oidcRedirectBaseUrl } = this.#config;\n return {\n configured: true,\n botTokenConfigured: Boolean(botToken),\n oidcConfigured: Boolean(clientId && clientSecret && oidcRedirectBaseUrl),\n repoBackedSessions: this.#repoBackedSessions,\n };\n }\n}\n"],"mappings":";;;;AAyEA,MAAM,uBAAsC,UAAS;CACnD,QAAQ,MAAM,MAAd;EACE,KAAK,mBACH,OAAO;EACT,KAAK,cACH,OAAO;EACT,KAAK,aACH,OAAO;EACT,SACE;CACJ;AACF;;;;;AAMA,SAAS,iBAAiB,SAA2E;CACnG,OAAO,OAAO,YACZ,OAAO,QAAQ,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,GAAG,WAAW,UAAU,KAAA,CAAS,CACzE;AACF;AAEA,IAAa,mBAAb,MAA4D;CAC1D,KAAc;;;;;;CAMd,4BAAqC;CAErC;;;;;;CAMA,sBAAsB;CAEtB,YAAY,QAAgC;EAC1C,IAAI,CAAC,OAAO,eACV,MAAM,IAAI,MACR,kGACF;EAEF,KAAKA,UAAU;CACjB;CAEA,SAAS,KAAgD;EAGvD,MAAM,iBACJ,IAAI,QAAQ,mBAAmB,IAAI,QAAQ,qBAAqB,CAAC,IAAI,QAAQ,kBAAkB,IAAI,CAAC;EACtG,KAAKC,sBAAsB,eAAe,SAAS;EACnD,OAAO,0BAA0B;GAC/B,OAAO;IACL,UAAU,KAAKD,QAAQ;IACvB,cAAc,KAAKA,QAAQ;IAC3B,eAAe,KAAKA,QAAQ;IAC5B,UAAU,KAAKA,QAAQ;GACzB;GACA,cAAc,IAAI,QAAQ;GAC1B,UAAU,IAAI,QAAQ;GACtB;GACA,gBAAgB,IAAI,QAAQ;GAC5B,WAAW,IAAI,SAAS;GACxB,MAAM,IAAI;GACV,gBAAgB;IACd,aAAa;IACb,cAAc;IACd,GAAG,iBAAiB,KAAKA,QAAQ,cAAc;GACjD;EACF,CAAC;CACH;CAEA,cAAc,KAAgD;EAC5D,OAAO,IAAI,mBAAmB,EAAE,YAAY,IAAI,WAAW,CAAC;CAC9D;CAEA,OAAO,KAAqC;EAC1C,MAAM,EAAE,UAAU,cAAc,qBAAqB,aAAa,KAAKA;EACvE,OAAO,yBAAyB;GAC9B,MAAM,IAAI;GACV,cAAc,IAAI,QAAQ;GAC1B,mBAAmB,IAAI;GACvB,MACE,YAAY,gBAAgB,sBACxB;IAAE;IAAU;IAAc,iBAAiB;IAAqB;GAAS,IACzE,KAAA;GACN,UAAU,IAAI,QAAQ;EACxB,CAAC;CACH;CAEA,cAAuC;EACrC,MAAM,EAAE,UAAU,cAAc,UAAU,wBAAwB,KAAKA;EACvE,OAAO;GACL,YAAY;GACZ,oBAAoB,QAAQ,QAAQ;GACpC,gBAAgB,QAAQ,YAAY,gBAAgB,mBAAmB;GACvE,oBAAoB,KAAKC;EAC3B;CACF;AACF"}
@@ -124,6 +124,7 @@ export declare function readSessionWorkspaceFile(session: SourceControlSession,
124
124
  allowUnapprovedPath?: boolean;
125
125
  }): Promise<WorkspaceFile>;
126
126
  export declare function parseWorkspaceChanges(output: string): WorkspaceChange[];
127
+ export declare function parseWorkspaceTrackedChanges(output: string): WorkspaceChange[];
127
128
  export declare function parseWorkspaceChangeStats(output: string): Map<string, {
128
129
  additions?: number;
129
130
  deletions?: number;
@@ -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;AAOpD,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;CAChC;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,gBAAgB;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,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,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,CAchC;AA6BD,iFAAiF;AACjF,wBAAsB,uBAAuB,CAC3C,OAAO,EAAE,oBAAoB,EAC7B,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,wBAAwB,CAAC,CAsCnC;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;AAoBD,wBAAsB,2BAA2B,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAgD1G;AAiBD,wBAAsB,wBAAwB,CAC5C,OAAO,EAAE,oBAAoB,EAC7B,IAAI,EAAE,MAAM,EACZ,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,aAAa,CAAC,CA4DxB;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
@@ -13,12 +13,13 @@ const MAX_DIFF_BYTES = 512 * 1024;
13
13
  const WORKSPACE_NUMSTAT_SCRIPT = `
14
14
  set -e
15
15
  workdir=$1
16
+ base=$2
16
17
  index_file=$(mktemp)
17
18
  untracked_file=$(mktemp)
18
19
  object_dir=$(mktemp -d)
19
20
  trap 'rm -f "$index_file" "$untracked_file"; rm -rf "$object_dir"' EXIT
20
21
  rm -f "$index_file"
21
- git -C "$workdir" diff --numstat -z --find-renames --no-ext-diff --no-textconv HEAD
22
+ git -C "$workdir" diff --numstat -z --find-renames --no-ext-diff --no-textconv "$base"
22
23
  git -C "$workdir" ls-files --others --exclude-standard -z >"$untracked_file"
23
24
  git_dir=$(git -C "$workdir" rev-parse --absolute-git-dir)
24
25
  export GIT_INDEX_FILE="$index_file"
@@ -413,6 +414,31 @@ function parseWorkspaceChanges(output) {
413
414
  }
414
415
  return changes.toSorted((a, b) => a.path.localeCompare(b.path));
415
416
  }
417
+ function parseWorkspaceTrackedChanges(output) {
418
+ const records = output.split("\0");
419
+ const changes = [];
420
+ for (let index = 0; index < records.length;) {
421
+ const code = records[index++];
422
+ if (!code) continue;
423
+ const status = changeStatus(code);
424
+ if (status === "renamed" || status === "copied") {
425
+ const previousPath = records[index++];
426
+ const path = records[index++];
427
+ if (path) changes.push({
428
+ path,
429
+ previousPath: previousPath || void 0,
430
+ status
431
+ });
432
+ continue;
433
+ }
434
+ const path = records[index++];
435
+ if (path) changes.push({
436
+ path,
437
+ status
438
+ });
439
+ }
440
+ return changes.toSorted((a, b) => a.path.localeCompare(b.path));
441
+ }
416
442
  function parseWorkspaceChangeStats(output) {
417
443
  const records = output.split("\0");
418
444
  const stats = /* @__PURE__ */ new Map();
@@ -452,24 +478,53 @@ function unavailableWorkspaceChanges(workspacePath) {
452
478
  changes: []
453
479
  };
454
480
  }
481
+ async function resolveSessionComparisonBase(session, sandbox, workdir) {
482
+ const result = await sandbox.executeCommand("git", [
483
+ "-C",
484
+ workdir,
485
+ "merge-base",
486
+ "HEAD",
487
+ `origin/${session.baseBranch}`
488
+ ], { timeout: 3e4 });
489
+ const sha = result.stdout.trim();
490
+ return result.exitCode === 0 && /^[0-9a-f]{40,64}$/i.test(sha) ? sha : "HEAD";
491
+ }
455
492
  async function listSessionWorkspaceChanges(session) {
456
493
  const handle = await sessionSandbox(session);
457
494
  if (!handle) return unavailableWorkspaceChanges(session.sessionId);
458
- const [statusResult, statsResult] = await Promise.all([handle.sandbox.executeCommand("git", [
459
- "-C",
460
- handle.workdir,
461
- "status",
462
- "--porcelain=v1",
463
- "-z",
464
- "--untracked-files=all"
465
- ], { timeout: 3e4 }), handle.sandbox.executeCommand("sh", [
466
- "-c",
467
- WORKSPACE_NUMSTAT_SCRIPT,
468
- "mastracode-numstat",
469
- handle.workdir
470
- ], { timeout: 3e4 })]);
495
+ const comparisonBase = await resolveSessionComparisonBase(session, handle.sandbox, handle.workdir);
496
+ const [statusResult, trackedResult, statsResult] = await Promise.all([
497
+ handle.sandbox.executeCommand("git", [
498
+ "-C",
499
+ handle.workdir,
500
+ "status",
501
+ "--porcelain=v1",
502
+ "-z",
503
+ "--untracked-files=all"
504
+ ], { timeout: 3e4 }),
505
+ handle.sandbox.executeCommand("git", [
506
+ "-C",
507
+ handle.workdir,
508
+ "diff",
509
+ "--name-status",
510
+ "-z",
511
+ "--find-renames",
512
+ comparisonBase
513
+ ], { timeout: 3e4 }),
514
+ handle.sandbox.executeCommand("sh", [
515
+ "-c",
516
+ WORKSPACE_NUMSTAT_SCRIPT,
517
+ "mastracode-numstat",
518
+ handle.workdir,
519
+ comparisonBase
520
+ ], { timeout: 3e4 })
521
+ ]);
471
522
  if (statusResult.exitCode !== 0) return unavailableWorkspaceChanges(session.sessionId);
472
- const changes = parseWorkspaceChanges(statusResult.stdout);
523
+ const statusChanges = parseWorkspaceChanges(statusResult.stdout);
524
+ const trackedChanges = trackedResult.exitCode === 0 ? parseWorkspaceTrackedChanges(trackedResult.stdout) : [];
525
+ const changesByPath = new Map(trackedChanges.map((change) => [change.path, change]));
526
+ for (const change of statusChanges) if (trackedResult.exitCode !== 0 || change.status === "untracked" || change.status === "conflicted") changesByPath.set(change.path, change);
527
+ const changes = [...changesByPath.values()].toSorted((a, b) => a.path.localeCompare(b.path));
473
528
  if (statsResult.exitCode !== 0) return {
474
529
  workspacePath: session.sessionId,
475
530
  available: true,
@@ -515,6 +570,7 @@ async function readSessionWorkspaceDiff(session, path, previousPath) {
515
570
  const safePreviousPath = previousPath ? assertRelativePath(previousPath, "previousPath") : void 0;
516
571
  const handle = await sessionSandbox(session);
517
572
  if (!handle) throw new Error("Session workspace is not available");
573
+ const comparisonBase = await resolveSessionComparisonBase(session, handle.sandbox, handle.workdir);
518
574
  const pathspecs = safePreviousPath ? [safePreviousPath, safePath] : [safePath];
519
575
  let result = await executeBoundedGitDiff(handle.sandbox, [
520
576
  "--literal-pathspecs",
@@ -525,7 +581,7 @@ async function readSessionWorkspaceDiff(session, path, previousPath) {
525
581
  "--no-ext-diff",
526
582
  "--no-color",
527
583
  "--unified=3",
528
- "HEAD",
584
+ comparisonBase,
529
585
  "--",
530
586
  ...pathspecs
531
587
  ]);
@@ -744,6 +800,6 @@ function buildFsRoutes(options = {}) {
744
800
  ];
745
801
  }
746
802
  //#endregion
747
- export { buildFsRoutes, listArtifacts, listDirectory, listSessionFilesystemFiles, listSessionRenderedPath, listSessionWorkspaceChanges, listWorkspaceRenderedPath, parseWorkspaceChangeStats, parseWorkspaceChanges, readSessionWorkspaceDiff, readSessionWorkspaceFile, readWorkspaceFile, resolveCodebase, resolveFsRoot };
803
+ export { buildFsRoutes, listArtifacts, listDirectory, listSessionFilesystemFiles, listSessionRenderedPath, listSessionWorkspaceChanges, listWorkspaceRenderedPath, parseWorkspaceChangeStats, parseWorkspaceChanges, parseWorkspaceTrackedChanges, readSessionWorkspaceDiff, readSessionWorkspaceFile, readWorkspaceFile, resolveCodebase, resolveFsRoot };
748
804
 
749
805
  //# sourceMappingURL=fs.js.map