@mastra/factory 0.10.0-alpha.1 → 0.10.0-alpha.13

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.
Files changed (138) hide show
  1. package/CHANGELOG.md +212 -0
  2. package/dist/auth.d.ts.map +1 -1
  3. package/dist/auth.js +2 -1
  4. package/dist/auth.js.map +1 -1
  5. package/dist/factory.d.ts +8 -0
  6. package/dist/factory.d.ts.map +1 -1
  7. package/dist/factory.js +25 -8
  8. package/dist/factory.js.map +1 -1
  9. package/dist/index.d.ts +2 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +2 -1
  12. package/dist/integrations/base.d.ts +3 -1
  13. package/dist/integrations/base.d.ts.map +1 -1
  14. package/dist/integrations/github/integration.d.ts +14 -0
  15. package/dist/integrations/github/integration.d.ts.map +1 -1
  16. package/dist/integrations/github/integration.js +48 -1
  17. package/dist/integrations/github/integration.js.map +1 -1
  18. package/dist/integrations/github/provenance.d.ts +7 -2
  19. package/dist/integrations/github/provenance.d.ts.map +1 -1
  20. package/dist/integrations/github/provenance.js +16 -2
  21. package/dist/integrations/github/provenance.js.map +1 -1
  22. package/dist/integrations/github/routes.d.ts +8 -1
  23. package/dist/integrations/github/routes.d.ts.map +1 -1
  24. package/dist/integrations/github/routes.js +228 -7
  25. package/dist/integrations/github/routes.js.map +1 -1
  26. package/dist/integrations/github/rules.d.ts.map +1 -1
  27. package/dist/integrations/github/rules.js +193 -127
  28. package/dist/integrations/github/rules.js.map +1 -1
  29. package/dist/integrations/github/session-subscriptions.d.ts +9 -0
  30. package/dist/integrations/github/session-subscriptions.d.ts.map +1 -1
  31. package/dist/integrations/github/session-subscriptions.js +39 -1
  32. package/dist/integrations/github/session-subscriptions.js.map +1 -1
  33. package/dist/integrations/linear/routes.d.ts.map +1 -1
  34. package/dist/integrations/linear/routes.js +50 -0
  35. package/dist/integrations/linear/routes.js.map +1 -1
  36. package/dist/integrations/platform/github/integration.d.ts +3 -1
  37. package/dist/integrations/platform/github/integration.d.ts.map +1 -1
  38. package/dist/integrations/platform/github/integration.js +33 -0
  39. package/dist/integrations/platform/github/integration.js.map +1 -1
  40. package/dist/routes/attention.d.ts +17 -0
  41. package/dist/routes/attention.d.ts.map +1 -0
  42. package/dist/routes/attention.js +305 -0
  43. package/dist/routes/attention.js.map +1 -0
  44. package/dist/routes/intake.d.ts +5 -0
  45. package/dist/routes/intake.d.ts.map +1 -1
  46. package/dist/routes/intake.js +70 -19
  47. package/dist/routes/intake.js.map +1 -1
  48. package/dist/routes/surface.d.ts +6 -5
  49. package/dist/routes/surface.d.ts.map +1 -1
  50. package/dist/routes/surface.js +62 -43
  51. package/dist/routes/surface.js.map +1 -1
  52. package/dist/routes/work-items.d.ts.map +1 -1
  53. package/dist/routes/work-items.js +35 -14
  54. package/dist/routes/work-items.js.map +1 -1
  55. package/dist/rules/defaults.js +5 -5
  56. package/dist/rules/defaults.js.map +1 -1
  57. package/dist/rules/dispatch-errors.d.ts +13 -0
  58. package/dist/rules/dispatch-errors.d.ts.map +1 -0
  59. package/dist/rules/dispatch-errors.js +77 -0
  60. package/dist/rules/dispatch-errors.js.map +1 -0
  61. package/dist/rules/dispatcher.d.ts +9 -0
  62. package/dist/rules/dispatcher.d.ts.map +1 -1
  63. package/dist/rules/dispatcher.js +118 -45
  64. package/dist/rules/dispatcher.js.map +1 -1
  65. package/dist/rules/processor.d.ts.map +1 -1
  66. package/dist/rules/processor.js +30 -14
  67. package/dist/rules/processor.js.map +1 -1
  68. package/dist/rules/terminal-cleanup.d.ts +1 -1
  69. package/dist/rules/terminal-cleanup.d.ts.map +1 -1
  70. package/dist/rules/terminal-cleanup.js +2 -2
  71. package/dist/rules/terminal-cleanup.js.map +1 -1
  72. package/dist/rules/tools.d.ts.map +1 -1
  73. package/dist/rules/tools.js +9 -5
  74. package/dist/rules/tools.js.map +1 -1
  75. package/dist/rules/transition-service.d.ts +5 -2
  76. package/dist/rules/transition-service.d.ts.map +1 -1
  77. package/dist/rules/transition-service.js +15 -2
  78. package/dist/rules/transition-service.js.map +1 -1
  79. package/dist/rules/types.d.ts +6 -1
  80. package/dist/rules/types.d.ts.map +1 -1
  81. package/dist/rules/types.js +25 -1
  82. package/dist/rules/types.js.map +1 -1
  83. package/dist/secret-encryption.d.ts +25 -0
  84. package/dist/secret-encryption.d.ts.map +1 -0
  85. package/dist/secret-encryption.js +96 -0
  86. package/dist/secret-encryption.js.map +1 -0
  87. package/dist/session/factory-session.d.ts +13 -3
  88. package/dist/session/factory-session.d.ts.map +1 -1
  89. package/dist/session/factory-session.js +15 -6
  90. package/dist/session/factory-session.js.map +1 -1
  91. package/dist/session/first-exec-capture.d.ts +16 -10
  92. package/dist/session/first-exec-capture.d.ts.map +1 -1
  93. package/dist/session/first-exec-capture.js +74 -14
  94. package/dist/session/first-exec-capture.js.map +1 -1
  95. package/dist/session/first-message-capture.d.ts +14 -10
  96. package/dist/session/first-message-capture.d.ts.map +1 -1
  97. package/dist/session/first-message-capture.js +17 -11
  98. package/dist/session/first-message-capture.js.map +1 -1
  99. package/dist/session/session-title.d.ts +7 -0
  100. package/dist/session/session-title.d.ts.map +1 -0
  101. package/dist/session/session-title.js +14 -0
  102. package/dist/session/session-title.js.map +1 -0
  103. package/dist/session/thread-title-mirror.d.ts +35 -0
  104. package/dist/session/thread-title-mirror.d.ts.map +1 -0
  105. package/dist/session/thread-title-mirror.js +51 -0
  106. package/dist/session/thread-title-mirror.js.map +1 -0
  107. package/dist/storage/domains/credentials/base.d.ts +3 -1
  108. package/dist/storage/domains/credentials/base.d.ts.map +1 -1
  109. package/dist/storage/domains/credentials/base.js +33 -15
  110. package/dist/storage/domains/credentials/base.js.map +1 -1
  111. package/dist/storage/domains/custom-providers/base.d.ts +3 -1
  112. package/dist/storage/domains/custom-providers/base.d.ts.map +1 -1
  113. package/dist/storage/domains/custom-providers/base.js +45 -25
  114. package/dist/storage/domains/custom-providers/base.js.map +1 -1
  115. package/dist/storage/domains/integrations/base.d.ts +3 -1
  116. package/dist/storage/domains/integrations/base.d.ts.map +1 -1
  117. package/dist/storage/domains/integrations/base.js +28 -12
  118. package/dist/storage/domains/integrations/base.js.map +1 -1
  119. package/dist/storage/domains/source-control/base.d.ts +8 -0
  120. package/dist/storage/domains/source-control/base.d.ts.map +1 -1
  121. package/dist/storage/domains/source-control/base.js +14 -2
  122. package/dist/storage/domains/source-control/base.js.map +1 -1
  123. package/dist/storage/domains/source-control/inmemory.d.ts +4 -0
  124. package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
  125. package/dist/storage/domains/source-control/inmemory.js +7 -0
  126. package/dist/storage/domains/source-control/inmemory.js.map +1 -1
  127. package/dist/storage/domains/work-items/base.d.ts +120 -12
  128. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  129. package/dist/storage/domains/work-items/base.js +514 -44
  130. package/dist/storage/domains/work-items/base.js.map +1 -1
  131. package/dist/work-item-branch.d.ts +23 -0
  132. package/dist/work-item-branch.d.ts.map +1 -0
  133. package/dist/work-item-branch.js +39 -0
  134. package/dist/work-item-branch.js.map +1 -0
  135. package/factory-skills/factory-rereview/SKILL.md +2 -0
  136. package/factory-skills/factory-review/SKILL.md +2 -0
  137. package/factory-skills/factory-triage/SKILL.md +7 -17
  138. package/package.json +5 -5
@@ -1 +1 @@
1
- {"version":3,"file":"provenance.js","names":[],"sources":["../../../src/integrations/github/provenance.ts"],"sourcesContent":["import type { FactoryRuleJsonValue } from '../../rules/types.js';\nimport type { IntegrationStorageHandle } from '../../storage/domains/integrations/base.js';\nimport type { SourceControlStorageHandle } from '../../storage/domains/source-control/base.js';\nimport type { FactoryRunBindingRecord, WorkItemRow } from '../../storage/domains/work-items/base.js';\nimport type { GithubIntegration } from './integration.js';\nimport { parseCreatedPullRequest } from './session-subscriptions.js';\n\nexport interface RecordFactoryPullRequestProvenanceInput {\n binding: FactoryRunBindingRecord;\n item: WorkItemRow;\n assistantMessageId: string;\n toolCallId: string;\n toolName: string;\n toolInput: FactoryRuleJsonValue;\n toolResult: FactoryRuleJsonValue;\n status: 'success' | 'error';\n}\n\nexport interface FactoryPullRequestProvenanceData {\n kind: 'factory-pr-provenance';\n bindingId: string;\n workItemId: string;\n repositoryId: number;\n pullRequestNumber: number;\n pullRequestUrl: string;\n assistantMessageId: string;\n toolCallId: string;\n}\n\nexport async function recordFactoryPullRequestProvenance(\n github: GithubIntegration,\n sourceControl: SourceControlStorageHandle,\n integrationStorage: IntegrationStorageHandle<\n Record<string, unknown>,\n Record<string, unknown>,\n FactoryPullRequestProvenanceData\n >,\n input: RecordFactoryPullRequestProvenanceInput,\n): Promise<void> {\n if (input.status !== 'success' || input.item.externalSource?.type === 'pull-request') return;\n const url = parseCreatedPullRequest({\n toolName: input.toolName,\n input: input.toolInput,\n output: input.toolResult,\n });\n if (!url) return;\n\n try {\n const match = url.match(/^https:\\/\\/github\\.com\\/([^/]+\\/[^/]+)\\/pull\\/(\\d+)\\/?$/i);\n if (!match) return;\n const repositorySlug = match[1]!;\n let repositoryId: number | undefined;\n let installationId: number | undefined;\n for (const connection of await sourceControl.connections.list({\n orgId: input.binding.orgId,\n factoryProjectId: input.binding.factoryProjectId,\n })) {\n const installation = await sourceControl.installations.get({\n orgId: input.binding.orgId,\n id: connection.installationId,\n });\n if (!installation) continue;\n for (const link of await sourceControl.projectRepositories.list({\n orgId: input.binding.orgId,\n connectionId: connection.id,\n })) {\n const repository = await sourceControl.repositories.get({ orgId: input.binding.orgId, id: link.repositoryId });\n if (!repository || repository.slug.toLowerCase() !== repositorySlug.toLowerCase()) continue;\n repositoryId = Number(repository.externalId);\n installationId = Number(installation.externalId);\n break;\n }\n if (repositoryId !== undefined) break;\n }\n const pullRequestNumber = Number(match[2]);\n const [owner, repo] = repositorySlug.split('/');\n if (\n !owner ||\n !repo ||\n repositoryId === undefined ||\n installationId === undefined ||\n !Number.isInteger(repositoryId) ||\n !Number.isInteger(installationId) ||\n !Number.isInteger(pullRequestNumber) ||\n pullRequestNumber < 1\n )\n return;\n const targetKey = `factory-pr-provenance:${repositoryId}:${pullRequestNumber}`;\n if (\n (await integrationStorage.subscriptions.listByTarget(targetKey)).some(row => row.orgId === input.binding.orgId)\n ) {\n return;\n }\n\n const { data } = await github\n .getInstallationOctokit(installationId)\n .pulls.get({ owner, repo, pull_number: pullRequestNumber });\n if (data.base.repo.id !== repositoryId || data.number !== pullRequestNumber || data.html_url !== url) return;\n\n await integrationStorage.subscriptions.create({\n orgId: input.binding.orgId,\n targetKey,\n threadId: input.binding.threadId,\n status: 'active',\n data: {\n kind: 'factory-pr-provenance',\n bindingId: input.binding.id,\n workItemId: input.item.id,\n repositoryId,\n pullRequestNumber,\n pullRequestUrl: url,\n assistantMessageId: input.assistantMessageId,\n toolCallId: input.toolCallId,\n },\n });\n } catch {\n return;\n }\n}\n"],"mappings":";;AA6BA,eAAsB,mCACpB,QACA,eACA,oBAKA,OACe;CACf,IAAI,MAAM,WAAW,aAAa,MAAM,KAAK,gBAAgB,SAAS,gBAAgB;CACtF,MAAM,MAAM,wBAAwB;EAClC,UAAU,MAAM;EAChB,OAAO,MAAM;EACb,QAAQ,MAAM;CAChB,CAAC;CACD,IAAI,CAAC,KAAK;CAEV,IAAI;EACF,MAAM,QAAQ,IAAI,MAAM,0DAA0D;EAClF,IAAI,CAAC,OAAO;EACZ,MAAM,iBAAiB,MAAM;EAC7B,IAAI;EACJ,IAAI;EACJ,KAAK,MAAM,cAAc,MAAM,cAAc,YAAY,KAAK;GAC5D,OAAO,MAAM,QAAQ;GACrB,kBAAkB,MAAM,QAAQ;EAClC,CAAC,GAAG;GACF,MAAM,eAAe,MAAM,cAAc,cAAc,IAAI;IACzD,OAAO,MAAM,QAAQ;IACrB,IAAI,WAAW;GACjB,CAAC;GACD,IAAI,CAAC,cAAc;GACnB,KAAK,MAAM,QAAQ,MAAM,cAAc,oBAAoB,KAAK;IAC9D,OAAO,MAAM,QAAQ;IACrB,cAAc,WAAW;GAC3B,CAAC,GAAG;IACF,MAAM,aAAa,MAAM,cAAc,aAAa,IAAI;KAAE,OAAO,MAAM,QAAQ;KAAO,IAAI,KAAK;IAAa,CAAC;IAC7G,IAAI,CAAC,cAAc,WAAW,KAAK,YAAY,MAAM,eAAe,YAAY,GAAG;IACnF,eAAe,OAAO,WAAW,UAAU;IAC3C,iBAAiB,OAAO,aAAa,UAAU;IAC/C;GACF;GACA,IAAI,iBAAiB,KAAA,GAAW;EAClC;EACA,MAAM,oBAAoB,OAAO,MAAM,EAAE;EACzC,MAAM,CAAC,OAAO,QAAQ,eAAe,MAAM,GAAG;EAC9C,IACE,CAAC,SACD,CAAC,QACD,iBAAiB,KAAA,KACjB,mBAAmB,KAAA,KACnB,CAAC,OAAO,UAAU,YAAY,KAC9B,CAAC,OAAO,UAAU,cAAc,KAChC,CAAC,OAAO,UAAU,iBAAiB,KACnC,oBAAoB,GAEpB;EACF,MAAM,YAAY,yBAAyB,aAAa,GAAG;EAC3D,KACG,MAAM,mBAAmB,cAAc,aAAa,SAAS,EAAA,CAAG,MAAK,QAAO,IAAI,UAAU,MAAM,QAAQ,KAAK,GAE9G;EAGF,MAAM,EAAE,SAAS,MAAM,OACpB,uBAAuB,cAAc,CAAC,CACtC,MAAM,IAAI;GAAE;GAAO;GAAM,aAAa;EAAkB,CAAC;EAC5D,IAAI,KAAK,KAAK,KAAK,OAAO,gBAAgB,KAAK,WAAW,qBAAqB,KAAK,aAAa,KAAK;EAEtG,MAAM,mBAAmB,cAAc,OAAO;GAC5C,OAAO,MAAM,QAAQ;GACrB;GACA,UAAU,MAAM,QAAQ;GACxB,QAAQ;GACR,MAAM;IACJ,MAAM;IACN,WAAW,MAAM,QAAQ;IACzB,YAAY,MAAM,KAAK;IACvB;IACA;IACA,gBAAgB;IAChB,oBAAoB,MAAM;IAC1B,YAAY,MAAM;GACpB;EACF,CAAC;CACH,QAAQ;EACN;CACF;AACF"}
1
+ {"version":3,"file":"provenance.js","names":[],"sources":["../../../src/integrations/github/provenance.ts"],"sourcesContent":["import type { FactoryRuleJsonValue } from '../../rules/types.js';\nimport type { IntegrationStorageHandle } from '../../storage/domains/integrations/base.js';\nimport type { SourceControlStorageHandle } from '../../storage/domains/source-control/base.js';\nimport type { FactoryRunBindingRecord, WorkItemRow, WorkItemsStorage } from '../../storage/domains/work-items/base.js';\nimport type { GithubIntegration } from './integration.js';\nimport { parseCreatedPullRequest } from './session-subscriptions.js';\n\nexport interface RecordFactoryPullRequestProvenanceInput {\n binding: FactoryRunBindingRecord;\n item: WorkItemRow;\n assistantMessageId: string;\n toolCallId: string;\n toolName: string;\n toolInput: FactoryRuleJsonValue;\n toolResult: FactoryRuleJsonValue;\n status: 'success' | 'error';\n}\n\nexport interface FactoryPullRequestProvenanceData {\n kind: 'factory-pr-provenance';\n bindingId: string;\n workItemId: string;\n repositoryId: number;\n pullRequestNumber: number;\n pullRequestUrl: string;\n assistantMessageId: string;\n toolCallId: string;\n}\n\nexport async function resolveFactoryPullRequestParentWorkItemId(\n integrationStorage: IntegrationStorageHandle<\n Record<string, unknown>,\n Record<string, unknown>,\n FactoryPullRequestProvenanceData\n >,\n input: { orgId: string; repositoryId: number; pullRequestNumber: number },\n): Promise<string | null> {\n const targetKey = `factory-pr-provenance:${input.repositoryId}:${input.pullRequestNumber}`;\n const provenance = (await integrationStorage.subscriptions.listByTarget(targetKey, { status: 'active' })).find(\n row => row.orgId === input.orgId && row.data?.kind === 'factory-pr-provenance',\n );\n return provenance?.data?.workItemId ?? null;\n}\n\nexport async function recordFactoryPullRequestProvenance(\n github: GithubIntegration,\n sourceControl: SourceControlStorageHandle,\n integrationStorage: IntegrationStorageHandle<\n Record<string, unknown>,\n Record<string, unknown>,\n FactoryPullRequestProvenanceData\n >,\n workItems: WorkItemsStorage,\n input: RecordFactoryPullRequestProvenanceInput,\n): Promise<void> {\n if (input.status !== 'success' || input.item.externalSource?.type === 'pull-request') return;\n const url = parseCreatedPullRequest({\n toolName: input.toolName,\n input: input.toolInput,\n output: input.toolResult,\n });\n if (!url) return;\n\n try {\n const match = url.match(/^https:\\/\\/github\\.com\\/([^/]+\\/[^/]+)\\/pull\\/(\\d+)\\/?$/i);\n if (!match) return;\n const repositorySlug = match[1]!;\n let repositoryId: number | undefined;\n let installationId: number | undefined;\n for (const connection of await sourceControl.connections.list({\n orgId: input.binding.orgId,\n factoryProjectId: input.binding.factoryProjectId,\n })) {\n const installation = await sourceControl.installations.get({\n orgId: input.binding.orgId,\n id: connection.installationId,\n });\n if (!installation) continue;\n for (const link of await sourceControl.projectRepositories.list({\n orgId: input.binding.orgId,\n connectionId: connection.id,\n })) {\n const repository = await sourceControl.repositories.get({ orgId: input.binding.orgId, id: link.repositoryId });\n if (!repository || repository.slug.toLowerCase() !== repositorySlug.toLowerCase()) continue;\n repositoryId = Number(repository.externalId);\n installationId = Number(installation.externalId);\n break;\n }\n if (repositoryId !== undefined) break;\n }\n const pullRequestNumber = Number(match[2]);\n const [owner, repo] = repositorySlug.split('/');\n if (\n !owner ||\n !repo ||\n repositoryId === undefined ||\n installationId === undefined ||\n !Number.isInteger(repositoryId) ||\n !Number.isInteger(installationId) ||\n !Number.isInteger(pullRequestNumber) ||\n pullRequestNumber < 1\n )\n return;\n const targetKey = `factory-pr-provenance:${repositoryId}:${pullRequestNumber}`;\n if (\n (await integrationStorage.subscriptions.listByTarget(targetKey)).some(row => row.orgId === input.binding.orgId)\n ) {\n return;\n }\n\n const { data } = await github\n .getInstallationOctokit(installationId)\n .pulls.get({ owner, repo, pull_number: pullRequestNumber });\n if (data.base.repo.id !== repositoryId || data.number !== pullRequestNumber || data.html_url !== url) return;\n\n await integrationStorage.subscriptions.create({\n orgId: input.binding.orgId,\n targetKey,\n threadId: input.binding.threadId,\n status: 'active',\n data: {\n kind: 'factory-pr-provenance',\n bindingId: input.binding.id,\n workItemId: input.item.id,\n repositoryId,\n pullRequestNumber,\n pullRequestUrl: url,\n assistantMessageId: input.assistantMessageId,\n toolCallId: input.toolCallId,\n },\n });\n\n const reviewItem = (\n await workItems.list({\n orgId: input.binding.orgId,\n factoryProjectId: input.binding.factoryProjectId,\n })\n ).find(\n item =>\n item.externalSource?.integrationId === 'github' &&\n item.externalSource.type === 'pull-request' &&\n (item.externalSource.externalId === `github-pr:${pullRequestNumber}` ||\n item.externalSource.externalId === `github:${repositoryId}:pull-request:${pullRequestNumber}`),\n );\n if (reviewItem) {\n await workItems.setParentWorkItemIfMissing({\n orgId: input.binding.orgId,\n id: reviewItem.id,\n userId: 'factory-pr-provenance',\n parentWorkItemId: input.item.id,\n });\n }\n } catch {\n return;\n }\n}\n"],"mappings":"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"}
@@ -11,18 +11,21 @@
11
11
  * so the SPA can cleanly hide all GitHub UI.
12
12
  */
13
13
  import type { MountedMastraCode } from '@mastra/code-sdk';
14
- import type { ApiRoute } from '@mastra/core/server';
14
+ import type { ApiRoute, IUserProvider } from '@mastra/core/server';
15
15
  import type { FactoryStorage } from '@mastra/core/storage';
16
16
  import type { RouteAuth } from '../../routes/route.js';
17
17
  import type { SandboxFleet } from '../../sandbox/fleet.js';
18
18
  import type { StateSigner } from '../../state-signing.js';
19
19
  import type { AuditEmitter } from '../../storage/domains/audit/domain.js';
20
+ import type { MemorySettingsStorage } from '../../storage/domains/memory-settings/base.js';
20
21
  import type { FactoryProjectsStorage } from '../../storage/domains/projects/base.js';
21
22
  import type { GithubIntegration } from './integration.js';
22
23
  import type { ParsedGithubWebhook } from './webhook.js';
23
24
  export interface MountGithubRoutesOptions {
24
25
  /** Host auth seam — resolves the signed-in user/tenant for each request. */
25
26
  auth: RouteAuth;
27
+ /** Optional user directory used to resolve session-owner display profiles. */
28
+ users?: SessionOwnerUserProvider;
26
29
  /**
27
30
  * Sandbox fleet for per-project sandboxes. A fleet constructed without a
28
31
  * machine config reports `enabled: false` and the sandbox-backed routes
@@ -52,6 +55,8 @@ export interface MountGithubRoutesOptions {
52
55
  redirectUri?: string;
53
56
  /** Controller used to route verified webhook notifications to exact subscribed sessions. */
54
57
  controller?: MountedMastraCode['controller'];
58
+ /** Owner-scoped observational-memory settings — the source of the model that names a thread. */
59
+ memorySettings: Pick<MemorySettingsStorage, 'get'>;
55
60
  /** Best-effort audit emission supplied by the factory-owned audit domain. */
56
61
  emitAudit?: AuditEmitter['emit'];
57
62
  /** Factory projects domain — resolves a project's default triage model. */
@@ -65,4 +70,6 @@ export interface MountGithubRoutesOptions {
65
70
  * returns only the `status` route so the SPA can detect the disabled state.
66
71
  */
67
72
  export declare function buildGithubRoutes(options: MountGithubRoutesOptions): ApiRoute[];
73
+ type SessionOwnerUserProvider = Pick<IUserProvider, 'getUser'> & Partial<Pick<IUserProvider, 'getUsers'>>;
74
+ export {};
68
75
  //# sourceMappingURL=routes.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/integrations/github/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAGpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAG3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAGvD,OAAO,KAAK,EAAuD,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAChH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AAC1E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AASrF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AA4C1D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAgBxD,MAAM,WAAW,wBAAwB;IACvC,4EAA4E;IAC5E,IAAI,EAAE,SAAS,CAAC;IAChB;;;;OAIG;IACH,KAAK,EAAE,YAAY,CAAC;IACpB,yEAAyE;IACzE,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB;;;;OAIG;IACH,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B;;;;OAIG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iFAAiF;IACjF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4FAA4F;IAC5F,UAAU,CAAC,EAAE,iBAAiB,CAAC,YAAY,CAAC,CAAC;IAC7C,6EAA6E;IAC7E,SAAS,CAAC,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC;IACjC,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,sBAAsB,CAAC;IAClC,iBAAiB,CAAC,EAAE,OAAO,qCAAqC,EAAE,4BAA4B,CAAC;IAC/F,+FAA+F;IAC/F,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACvE;AA4ND;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,QAAQ,EAAE,CA+lB/E"}
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../../src/integrations/github/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAG1D,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGnE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAG3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAGvD,OAAO,KAAK,EAAuD,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAEhH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uCAAuC,CAAC;AAC1E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,+CAA+C,CAAC;AAC3F,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wCAAwC,CAAC;AASrF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AA2C1D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAgBxD,MAAM,WAAW,wBAAwB;IACvC,4EAA4E;IAC5E,IAAI,EAAE,SAAS,CAAC;IAChB,8EAA8E;IAC9E,KAAK,CAAC,EAAE,wBAAwB,CAAC;IACjC;;;;OAIG;IACH,KAAK,EAAE,YAAY,CAAC;IACpB,yEAAyE;IACzE,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB;;;;OAIG;IACH,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B;;;;OAIG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iFAAiF;IACjF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4FAA4F;IAC5F,UAAU,CAAC,EAAE,iBAAiB,CAAC,YAAY,CAAC,CAAC;IAC7C,gGAAgG;IAChG,cAAc,EAAE,IAAI,CAAC,qBAAqB,EAAE,KAAK,CAAC,CAAC;IACnD,6EAA6E;IAC7E,SAAS,CAAC,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC;IACjC,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,sBAAsB,CAAC;IAClC,iBAAiB,CAAC,EAAE,OAAO,qCAAqC,EAAE,4BAA4B,CAAC;IAC/F,+FAA+F;IAC/F,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACvE;AA4ND;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,QAAQ,EAAE,CAqrB/E;AAkQD,KAAK,wBAAwB,GAAG,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC,CAAC"}
@@ -3,12 +3,15 @@ import { listPullRequestSubscriptionsForThread, subscribeToPullRequest } from ".
3
3
  import { SandboxBudgetError } from "../../sandbox/fleet.js";
4
4
  import { MaterializeError, WorktreeError, commitAll, ensureProjectSandbox, isValidGitRef as isValidGitRef$1, materializeRepo, pushBranch, teardownProjectSandbox } from "./sandbox.js";
5
5
  import { baseCheckpointIsStale } from "../../sandbox/base-checkpoint-triggers.js";
6
+ import { normalizeSessionTitle } from "../../session/session-title.js";
6
7
  import { getGithubFeatureDiagnostics, isGithubFeatureEnabled } from "./config.js";
7
8
  import { reclaimDeletedSessionSandbox } from "./sandbox-release.js";
8
9
  import { handleGithubWebhook } from "./webhook.js";
9
10
  import { registerApiRoute } from "@mastra/core/server";
10
11
  import { randomUUID } from "crypto";
11
12
  import { UniqueViolationError } from "@mastra/core/storage";
13
+ import { resolveModel } from "@mastra/code-sdk/agents/model";
14
+ import { RequestContext } from "@mastra/core/request-context";
12
15
  import { streamSSE } from "hono/streaming";
13
16
  //#region src/integrations/github/routes.ts
14
17
  /**
@@ -24,7 +27,6 @@ import { streamSSE } from "hono/streaming";
24
27
  * so the SPA can cleanly hide all GitHub UI.
25
28
  */
26
29
  const sessionOperationLocks = /* @__PURE__ */ new Map();
27
- const MAX_SESSION_TITLE_LENGTH = 80;
28
30
  const USER_SESSION_BRANCH_PREFIX = "user/session-";
29
31
  const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
30
32
  /**
@@ -69,9 +71,6 @@ function isValidGitRef(value) {
69
71
  function isJsonObject(value) {
70
72
  return typeof value === "object" && value !== null && !Array.isArray(value);
71
73
  }
72
- function normalizeSessionTitle(title) {
73
- return [...title.replace(/\s+/g, " ").trim()].slice(0, MAX_SESSION_TITLE_LENGTH).join("").trimEnd() || null;
74
- }
75
74
  /**
76
75
  * Resolve the org-scoped tenant for a GitHub request. GitHub project features
77
76
  * are org-owned, so they require both a signed-in user and a WorkOS
@@ -108,6 +107,11 @@ function parseListPage(raw) {
108
107
  const page = Number(raw);
109
108
  return page >= 1 ? page : null;
110
109
  }
110
+ function parseResourceNumber(raw) {
111
+ if (raw === void 0 || !/^\d{1,10}$/.test(raw)) return null;
112
+ const parsed = Number(raw);
113
+ return parsed > 0 ? parsed : null;
114
+ }
111
115
  const VALID_ISSUE_LABEL_FILTERS = /* @__PURE__ */ new Set(["status: auto-triaged", "status: needs approval"]);
112
116
  function parseIssueLabelFilter(raw) {
113
117
  if (raw === void 0 || raw === "") return void 0;
@@ -208,7 +212,7 @@ async function ingestPolledEvents(events, ingestFactoryEvent) {
208
212
  */
209
213
  function buildGithubRoutes(options) {
210
214
  const routes = [];
211
- const { auth, fleet, storage, github, stateSigner, controller, emitAudit, sessionRetirement } = options;
215
+ const { auth, users, fleet, storage, github, stateSigner, controller, memorySettings, emitAudit, sessionRetirement } = options;
212
216
  const diagnostics = () => getGithubFeatureDiagnostics({
213
217
  github,
214
218
  auth,
@@ -531,6 +535,48 @@ function buildGithubRoutes(options) {
531
535
  }
532
536
  }
533
537
  }));
538
+ routes.push(registerApiRoute("/web/github/projects/:id/issues/:number", {
539
+ method: "GET",
540
+ requiresAuth: false,
541
+ handler: async (c) => {
542
+ const loaded = await loadOrgProject({
543
+ github,
544
+ auth,
545
+ c: loose(c)
546
+ });
547
+ if ("response" in loaded) return loaded.response;
548
+ const number = parseResourceNumber(c.req.param("number"));
549
+ if (number === null) return c.json({ error: "invalid_number" }, 400);
550
+ try {
551
+ const detail = await github.intake.getIssue({
552
+ connection: {
553
+ type: "app-installation",
554
+ installationId: Number(loaded.project.installation.externalId)
555
+ },
556
+ sourceId: loaded.project.repository.slug,
557
+ issueId: String(number)
558
+ });
559
+ if (detail === null) return c.json({ error: "issue_not_found" }, 404);
560
+ return c.json({
561
+ number: Number(detail.id),
562
+ title: detail.title,
563
+ url: detail.url,
564
+ author: detail.author,
565
+ assignee: detail.assignee,
566
+ labels: detail.labels,
567
+ comments: detail.commentCount ?? 0,
568
+ createdAt: detail.createdAt,
569
+ updatedAt: detail.updatedAt,
570
+ description: detail.description
571
+ });
572
+ } catch (err) {
573
+ return c.json({
574
+ error: "github_fetch_failed",
575
+ message: err instanceof Error ? err.message : String(err)
576
+ }, 502);
577
+ }
578
+ }
579
+ }));
534
580
  routes.push(registerApiRoute("/web/github/projects/:id/prs", {
535
581
  method: "GET",
536
582
  requiresAuth: false,
@@ -578,6 +624,49 @@ function buildGithubRoutes(options) {
578
624
  }
579
625
  }
580
626
  }));
627
+ routes.push(registerApiRoute("/web/github/projects/:id/prs/:number", {
628
+ method: "GET",
629
+ requiresAuth: false,
630
+ handler: async (c) => {
631
+ const loaded = await loadOrgProject({
632
+ github,
633
+ auth,
634
+ c: loose(c)
635
+ });
636
+ if ("response" in loaded) return loaded.response;
637
+ const number = parseResourceNumber(c.req.param("number"));
638
+ if (number === null) return c.json({ error: "invalid_number" }, 400);
639
+ try {
640
+ const pr = await github.versionControl.getPullRequest({
641
+ connection: {
642
+ type: "app-installation",
643
+ installationId: Number(loaded.project.installation.externalId)
644
+ },
645
+ sourceId: loaded.project.repository.slug,
646
+ pullRequestId: String(number)
647
+ });
648
+ if (pr === null) return c.json({ error: "pull_request_not_found" }, 404);
649
+ return c.json({
650
+ number: Number(pr.id),
651
+ title: pr.title,
652
+ url: pr.url,
653
+ author: pr.author,
654
+ assignees: pr.assignees ?? [],
655
+ requestedReviewers: pr.requestedReviewers ?? [],
656
+ baseBranch: pr.baseBranch,
657
+ headBranch: pr.headBranch,
658
+ createdAt: pr.createdAt,
659
+ updatedAt: pr.updatedAt,
660
+ description: pr.body
661
+ });
662
+ } catch (err) {
663
+ return c.json({
664
+ error: "github_fetch_failed",
665
+ message: err instanceof Error ? err.message : String(err)
666
+ }, 502);
667
+ }
668
+ }
669
+ }));
581
670
  routes.push(registerApiRoute("/web/github/projects/:id/settings", {
582
671
  method: "GET",
583
672
  requiresAuth: false,
@@ -686,8 +775,10 @@ function buildGithubRoutes(options) {
686
775
  routes.push(...buildProjectGitRoutes({
687
776
  github,
688
777
  auth,
778
+ users,
689
779
  fleet,
690
780
  controller,
781
+ memorySettings,
691
782
  emitAudit,
692
783
  sessionRetirement
693
784
  }));
@@ -894,7 +985,87 @@ async function loadOwnedProject(options) {
894
985
  sandboxRow: await loadOrCreateSandboxRow(github, project, userId)
895
986
  };
896
987
  }
897
- function buildProjectGitRoutes({ github, auth, fleet, controller, emitAudit, sessionRetirement }) {
988
+ /**
989
+ * One naming per session at a time: a second caller joins the run already in
990
+ * flight instead of paying for another model call and racing its rename.
991
+ */
992
+ function createSessionNaming() {
993
+ const inFlight = /* @__PURE__ */ new Map();
994
+ return (sessionId, run) => {
995
+ const pending = inFlight.get(sessionId);
996
+ if (pending) return pending;
997
+ const started = run().finally(() => inFlight.delete(sessionId));
998
+ inFlight.set(sessionId, started);
999
+ return started;
1000
+ };
1001
+ }
1002
+ /**
1003
+ * Name a thread with a stored model id. Resolution has to go through
1004
+ * mastracode's gateway: a bare id handed to core's model router looks for a
1005
+ * process env key instead of the caller's stored provider credentials.
1006
+ */
1007
+ function titleModel(modelId) {
1008
+ return ({ requestContext }) => resolveModel(modelId, {
1009
+ remapForCodexOAuth: true,
1010
+ requestContext
1011
+ });
1012
+ }
1013
+ const MAX_SESSION_OWNER_PROFILES = 100;
1014
+ const MAX_SESSION_OWNER_PROFILE_CACHE_ENTRIES = 500;
1015
+ const SESSION_OWNER_PROFILE_TTL_MS = 5 * 6e4;
1016
+ function createSessionOwnerProfileResolver(users) {
1017
+ const cache = /* @__PURE__ */ new Map();
1018
+ const cacheProfile = (userId, profile, expiresAt) => {
1019
+ cache.delete(userId);
1020
+ cache.set(userId, {
1021
+ profile,
1022
+ expiresAt
1023
+ });
1024
+ if (cache.size > MAX_SESSION_OWNER_PROFILE_CACHE_ENTRIES) {
1025
+ const oldestUserId = cache.keys().next().value;
1026
+ if (oldestUserId !== void 0) cache.delete(oldestUserId);
1027
+ }
1028
+ };
1029
+ return async (userIds) => {
1030
+ if (!users) return /* @__PURE__ */ new Map();
1031
+ const requestedUserIds = [...new Set(userIds)].slice(0, MAX_SESSION_OWNER_PROFILES);
1032
+ const profiles = /* @__PURE__ */ new Map();
1033
+ const unresolvedUserIds = [];
1034
+ const now = Date.now();
1035
+ for (const userId of requestedUserIds) {
1036
+ const cached = cache.get(userId);
1037
+ if (!cached || cached.expiresAt <= now) {
1038
+ cache.delete(userId);
1039
+ unresolvedUserIds.push(userId);
1040
+ } else if (cached.profile) profiles.set(userId, cached.profile);
1041
+ }
1042
+ if (unresolvedUserIds.length === 0) return profiles;
1043
+ let resolvedUsers;
1044
+ if (users.getUsers) try {
1045
+ resolvedUsers = await users.getUsers(unresolvedUserIds);
1046
+ } catch (error) {
1047
+ console.warn("[GitHub Sessions] Bulk session owner profile lookup failed; falling back to individual lookups", { error: error instanceof Error ? error.message : String(error) });
1048
+ resolvedUsers = (await Promise.allSettled(unresolvedUserIds.map((userId) => users.getUser(userId)))).filter((result) => result.status === "fulfilled").map((result) => result.value);
1049
+ }
1050
+ else resolvedUsers = (await Promise.allSettled(unresolvedUserIds.map((userId) => users.getUser(userId)))).filter((result) => result.status === "fulfilled").map((result) => result.value);
1051
+ for (const user of resolvedUsers) {
1052
+ if (!user) continue;
1053
+ const name = user.name?.trim() || user.email?.trim();
1054
+ if (!name) continue;
1055
+ profiles.set(user.id, {
1056
+ id: user.id,
1057
+ name,
1058
+ ...user.avatarUrl ? { avatarUrl: user.avatarUrl } : {}
1059
+ });
1060
+ }
1061
+ const expiresAt = now + SESSION_OWNER_PROFILE_TTL_MS;
1062
+ for (const userId of unresolvedUserIds) cacheProfile(userId, profiles.get(userId), expiresAt);
1063
+ return profiles;
1064
+ };
1065
+ }
1066
+ function buildProjectGitRoutes({ github, auth, users, fleet, controller, memorySettings, emitAudit, sessionRetirement }) {
1067
+ const nameSession = createSessionNaming();
1068
+ const resolveSessionOwnerProfiles = createSessionOwnerProfileResolver(users);
898
1069
  return [
899
1070
  registerApiRoute("/web/github/projects/:id/sessions", {
900
1071
  method: "GET",
@@ -914,7 +1085,14 @@ function buildProjectGitRoutes({ github, auth, fleet, controller, emitAudit, ses
914
1085
  projectRepositoryId: project.id,
915
1086
  viewerUserId: userId
916
1087
  });
917
- return c.json({ sessions });
1088
+ const owners = await resolveSessionOwnerProfiles(sessions.map((session) => session.userId));
1089
+ return c.json({ sessions: sessions.map((session) => {
1090
+ const owner = owners.get(session.userId);
1091
+ return {
1092
+ ...session,
1093
+ ...owner ? { owner } : {}
1094
+ };
1095
+ }) });
918
1096
  }
919
1097
  }),
920
1098
  registerApiRoute("/web/github/projects/:id/sessions", {
@@ -1029,6 +1207,49 @@ function buildProjectGitRoutes({ github, auth, fleet, controller, emitAudit, ses
1029
1207
  return c.json({ removed: true });
1030
1208
  }
1031
1209
  }),
1210
+ registerApiRoute("/web/user-sessions/:sessionId/title", {
1211
+ method: "POST",
1212
+ requiresAuth: false,
1213
+ handler: async (c) => {
1214
+ const resolved = await resolveOrgTenant(loose(c), auth);
1215
+ if ("response" in resolved) return resolved.response;
1216
+ const sessionId = c.req.param("sessionId");
1217
+ const row = await github.sourceControlStorage.sessions.getBySessionId(sessionId);
1218
+ if (!row || row.orgId !== resolved.tenant.orgId || row.userId !== resolved.tenant.userId) return c.json({ error: "Session not found" }, 404);
1219
+ if (!controller) return c.json({ error: "Sessions are not available on this server." }, 503);
1220
+ const thread = (await controller.queryThreads({ resourceId: sessionId })).sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime())[0];
1221
+ if (!thread) return c.json({ error: "This session has no conversation to name yet." }, 409);
1222
+ const stored = await memorySettings.get({
1223
+ orgId: row.orgId,
1224
+ userId: row.userId
1225
+ });
1226
+ const requestContext = new RequestContext();
1227
+ requestContext.set("user", {
1228
+ workosId: row.userId,
1229
+ organizationId: row.orgId
1230
+ });
1231
+ try {
1232
+ const title = await nameSession(sessionId, async () => {
1233
+ const generated = await controller.generateThreadTitle({
1234
+ threadId: thread.id,
1235
+ resourceId: sessionId,
1236
+ requestContext,
1237
+ ...stored?.observerModelId ? { model: titleModel(stored.observerModelId) } : {}
1238
+ });
1239
+ const named = generated ? normalizeSessionTitle(generated) : null;
1240
+ if (named) await github.sourceControlStorage.sessions.rename({
1241
+ sessionId,
1242
+ title: named
1243
+ });
1244
+ return named;
1245
+ });
1246
+ if (!title) return c.json({ error: "The model returned an empty title. Try again." }, 502);
1247
+ return c.json({ title });
1248
+ } catch (error) {
1249
+ return c.json({ error: error instanceof Error ? error.message : String(error) }, 500);
1250
+ }
1251
+ }
1252
+ }),
1032
1253
  registerApiRoute("/web/github/projects/:id/commit", {
1033
1254
  method: "POST",
1034
1255
  requiresAuth: false,