@mastra/factory 0.15.0 → 0.16.0-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/boards/index.d.ts +1 -1
- package/dist/boards/index.d.ts.map +1 -1
- package/dist/boards/index.js +2 -2
- package/dist/boards/index.js.map +1 -1
- package/dist/boards/review.d.ts.map +1 -1
- package/dist/boards/review.js +4 -1
- package/dist/boards/review.js.map +1 -1
- package/dist/boards/semantics.d.ts +7 -0
- package/dist/boards/semantics.d.ts.map +1 -1
- package/dist/boards/semantics.js +15 -1
- package/dist/boards/semantics.js.map +1 -1
- package/dist/boards/transition-policy.d.ts +4 -0
- package/dist/boards/transition-policy.d.ts.map +1 -1
- package/dist/boards/transition-policy.js.map +1 -1
- package/dist/boards/work-transition-policy.d.ts.map +1 -1
- package/dist/boards/work-transition-policy.js +6 -1
- package/dist/boards/work-transition-policy.js.map +1 -1
- package/dist/capabilities/intake.d.ts +4 -0
- package/dist/capabilities/intake.d.ts.map +1 -1
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +51 -12
- package/dist/factory.js.map +1 -1
- package/dist/integrations/base.d.ts +4 -2
- package/dist/integrations/base.d.ts.map +1 -1
- package/dist/integrations/linear/claim.d.ts +8 -0
- package/dist/integrations/linear/claim.d.ts.map +1 -0
- package/dist/integrations/linear/claim.js +14 -0
- package/dist/integrations/linear/claim.js.map +1 -0
- package/dist/integrations/linear/default-rules.d.ts +1 -0
- package/dist/integrations/linear/default-rules.d.ts.map +1 -1
- package/dist/integrations/linear/default-rules.js +2 -0
- package/dist/integrations/linear/default-rules.js.map +1 -1
- package/dist/integrations/linear/integration.d.ts +24 -6
- package/dist/integrations/linear/integration.d.ts.map +1 -1
- package/dist/integrations/linear/integration.js +257 -37
- package/dist/integrations/linear/integration.js.map +1 -1
- package/dist/integrations/linear/routes.d.ts +10 -0
- package/dist/integrations/linear/routes.d.ts.map +1 -1
- package/dist/integrations/linear/routes.js +74 -12
- package/dist/integrations/linear/routes.js.map +1 -1
- package/dist/integrations/linear/rules.d.ts +1 -1
- package/dist/integrations/linear/rules.d.ts.map +1 -1
- package/dist/integrations/linear/rules.js +27 -2
- package/dist/integrations/linear/rules.js.map +1 -1
- package/dist/integrations/platform/linear/integration.d.ts +34 -1
- package/dist/integrations/platform/linear/integration.d.ts.map +1 -1
- package/dist/integrations/platform/linear/integration.js +271 -48
- package/dist/integrations/platform/linear/integration.js.map +1 -1
- package/dist/routes/intake.d.ts.map +1 -1
- package/dist/routes/intake.js +15 -5
- package/dist/routes/intake.js.map +1 -1
- package/dist/rules/dispatch-errors.d.ts.map +1 -1
- package/dist/rules/dispatch-errors.js +8 -0
- package/dist/rules/dispatch-errors.js.map +1 -1
- package/dist/rules/dispatcher.d.ts +1 -0
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +179 -105
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/rules/start-coordinator.d.ts.map +1 -1
- package/dist/rules/start-coordinator.js +4 -1
- package/dist/rules/start-coordinator.js.map +1 -1
- package/dist/rules/terminal-cleanup.d.ts +14 -1
- package/dist/rules/terminal-cleanup.d.ts.map +1 -1
- package/dist/rules/terminal-cleanup.js +20 -0
- package/dist/rules/terminal-cleanup.js.map +1 -1
- package/dist/rules/tools.d.ts +2 -0
- package/dist/rules/tools.d.ts.map +1 -1
- package/dist/rules/tools.js +30 -2
- package/dist/rules/tools.js.map +1 -1
- package/dist/rules/transition-service.d.ts +13 -0
- package/dist/rules/transition-service.d.ts.map +1 -1
- package/dist/rules/transition-service.js +11 -2
- package/dist/rules/transition-service.js.map +1 -1
- package/dist/rules/types.d.ts +15 -0
- package/dist/rules/types.d.ts.map +1 -1
- package/dist/rules/types.js.map +1 -1
- package/dist/rules/validation.d.ts.map +1 -1
- package/dist/rules/validation.js +11 -2
- package/dist/rules/validation.js.map +1 -1
- package/dist/session/run-audit.d.ts +9 -0
- package/dist/session/run-audit.d.ts.map +1 -1
- package/dist/session/run-audit.js +37 -6
- package/dist/session/run-audit.js.map +1 -1
- package/dist/skills/service.d.ts +12 -0
- package/dist/skills/service.d.ts.map +1 -1
- package/dist/skills/service.js +27 -1
- package/dist/skills/service.js.map +1 -1
- package/dist/storage/domains/intake/base.d.ts +11 -8
- package/dist/storage/domains/intake/base.d.ts.map +1 -1
- package/dist/storage/domains/intake/base.js +69 -24
- package/dist/storage/domains/intake/base.js.map +1 -1
- package/dist/storage/domains/work-items/base.d.ts +41 -2
- package/dist/storage/domains/work-items/base.d.ts.map +1 -1
- package/dist/storage/domains/work-items/base.js +159 -12
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/dist/supervisor/session-messaging.d.ts +23 -0
- package/dist/supervisor/session-messaging.d.ts.map +1 -0
- package/dist/supervisor/session-messaging.js +15 -0
- package/dist/supervisor/session-messaging.js.map +1 -0
- package/dist/supervisor/write-tools.d.ts +2 -1
- package/dist/supervisor/write-tools.d.ts.map +1 -1
- package/dist/supervisor/write-tools.js +11 -7
- package/dist/supervisor/write-tools.js.map +1 -1
- package/package.json +12 -12
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integration.js","names":["#client","#endpointHost","#workflowStatesByTeam","#resolveIntakeDispatch","#listProjectSources","#listIssues","#findIssue","#loadComments","#resolveWorkspaceForIssue","#listWorkflowStates","#rules","#projects","#auth","#listWorkspaces","#connectRoute","#candidateWorkspaceIds"],"sources":["../../../../src/integrations/platform/linear/integration.ts"],"sourcesContent":["import type { RequestContext } from '@mastra/core/request-context';\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { MastraWorker } from '@mastra/core/worker';\nimport type { Context } from 'hono';\n\nimport type { IntegrationConnection } from '../../../capabilities/connection.js';\nimport type { Intake, IntakeIssue, IntakeIssueDetail, UpdateIntakeIssueInput } from '../../../capabilities/intake.js';\nimport type { RouteAuth } from '../../../routes/route.js';\nimport type { FactoryProjectsStorage } from '../../../storage/domains/projects/base.js';\nimport type { FactoryIntegration, IntegrationContext, IntegrationTools } from '../../base.js';\nimport { buildLinearAgentTools } from '../../linear/agent-tools.js';\nimport type { LinearEventRules, LinearRuleOverrides } from '../../linear/default-rules.js';\nimport { resolveLinearRules } from '../../linear/default-rules.js';\nimport type { LinearConnectionCheck, LinearIntegration } from '../../linear/integration.js';\nimport { attachLinearIssueReconciler } from '../../linear/issue-reconciler.js';\nimport {\n linearIssueReconciliationEnabled,\n linearIssueReconciliationInterval,\n} from '../../linear/reconciliation-config.js';\nimport { buildLinearRoutes } from '../../linear/routes.js';\nimport { attachLinearRules } from '../../linear/rules.js';\nimport type { LinearConnectionData, LinearConnectionRow, LinearStorageHandle } from '../../linear/storage.js';\nimport {\n logPlatformInfo,\n logPlatformWarn,\n PlatformApiClient,\n PlatformApiError,\n platformApiClientConfigFromEnv,\n} from '../api-client.js';\nimport { PlatformLinearEventWorker } from './event-worker.js';\nimport type { PlatformLinearEventStorage } from './event-worker.js';\n\ntype PageInfo = { hasNextPage: boolean; endCursor: string | null };\ntype LinearUser = {\n id: string;\n name: string;\n displayName: string;\n email: string | null;\n avatarUrl: string | null;\n};\ntype LinearIssue = {\n id: string;\n identifier: string;\n number: number;\n title: string;\n description: string | null;\n url: string;\n priority: number;\n priorityLabel: string;\n labels: Array<{ id: string; name: string }>;\n state: { id: string; name: string; type: string };\n team: { id: string; key: string; name: string };\n assignee: LinearUser | null;\n creator: LinearUser | null;\n createdAt: string;\n updatedAt: string;\n archivedAt: string | null;\n};\ntype LinearComment = {\n id: string;\n body: string;\n url: string;\n issue: { id: string; identifier: string };\n user: LinearUser | null;\n parent: { id: string } | null;\n createdAt: string;\n updatedAt: string;\n};\ntype LinearWorkspace = {\n linearWorkspaceId: string;\n linearWorkspaceName: string;\n urlKey: string | null;\n connected: boolean;\n};\ntype LinearProject = {\n id: string;\n name: string;\n state: string;\n teams: Array<{ id: string; key: string; name: string }>;\n};\ntype ProjectSource = { workspace: LinearWorkspace; project: LinearProject };\ntype LinearWorkflowState = {\n id: string;\n name: string;\n type: string;\n position: number;\n teamId: string;\n};\n\nconst API_PREFIX = '/v1/server/linear';\nconst PAGE_SIZE = 30;\nconst MAX_REFERENCE_PAGES = 20;\nconst MAX_COMMENT_PAGES = 20;\nconst PLATFORM_MANAGED_CONNECTION_TOKEN = 'platform-managed';\n\nfunction loose(c: unknown): Context {\n return c as Context;\n}\n\nfunction routeBaseUrl(ctx: IntegrationContext, requestUrl: string): string {\n return (ctx.baseUrl || new URL(requestUrl).origin).replace(/\\/+$/, '');\n}\n\nexport class PlatformLinearIntegration implements FactoryIntegration {\n readonly id = 'linear';\n readonly #client: PlatformApiClient;\n readonly #endpointHost: string;\n #projects: FactoryProjectsStorage | undefined;\n #auth: RouteAuth | undefined;\n /**\n * Per-instance workflow-state cache keyed by `${workspaceId}:${teamId}`. Scoped to the process\n * lifetime; not shared across requests intentionally to keep failure modes simple (stale states\n * clear on process restart). Populated lazily on first `updateIssue` per team.\n */\n readonly #workflowStatesByTeam = new Map<string, LinearWorkflowState[]>();\n\n readonly intake: Intake = {\n resolveIntakeDispatch: input => this.#resolveIntakeDispatch(input),\n listSources: async () => {\n const sources = await this.#listProjectSources();\n return sources.map(({ workspace, project }) => ({\n id: encodeSourceId(workspace.linearWorkspaceId, project.id),\n name: project.name,\n type: 'project',\n metadata: {\n workspaceId: workspace.linearWorkspaceId,\n workspaceName: workspace.linearWorkspaceName,\n workspaceUrlKey: workspace.urlKey,\n state: project.state,\n teams: project.teams,\n },\n }));\n },\n listItems: async ({ sourceIds, cursor }) => {\n const result = await this.#listIssues(sourceIds, cursor);\n return {\n items: result.issues.map(({ issue, source }) => ({\n source: { type: 'issue', externalId: issue.id, url: issue.url },\n sourceId: encodeSourceId(source.workspace.linearWorkspaceId, source.project.id),\n title: issue.title,\n status: issue.state.name,\n labels: issue.labels.map(label => label.name),\n assignee: issue.assignee?.displayName ?? issue.assignee?.name ?? null,\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n metadata: {\n identifier: issue.identifier,\n workspaceId: source.workspace.linearWorkspaceId,\n workspaceName: source.workspace.linearWorkspaceName,\n projectId: source.project.id,\n projectName: source.project.name,\n team: issue.team.key,\n priority: issue.priorityLabel,\n },\n })),\n nextCursor: result.nextCursor,\n };\n },\n listIssues: async ({ connection, sourceIds, labels, cursor }) => {\n requireLinearConnection(connection);\n const result = await this.#listIssues(sourceIds, cursor, labels);\n return {\n issues: result.issues.map(({ issue, source }) => ({\n ...parseIssue(issue),\n sourceId: encodeSourceId(source.workspace.linearWorkspaceId, source.project.id),\n })),\n nextCursor: result.nextCursor,\n };\n },\n getIssue: async ({ connection, sourceId, issueId }) => {\n requireLinearConnection(connection);\n const located = await this.#findIssue(sourceId, issueId);\n if (!located) return null;\n const comments = await this.#loadComments(located.workspaceId, issueId, located.issue.comments);\n return parseIssueDetail(located.issue, comments);\n },\n createComment: async ({ connection, sourceId, issueId, body }) => {\n requireLinearConnection(connection);\n const workspaceId = await this.#resolveWorkspaceForIssue(sourceId, issueId);\n if (!workspaceId) return null;\n try {\n const comment = await this.#client.request<LinearComment>(\n 'POST',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspaceId)}/issues/${encodeURIComponent(issueId)}/comments`,\n { body },\n );\n return { id: comment.id, url: comment.url };\n } catch (error) {\n if (isNotFound(error)) return null;\n throw error;\n }\n },\n updateIssue: async (input: UpdateIntakeIssueInput): Promise<IntakeIssue | null> => {\n requireLinearConnection(input.connection);\n const located = await this.#findIssue(input.sourceId, input.issueId);\n if (!located) return null;\n const { workspaceId, issue } = located;\n\n const target = input.state;\n // Idempotency: skip the write entirely if the issue is already at the target state.\n if (target.kind === 'byType' && issue.state.type === target.stateType) {\n return parseIssue(issue);\n }\n if (target.kind === 'byName' && issue.state.name.toLowerCase() === target.name.toLowerCase()) {\n return parseIssue(issue);\n }\n\n let states: LinearWorkflowState[];\n try {\n states = await this.#listWorkflowStates(workspaceId, issue.team.id);\n } catch (error) {\n if (isNotFound(error)) {\n // Platform companion endpoint not deployed yet — degrade to policy skip so PR 1\n // is standalone-mergeable ahead of the platform-side workflow-states route landing.\n logPlatformWarn('Platform Linear: workflow-states endpoint not available; skipping updateIssue.', {\n workspaceId,\n teamId: issue.team.id,\n });\n return null;\n }\n throw error;\n }\n let matched: LinearWorkflowState | undefined;\n if (target.kind === 'byType') {\n matched = states\n .slice()\n .sort((a, b) => a.position - b.position)\n .find(s => s.type === target.stateType);\n } else {\n const wanted = target.name.toLowerCase();\n matched = states.find(s => s.name.toLowerCase() === wanted);\n }\n if (!matched) {\n logPlatformWarn('Platform Linear: no workflow state matched target; skipping updateIssue.', {\n workspaceId,\n teamId: issue.team.id,\n target,\n });\n return null;\n }\n\n try {\n await this.#client.request<LinearIssue>(\n 'PATCH',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspaceId)}/issues/${encodeURIComponent(input.issueId)}`,\n { stateId: matched.id },\n );\n } catch (error) {\n if (isNotFound(error)) return null;\n throw error;\n }\n\n const refreshed = await this.#findIssue(input.sourceId, input.issueId);\n return refreshed ? parseIssue(refreshed.issue) : null;\n },\n };\n\n readonly #rules: LinearEventRules;\n\n get rules(): LinearEventRules {\n return this.#rules;\n }\n\n constructor({ rules }: { rules?: LinearRuleOverrides } = {}) {\n this.#rules = resolveLinearRules(rules);\n const config = platformApiClientConfigFromEnv();\n this.#client = new PlatformApiClient(config);\n this.#endpointHost = new URL(config.baseUrl).host;\n }\n\n get storage(): LinearStorageHandle {\n const now = new Date();\n return {\n integrationId: this.id,\n connections: {\n get: async (orgId: string) => ({\n id: `platform-linear:${orgId}`,\n orgId,\n userId: null,\n data: {\n accessToken: PLATFORM_MANAGED_CONNECTION_TOKEN,\n refreshToken: null,\n expiresAtMs: null,\n scope: 'read,comments:create',\n workspaceName: null,\n workspaceUrlKey: null,\n } satisfies LinearConnectionData,\n metadata: {},\n createdAt: now,\n updatedAt: now,\n }),\n },\n } as unknown as LinearStorageHandle;\n }\n\n get projects(): FactoryProjectsStorage {\n if (!this.#projects) throw new Error('PlatformLinearIntegration projects storage has not been initialized.');\n return this.#projects;\n }\n\n initialize({ projects, auth }: { projects: FactoryProjectsStorage; auth?: RouteAuth }): void {\n this.#projects = projects;\n this.#auth = auth;\n logPlatformInfo('Platform Linear integration initialized', { endpointHost: this.#endpointHost });\n }\n\n get authEnabled(): boolean {\n return this.#auth?.enabled() ?? false;\n }\n\n async resolveOrgId(resourceId: string): Promise<string | null> {\n try {\n const project = await this.projects.getById({ id: resourceId });\n return project?.orgId ?? null;\n } catch {\n return null;\n }\n }\n\n async loadConnection(orgId: string): Promise<LinearConnectionRow | null> {\n const workspace = (await this.#listWorkspaces())[0];\n if (!workspace) return null;\n const now = new Date();\n return {\n id: `platform-linear:${orgId}`,\n orgId,\n userId: null,\n accessToken: PLATFORM_MANAGED_CONNECTION_TOKEN,\n scope: 'read,comments:create',\n refreshToken: null,\n expiresAt: null,\n workspaceName: workspace.linearWorkspaceName,\n workspaceUrlKey: workspace.urlKey,\n createdAt: now,\n updatedAt: now,\n };\n }\n\n async getFreshAccessToken(_connection: LinearConnectionRow): Promise<string> {\n return PLATFORM_MANAGED_CONNECTION_TOKEN;\n }\n\n /**\n * Background-dispatch context: platform-managed connection token when the\n * org has a connected workspace. Linear work items store the issue UUID\n * directly as `externalId`.\n */\n async #resolveIntakeDispatch({\n orgId,\n externalSource,\n }: {\n orgId: string;\n externalSource: { type: string; externalId: string };\n }): Promise<{ connection: IntegrationConnection; issueId: string } | null> {\n if (externalSource.type !== 'issue') return null;\n const connection = await this.loadConnection(orgId);\n if (!connection) return null;\n return {\n connection: { type: 'oauth', accessToken: PLATFORM_MANAGED_CONNECTION_TOKEN },\n issueId: externalSource.externalId,\n };\n }\n\n canPostComments(connection: LinearConnectionRow): boolean {\n const scopes = (connection.scope ?? '').split(/[\\s,]+/).filter(Boolean);\n return scopes.some(scope => scope === 'comments:create' || scope === 'write' || scope === 'admin');\n }\n\n async checkConnection(orgId: string): Promise<LinearConnectionCheck> {\n const connection = await this.loadConnection(orgId);\n return {\n connected: connection !== null,\n canComment: connection !== null && this.canPostComments(connection),\n checkedAt: Date.now(),\n };\n }\n\n workers(ctx: IntegrationContext): MastraWorker[] {\n const pollingEnabled = process.env.MASTRACODE_PLATFORM_LINEAR_POLLING_ENABLED?.trim().toLowerCase() !== 'false';\n const reconcileEnabled = linearIssueReconciliationEnabled();\n if (!pollingEnabled && !reconcileEnabled) return [];\n\n const ingest = attachLinearRules(this, ctx);\n const reconcile = reconcileEnabled ? attachLinearIssueReconciler(this, ctx) : undefined;\n const workItems = ctx.runtime?.workItems;\n if (!workItems) return [];\n if (!ingest && !reconcile) return [];\n\n const pollIntervalMs = optionalPositiveIntegerEnv('MASTRACODE_PLATFORM_LINEAR_POLLING_INTERVAL_MS');\n const reconcileIntervalMs = linearIssueReconciliationInterval();\n\n return [\n new PlatformLinearEventWorker({\n client: this.#client,\n linear: { listWorkspaces: () => this.listWorkspaces() },\n storage: ctx.storage.generic as unknown as PlatformLinearEventStorage,\n projects: ctx.storage.projects,\n workItems,\n ...(ingest ? { ingestFactoryIssue: ingest } : {}),\n ...(reconcile ? { reconcileFactoryState: reconcile } : {}),\n pollEventsEnabled: pollingEnabled,\n ...(pollIntervalMs !== undefined ? { intervalMs: pollIntervalMs } : {}),\n ...(reconcileIntervalMs !== undefined ? { reconcileIntervalMs } : {}),\n }),\n ];\n }\n\n routes(ctx: IntegrationContext): ApiRoute[] {\n return [\n this.#connectRoute(ctx),\n ...buildLinearRoutes({\n auth: ctx.auth,\n linear: this as unknown as LinearIntegration,\n stateSigner: ctx.stateSigner,\n baseUrl: ctx.baseUrl,\n intake: ctx.storage.intake,\n projects: ctx.storage.projects,\n ingestFactoryIssues: attachLinearRules(this, ctx),\n }).filter(route => !route.path.startsWith('/auth/linear/')),\n ];\n }\n\n #connectRoute(ctx: IntegrationContext): ApiRoute {\n return registerApiRoute('/auth/linear/connect', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n await ctx.auth.ensureUser(loose(c));\n const tenant = ctx.auth.tenant(loose(c));\n if (!tenant?.orgId) return c.json({ error: 'unauthorized' }, 401);\n\n const returnTo = c.req.query('return_to') || '/';\n const originator = routeBaseUrl(ctx, c.req.url);\n logPlatformInfo('Starting Platform Linear connect flow', {\n orgId: tenant.orgId,\n returnTo,\n originator,\n });\n const query = new URLSearchParams({ return_to: returnTo, originator });\n const location = await this.#client.requestRedirect('GET', `${API_PREFIX}/authorize?${query}`);\n return c.redirect(location);\n },\n });\n }\n\n async agentTools({ requestContext }: { requestContext: RequestContext }): Promise<IntegrationTools> {\n return buildLinearAgentTools({ requestContext, linear: this as unknown as LinearIntegration });\n }\n\n diagnostics(): Record<string, unknown> {\n return { mode: 'platform', endpointHost: this.#endpointHost };\n }\n\n async listProjects(): Promise<Array<LinearProject & { workspaceId: string }>> {\n return (await this.#listProjectSources()).map(({ workspace, project }) => ({\n ...project,\n id: encodeSourceId(workspace.linearWorkspaceId, project.id),\n workspaceId: workspace.linearWorkspaceId,\n }));\n }\n\n async #listProjectSources(): Promise<ProjectSource[]> {\n const workspaces = await this.#listWorkspaces();\n const projectGroups = await Promise.all(\n workspaces.map(async workspace => {\n const projects: LinearProject[] = [];\n let after: string | undefined;\n for (let page = 0; page < MAX_REFERENCE_PAGES; page += 1) {\n const query = new URLSearchParams({ first: '200' });\n if (after) query.set('after', after);\n const result = await this.#client.request<{ projects: LinearProject[]; pageInfo: PageInfo }>(\n 'GET',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspace.linearWorkspaceId)}/projects?${query}`,\n );\n projects.push(...result.projects);\n if (!result.pageInfo.hasNextPage || !result.pageInfo.endCursor) break;\n after = result.pageInfo.endCursor;\n }\n return projects.map(project => ({ workspace, project }));\n }),\n );\n return projectGroups.flat();\n }\n\n async listWorkspaces(): Promise<LinearWorkspace[]> {\n return this.#listWorkspaces();\n }\n\n async #listWorkspaces(): Promise<LinearWorkspace[]> {\n const result = await this.#client.request<{ workspaces: LinearWorkspace[] }>('GET', `${API_PREFIX}/workspaces`);\n return result.workspaces.filter(workspace => workspace.connected);\n }\n\n async #listIssues(sourceIds: string[], cursor?: string, labels?: string[]) {\n if (sourceIds.length === 0)\n return { issues: [] as Array<{ issue: LinearIssue; source: ProjectSource }>, nextCursor: null };\n const sources = await this.#listProjectSources();\n const sourceMap = new Map(\n sources.map(source => [encodeSourceId(source.workspace.linearWorkspaceId, source.project.id), source]),\n );\n const selected = sourceIds\n .map(sourceId => sourceMap.get(sourceId))\n .filter((source): source is ProjectSource => !!source);\n const cursors = decodeCursor(cursor, sourceIds);\n const normalizedLabels = normalizeLabels(labels);\n const nextState: Record<string, string | null> = {};\n let hasNextPage = false;\n const pages = await Promise.all(\n selected.map(async source => {\n const sourceId = encodeSourceId(source.workspace.linearWorkspaceId, source.project.id);\n if (cursors[sourceId] === null) {\n nextState[sourceId] = null;\n return [] as Array<{ issue: LinearIssue; source: ProjectSource }>;\n }\n const query = new URLSearchParams({\n first: String(PAGE_SIZE),\n projectIds: source.project.id,\n stateType: 'triage,backlog,unstarted,started',\n orderBy: 'updatedAt',\n });\n const after = cursors[sourceId];\n if (after) query.set('after', after);\n const result = await this.#client.request<{ issues: LinearIssue[]; pageInfo: PageInfo }>(\n 'GET',\n `${API_PREFIX}/workspaces/${encodeURIComponent(source.workspace.linearWorkspaceId)}/issues?${query}`,\n );\n const next = result.pageInfo.hasNextPage ? result.pageInfo.endCursor : null;\n nextState[sourceId] = next;\n hasNextPage ||= next !== null;\n return result.issues\n .filter(\n issue => normalizedLabels.length === 0 || issue.labels.some(label => normalizedLabels.includes(label.name)),\n )\n .map(issue => ({ issue, source }));\n }),\n );\n return {\n issues: pages.flat(),\n nextCursor: hasNextPage ? encodeCursor(nextState, sourceIds) : null,\n };\n }\n\n async #findIssue(\n sourceId: string | undefined,\n issueId: string,\n ): Promise<{\n workspaceId: string;\n issue: LinearIssue & { comments?: { nodes: LinearComment[]; pageInfo: PageInfo } };\n } | null> {\n const workspaceIds = await this.#candidateWorkspaceIds(sourceId);\n for (const workspaceId of workspaceIds) {\n try {\n const issue = await this.#client.request<\n LinearIssue & { comments?: { nodes: LinearComment[]; pageInfo: PageInfo } }\n >(\n 'GET',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspaceId)}/issues/${encodeURIComponent(issueId)}?include=comments`,\n );\n return { workspaceId, issue };\n } catch (error) {\n if (!isNotFound(error)) throw error;\n }\n }\n return null;\n }\n\n async #resolveWorkspaceForIssue(sourceId: string | undefined, issueId: string): Promise<string | null> {\n const workspaceIds = await this.#candidateWorkspaceIds(sourceId);\n if (workspaceIds.length === 1) return workspaceIds[0]!;\n for (const workspaceId of workspaceIds) {\n try {\n await this.#client.request<LinearIssue>(\n 'GET',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspaceId)}/issues/${encodeURIComponent(issueId)}`,\n );\n return workspaceId;\n } catch (error) {\n if (!isNotFound(error)) throw error;\n }\n }\n return null;\n }\n\n async #listWorkflowStates(workspaceId: string, teamId: string): Promise<LinearWorkflowState[]> {\n const cacheKey = `${workspaceId}:${teamId}`;\n const cached = this.#workflowStatesByTeam.get(cacheKey);\n if (cached) return cached;\n const result = await this.#client.request<{ workflowStates: LinearWorkflowState[]; pageInfo: PageInfo }>(\n 'GET',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspaceId)}/workflow-states?teamId=${encodeURIComponent(teamId)}&first=100`,\n );\n this.#workflowStatesByTeam.set(cacheKey, result.workflowStates);\n return result.workflowStates;\n }\n\n async #candidateWorkspaceIds(sourceId: string | undefined): Promise<string[]> {\n if (sourceId) return [decodeSourceId(sourceId).workspaceId];\n return (await this.#listWorkspaces()).map(workspace => workspace.linearWorkspaceId);\n }\n\n async #loadComments(\n workspaceId: string,\n issueId: string,\n embedded: { nodes: LinearComment[]; pageInfo: PageInfo } | undefined,\n ): Promise<LinearComment[]> {\n const comments = [...(embedded?.nodes ?? [])];\n let pageInfo = embedded?.pageInfo;\n let page = 0;\n while (pageInfo?.hasNextPage && pageInfo.endCursor && page < MAX_COMMENT_PAGES) {\n const result = await this.#client.request<{ comments: LinearComment[]; pageInfo: PageInfo }>(\n 'GET',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspaceId)}/issues/${encodeURIComponent(issueId)}/comments?first=200&after=${encodeURIComponent(pageInfo.endCursor)}`,\n );\n comments.push(...result.comments);\n pageInfo = result.pageInfo;\n page += 1;\n }\n return comments;\n }\n}\n\nfunction parseIssue(issue: LinearIssue): IntakeIssue {\n return {\n id: issue.id,\n identifier: issue.identifier,\n title: issue.title,\n url: issue.url,\n author: issue.creator?.displayName ?? issue.creator?.name ?? null,\n state: issue.state.name,\n stateType: issue.state.type,\n priority: issue.priorityLabel,\n assignee: issue.assignee?.displayName ?? issue.assignee?.name ?? null,\n source: issue.team.key,\n labels: issue.labels.map(label => label.name),\n commentCount: null,\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n };\n}\n\nfunction parseIssueDetail(issue: LinearIssue, comments: LinearComment[]): IntakeIssueDetail {\n return {\n ...parseIssue(issue),\n commentCount: comments.length,\n description: issue.description?.trim() ? issue.description : null,\n comments: comments.map(comment => ({\n author: comment.user?.displayName ?? comment.user?.name ?? null,\n body: comment.body,\n createdAt: comment.createdAt,\n })),\n };\n}\n\nfunction encodeSourceId(workspaceId: string, projectId: string): string {\n return `linear-project:${Buffer.from(JSON.stringify({ workspaceId, projectId })).toString('base64url')}`;\n}\n\nfunction decodeSourceId(sourceId: string): { workspaceId: string; projectId: string } {\n if (!sourceId.startsWith('linear-project:')) throw new Error('Linear project source id is invalid.');\n try {\n const parsed = JSON.parse(Buffer.from(sourceId.slice('linear-project:'.length), 'base64url').toString('utf8')) as {\n workspaceId?: unknown;\n projectId?: unknown;\n };\n if (typeof parsed.workspaceId !== 'string' || !parsed.workspaceId) throw new Error();\n if (typeof parsed.projectId !== 'string' || !parsed.projectId) throw new Error();\n return { workspaceId: parsed.workspaceId, projectId: parsed.projectId };\n } catch {\n throw new Error('Linear project source id is invalid.');\n }\n}\n\nfunction normalizeLabels(labels: string[] | undefined): string[] {\n return [...new Set((labels ?? []).map(label => label.trim()).filter(Boolean))];\n}\n\nfunction decodeCursor(cursor: string | undefined, sourceIds: string[]): Record<string, string | null | undefined> {\n if (!cursor) return {};\n if (sourceIds.length === 1) return { [sourceIds[0]!]: cursor };\n try {\n const parsed = JSON.parse(cursor) as unknown;\n if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) throw new Error();\n return parsed as Record<string, string | null>;\n } catch {\n throw new Error('Linear cursor is invalid.');\n }\n}\n\nfunction encodeCursor(state: Record<string, string | null>, sourceIds: string[]): string {\n if (sourceIds.length === 1) return state[sourceIds[0]!]!;\n return JSON.stringify(state);\n}\n\nfunction requireLinearConnection(connection: IntegrationConnection): void {\n if (connection.type !== 'oauth') {\n throw new Error('Linear capabilities require an OAuth connection.');\n }\n}\n\nfunction isNotFound(error: unknown): boolean {\n return error instanceof PlatformApiError && error.status === 404;\n}\n\nfunction optionalPositiveIntegerEnv(name: string): number | undefined {\n const value = process.env[name]?.trim();\n if (!value) return undefined;\n const parsed = Number(value);\n if (!Number.isSafeInteger(parsed) || parsed <= 0) {\n throw new Error(`${name} must be a positive integer.`);\n }\n return parsed;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"integration.js","names":["#client","#endpointHost","#workflowStatesByTeam","#resolveIntakeDispatch","#listProjectSources","#listTeamSources","#listIssues","#findIssue","#loadComments","#resolveWorkspaceForIssue","#listWorkflowStates","#rules","#projects","#auth","#listWorkspaces","#connectRoute","#candidateWorkspaceIds"],"sources":["../../../../src/integrations/platform/linear/integration.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport type { RequestContext } from '@mastra/core/request-context';\nimport type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { MastraWorker } from '@mastra/core/worker';\nimport type { Context } from 'hono';\n\nimport type { IntegrationConnection } from '../../../capabilities/connection.js';\nimport type { Intake, IntakeIssue, IntakeIssueDetail, UpdateIntakeIssueInput } from '../../../capabilities/intake.js';\nimport type { RouteAuth } from '../../../routes/route.js';\nimport type { FactoryProjectsStorage } from '../../../storage/domains/projects/base.js';\nimport type { FactoryIntegration, IntegrationContext, IntegrationTools } from '../../base.js';\nimport { buildLinearAgentTools } from '../../linear/agent-tools.js';\nimport type { LinearEventRules, LinearRuleOverrides } from '../../linear/default-rules.js';\nimport { resolveLinearRules } from '../../linear/default-rules.js';\nimport type {\n LinearConnectionCheck,\n LinearIntegration,\n LinearIssueDetail as LinearRouteIssueDetail,\n} from '../../linear/integration.js';\nimport { attachLinearIssueReconciler } from '../../linear/issue-reconciler.js';\nimport {\n linearIssueReconciliationEnabled,\n linearIssueReconciliationInterval,\n} from '../../linear/reconciliation-config.js';\nimport { buildLinearRoutes } from '../../linear/routes.js';\nimport { attachLinearRules } from '../../linear/rules.js';\nimport type { LinearConnectionData, LinearConnectionRow, LinearStorageHandle } from '../../linear/storage.js';\nimport {\n logPlatformInfo,\n logPlatformWarn,\n PlatformApiClient,\n PlatformApiError,\n platformApiClientConfigFromEnv,\n} from '../api-client.js';\nimport { PlatformLinearEventWorker } from './event-worker.js';\nimport type { PlatformLinearEventStorage } from './event-worker.js';\n\ntype PageInfo = { hasNextPage: boolean; endCursor: string | null };\ntype LinearUser = {\n id: string;\n name: string;\n displayName: string;\n email: string | null;\n avatarUrl: string | null;\n};\ntype LinearIssue = {\n id: string;\n identifier: string;\n number: number;\n title: string;\n description: string | null;\n url: string;\n priority: number;\n priorityLabel: string;\n labels: Array<{ id: string; name: string }>;\n state: { id: string; name: string; type: string };\n team: { id: string; key: string; name: string };\n project: { id: string } | null;\n assignee: LinearUser | null;\n creator: LinearUser | null;\n createdAt: string;\n updatedAt: string;\n archivedAt: string | null;\n};\ntype LinearComment = {\n id: string;\n body: string;\n url: string;\n issue: { id: string; identifier: string };\n user: LinearUser | null;\n parent: { id: string } | null;\n createdAt: string;\n updatedAt: string;\n};\ntype LinearWorkspace = {\n linearWorkspaceId: string;\n linearWorkspaceName: string;\n urlKey: string | null;\n connected: boolean;\n};\ntype LinearProject = {\n id: string;\n name: string;\n state: string;\n teams: Array<{ id: string; key: string; name: string }>;\n};\ntype LinearTeam = { id: string; key: string; name: string };\ntype ProjectSource = { workspace: LinearWorkspace; project: LinearProject };\ntype TeamSource = { workspace: LinearWorkspace; team: LinearTeam };\ntype LinearWorkflowState = {\n id: string;\n name: string;\n type: string;\n position: number;\n teamId: string;\n};\n\nconst API_PREFIX = '/v1/server/linear';\nconst PAGE_SIZE = 30;\nconst MAX_REFERENCE_PAGES = 20;\nconst MAX_COMMENT_PAGES = 20;\nconst PLATFORM_MANAGED_CONNECTION_TOKEN = 'platform-managed';\n\nfunction loose(c: unknown): Context {\n return c as Context;\n}\n\nfunction routeBaseUrl(ctx: IntegrationContext, requestUrl: string): string {\n return (ctx.baseUrl || new URL(requestUrl).origin).replace(/\\/+$/, '');\n}\n\nexport class PlatformLinearIntegration implements FactoryIntegration {\n readonly id = 'linear';\n readonly #client: PlatformApiClient;\n readonly #endpointHost: string;\n #projects: FactoryProjectsStorage | undefined;\n #auth: RouteAuth | undefined;\n /**\n * Per-instance workflow-state cache keyed by `${workspaceId}:${teamId}`. Scoped to the process\n * lifetime; not shared across requests intentionally to keep failure modes simple (stale states\n * clear on process restart). Populated lazily on first `updateIssue` per team.\n */\n readonly #workflowStatesByTeam = new Map<string, LinearWorkflowState[]>();\n\n readonly intake: Intake = {\n resolveIntakeDispatch: input => this.#resolveIntakeDispatch(input),\n listSources: async () => {\n const [projectSources, teamSources] = await Promise.all([this.#listProjectSources(), this.#listTeamSources()]);\n const projects = projectSources.map(({ workspace, project }) => ({\n id: encodeSourceId(workspace.linearWorkspaceId, project.id),\n name: project.name,\n type: 'project' as const,\n metadata: {\n workspaceId: workspace.linearWorkspaceId,\n workspaceName: workspace.linearWorkspaceName,\n workspaceUrlKey: workspace.urlKey,\n state: project.state,\n teams: project.teams,\n },\n }));\n const teams = teamSources.map(({ workspace, team }) => ({\n id: encodeTeamSourceId(workspace.linearWorkspaceId, team.id),\n name: team.name,\n type: 'team' as const,\n metadata: {\n workspaceId: workspace.linearWorkspaceId,\n workspaceName: workspace.linearWorkspaceName,\n workspaceUrlKey: workspace.urlKey,\n teamKey: team.key,\n },\n }));\n return [...projects, ...teams];\n },\n listItems: async ({ sourceIds, attributionSourceIds, cursor }) => {\n const result = await this.#listIssues(sourceIds, cursor, undefined, attributionSourceIds);\n return {\n items: result.issues.map(({ issue, sourceId, workspace }) => ({\n source: { type: 'issue', externalId: issue.id, url: issue.url },\n sourceId,\n title: issue.title,\n status: issue.state.name,\n labels: issue.labels.map(label => label.name),\n assignee: issue.assignee?.displayName ?? issue.assignee?.name ?? null,\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n metadata: {\n identifier: issue.identifier,\n workspaceId: workspace.linearWorkspaceId,\n workspaceName: workspace.linearWorkspaceName,\n projectId: issue.project?.id ?? null,\n team: issue.team.key,\n priority: issue.priorityLabel,\n },\n })),\n nextCursor: result.nextCursor,\n };\n },\n listIssues: async ({ connection, sourceIds, attributionSourceIds, labels, cursor }) => {\n requireLinearConnection(connection);\n const result = await this.#listIssues(sourceIds, cursor, labels, attributionSourceIds);\n return {\n issues: result.issues.map(({ issue, sourceId }) => ({\n ...parseIssue(issue),\n sourceId,\n })),\n nextCursor: result.nextCursor,\n };\n },\n getIssue: async ({ connection, sourceId, issueId }) => {\n requireLinearConnection(connection);\n const located = await this.#findIssue(sourceId, issueId);\n if (!located) return null;\n const comments = await this.#loadComments(located.workspaceId, issueId, located.issue.comments);\n return parseIssueDetail(located.issue, comments);\n },\n createComment: async ({ connection, sourceId, issueId, body }) => {\n requireLinearConnection(connection);\n const workspaceId = await this.#resolveWorkspaceForIssue(sourceId, issueId);\n if (!workspaceId) return null;\n try {\n const comment = await this.#client.request<LinearComment>(\n 'POST',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspaceId)}/issues/${encodeURIComponent(issueId)}/comments`,\n { body },\n );\n return { id: comment.id, url: comment.url };\n } catch (error) {\n if (isNotFound(error)) return null;\n throw error;\n }\n },\n updateIssue: async (input: UpdateIntakeIssueInput): Promise<IntakeIssue | null> => {\n requireLinearConnection(input.connection);\n const located = await this.#findIssue(input.sourceId, input.issueId);\n if (!located) return null;\n const { workspaceId, issue } = located;\n\n const target = input.state;\n // Idempotency: skip the write entirely if the issue is already at the target state.\n if (target.kind === 'byType' && issue.state.type === target.stateType) {\n return parseIssue(issue);\n }\n if (target.kind === 'byName' && issue.state.name.toLowerCase() === target.name.toLowerCase()) {\n return parseIssue(issue);\n }\n\n let states: LinearWorkflowState[];\n try {\n states = await this.#listWorkflowStates(workspaceId, issue.team.id);\n } catch (error) {\n if (isNotFound(error)) {\n // Platform companion endpoint not deployed yet — degrade to policy skip so PR 1\n // is standalone-mergeable ahead of the platform-side workflow-states route landing.\n logPlatformWarn('Platform Linear: workflow-states endpoint not available; skipping updateIssue.', {\n workspaceId,\n teamId: issue.team.id,\n });\n return null;\n }\n throw error;\n }\n let matched: LinearWorkflowState | undefined;\n if (target.kind === 'byType') {\n matched = states\n .slice()\n .sort((a, b) => a.position - b.position)\n .find(s => s.type === target.stateType);\n } else {\n const wanted = target.name.toLowerCase();\n matched = states.find(s => s.name.toLowerCase() === wanted);\n }\n if (!matched) {\n logPlatformWarn('Platform Linear: no workflow state matched target; skipping updateIssue.', {\n workspaceId,\n teamId: issue.team.id,\n target,\n });\n return null;\n }\n\n try {\n await this.#client.request<LinearIssue>(\n 'PATCH',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspaceId)}/issues/${encodeURIComponent(input.issueId)}`,\n { stateId: matched.id },\n );\n } catch (error) {\n if (isNotFound(error)) return null;\n throw error;\n }\n\n const refreshed = await this.#findIssue(input.sourceId, input.issueId);\n return refreshed ? parseIssue(refreshed.issue) : null;\n },\n };\n\n readonly #rules: LinearEventRules;\n\n get rules(): LinearEventRules {\n return this.#rules;\n }\n\n constructor({ rules }: { rules?: LinearRuleOverrides } = {}) {\n this.#rules = resolveLinearRules(rules);\n const config = platformApiClientConfigFromEnv();\n this.#client = new PlatformApiClient(config);\n this.#endpointHost = new URL(config.baseUrl).host;\n }\n\n get storage(): LinearStorageHandle {\n const now = new Date();\n return {\n integrationId: this.id,\n connections: {\n get: async (orgId: string) => ({\n id: `platform-linear:${orgId}`,\n orgId,\n userId: null,\n data: {\n accessToken: PLATFORM_MANAGED_CONNECTION_TOKEN,\n refreshToken: null,\n expiresAtMs: null,\n scope: 'read,comments:create',\n workspaceName: null,\n workspaceUrlKey: null,\n } satisfies LinearConnectionData,\n metadata: {},\n createdAt: now,\n updatedAt: now,\n }),\n },\n } as unknown as LinearStorageHandle;\n }\n\n get projects(): FactoryProjectsStorage {\n if (!this.#projects) throw new Error('PlatformLinearIntegration projects storage has not been initialized.');\n return this.#projects;\n }\n\n initialize({ projects, auth }: { projects: FactoryProjectsStorage; auth?: RouteAuth }): void {\n this.#projects = projects;\n this.#auth = auth;\n logPlatformInfo('Platform Linear integration initialized', { endpointHost: this.#endpointHost });\n }\n\n get authEnabled(): boolean {\n return this.#auth?.enabled() ?? false;\n }\n\n async resolveOrgId(resourceId: string): Promise<string | null> {\n try {\n const project = await this.projects.getById({ id: resourceId });\n return project?.orgId ?? null;\n } catch {\n return null;\n }\n }\n\n async loadConnection(orgId: string): Promise<LinearConnectionRow | null> {\n const workspace = (await this.#listWorkspaces())[0];\n if (!workspace) return null;\n const now = new Date();\n return {\n id: `platform-linear:${orgId}`,\n orgId,\n userId: null,\n accessToken: PLATFORM_MANAGED_CONNECTION_TOKEN,\n scope: 'read,comments:create',\n refreshToken: null,\n expiresAt: null,\n workspaceName: workspace.linearWorkspaceName,\n workspaceUrlKey: workspace.urlKey,\n createdAt: now,\n updatedAt: now,\n };\n }\n\n async getFreshAccessToken(_connection: LinearConnectionRow): Promise<string> {\n return PLATFORM_MANAGED_CONNECTION_TOKEN;\n }\n\n /**\n * Background-dispatch context: platform-managed connection token when the\n * org has a connected workspace. Linear work items store the issue UUID\n * directly as `externalId`.\n */\n async #resolveIntakeDispatch({\n orgId,\n externalSource,\n }: {\n orgId: string;\n externalSource: { type: string; externalId: string };\n }): Promise<{ connection: IntegrationConnection; issueId: string } | null> {\n if (externalSource.type !== 'issue') return null;\n const connection = await this.loadConnection(orgId);\n if (!connection) return null;\n return {\n connection: { type: 'oauth', accessToken: PLATFORM_MANAGED_CONNECTION_TOKEN },\n issueId: externalSource.externalId,\n };\n }\n\n canPostComments(connection: LinearConnectionRow): boolean {\n const scopes = (connection.scope ?? '').split(/[\\s,]+/).filter(Boolean);\n return scopes.some(scope => scope === 'comments:create' || scope === 'write' || scope === 'admin');\n }\n\n async checkConnection(orgId: string): Promise<LinearConnectionCheck> {\n const connection = await this.loadConnection(orgId);\n return {\n connected: connection !== null,\n canComment: connection !== null && this.canPostComments(connection),\n checkedAt: Date.now(),\n };\n }\n\n workers(ctx: IntegrationContext): MastraWorker[] {\n const pollingEnabled = process.env.MASTRACODE_PLATFORM_LINEAR_POLLING_ENABLED?.trim().toLowerCase() !== 'false';\n const reconcileEnabled = linearIssueReconciliationEnabled();\n if (!pollingEnabled && !reconcileEnabled) return [];\n\n const ingest = attachLinearRules(this, ctx);\n const reconcile = reconcileEnabled ? attachLinearIssueReconciler(this, ctx) : undefined;\n const workItems = ctx.runtime?.workItems;\n if (!workItems) return [];\n if (!ingest && !reconcile) return [];\n\n const pollIntervalMs = optionalPositiveIntegerEnv('MASTRACODE_PLATFORM_LINEAR_POLLING_INTERVAL_MS');\n const reconcileIntervalMs = linearIssueReconciliationInterval();\n\n return [\n new PlatformLinearEventWorker({\n client: this.#client,\n linear: { listWorkspaces: () => this.listWorkspaces() },\n storage: ctx.storage.generic as unknown as PlatformLinearEventStorage,\n projects: ctx.storage.projects,\n workItems,\n ...(ingest ? { ingestFactoryIssue: ingest } : {}),\n ...(reconcile ? { reconcileFactoryState: reconcile } : {}),\n pollEventsEnabled: pollingEnabled,\n ...(pollIntervalMs !== undefined ? { intervalMs: pollIntervalMs } : {}),\n ...(reconcileIntervalMs !== undefined ? { reconcileIntervalMs } : {}),\n }),\n ];\n }\n\n routes(ctx: IntegrationContext): ApiRoute[] {\n return [\n this.#connectRoute(ctx),\n ...buildLinearRoutes({\n auth: ctx.auth,\n linear: this as unknown as LinearIntegration,\n stateSigner: ctx.stateSigner,\n baseUrl: ctx.baseUrl,\n intake: ctx.storage.intake,\n projects: ctx.storage.projects,\n ingestFactoryIssues: attachLinearRules(this, ctx),\n workItems: ctx.runtime?.workItems,\n boards: ctx.runtime?.boards,\n }).filter(route => !route.path.startsWith('/auth/linear/')),\n ];\n }\n\n #connectRoute(ctx: IntegrationContext): ApiRoute {\n return registerApiRoute('/auth/linear/connect', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n await ctx.auth.ensureUser(loose(c));\n const tenant = ctx.auth.tenant(loose(c));\n if (!tenant?.orgId) return c.json({ error: 'unauthorized' }, 401);\n\n const returnTo = c.req.query('return_to') || '/';\n const originator = routeBaseUrl(ctx, c.req.url);\n logPlatformInfo('Starting Platform Linear connect flow', {\n orgId: tenant.orgId,\n returnTo,\n originator,\n });\n const query = new URLSearchParams({ return_to: returnTo, originator });\n const location = await this.#client.requestRedirect('GET', `${API_PREFIX}/authorize?${query}`);\n return c.redirect(location);\n },\n });\n }\n\n async agentTools({ requestContext }: { requestContext: RequestContext }): Promise<IntegrationTools> {\n return buildLinearAgentTools({ requestContext, linear: this as unknown as LinearIntegration });\n }\n\n diagnostics(): Record<string, unknown> {\n return { mode: 'platform', endpointHost: this.#endpointHost };\n }\n\n async listProjects(): Promise<Array<LinearProject & { workspaceId: string }>> {\n return (await this.#listProjectSources()).map(({ workspace, project }) => ({\n ...project,\n id: encodeSourceId(workspace.linearWorkspaceId, project.id),\n workspaceId: workspace.linearWorkspaceId,\n }));\n }\n\n async listTeams(_accessToken: string): Promise<Array<LinearTeam & { workspaceId: string; sourceId: string }>> {\n return (await this.#listTeamSources()).map(({ workspace, team }) => ({\n ...team,\n workspaceId: workspace.linearWorkspaceId,\n sourceId: encodeTeamSourceId(workspace.linearWorkspaceId, team.id),\n }));\n }\n\n async fetchIssueDetail(\n _accessToken: string,\n idOrIdentifier: string,\n sourceIds?: string[],\n routedSourceIds?: string[],\n ): Promise<LinearRouteIssueDetail | null> {\n const located = await this.#findIssue(undefined, idOrIdentifier, sourceIds, routedSourceIds);\n if (!located) return null;\n const comments = await this.#loadComments(located.workspaceId, located.issue.id, located.issue.comments);\n const issue = located.issue;\n return {\n id: issue.id,\n projectId: issue.project?.id ?? null,\n workspaceId: located.workspaceId,\n teamId: issue.team.id,\n identifier: issue.identifier,\n title: issue.title,\n description: issue.description?.trim() ? issue.description : null,\n url: issue.url,\n state: issue.state.name,\n stateType: issue.state.type,\n priorityLabel: issue.priorityLabel,\n assignee: issue.assignee?.displayName ?? issue.assignee?.name ?? null,\n creator: issue.creator?.displayName ?? issue.creator?.name ?? null,\n team: issue.team.key,\n labels: issue.labels.map(label => label.name),\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n comments: comments.map(comment => ({\n author: comment.user?.displayName ?? comment.user?.name ?? null,\n body: comment.body,\n createdAt: comment.createdAt,\n })),\n };\n }\n\n sourceMatchesIssue(\n sourceId: string,\n issue: Pick<LinearRouteIssueDetail, 'workspaceId' | 'projectId' | 'teamId'>,\n ): boolean {\n const source = parseSourceId(sourceId);\n if (issue.workspaceId !== source.workspaceId) return false;\n return source.kind === 'team' ? issue.teamId === source.teamId : issue.projectId === source.projectId;\n }\n\n async #listProjectSources(workspaceIds?: ReadonlySet<string>): Promise<ProjectSource[]> {\n const workspaces = await this.#listWorkspaces();\n const scopedWorkspaces = workspaceIds\n ? workspaces.filter(workspace => workspaceIds.has(workspace.linearWorkspaceId))\n : workspaces;\n const projectGroups = await Promise.all(\n scopedWorkspaces.map(async workspace => {\n const projects: LinearProject[] = [];\n let after: string | undefined;\n for (let page = 0; page < MAX_REFERENCE_PAGES; page += 1) {\n const query = new URLSearchParams({ first: '200' });\n if (after) query.set('after', after);\n const result = await this.#client.request<{ projects: LinearProject[]; pageInfo: PageInfo }>(\n 'GET',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspace.linearWorkspaceId)}/projects?${query}`,\n );\n projects.push(...result.projects);\n if (!result.pageInfo.hasNextPage || !result.pageInfo.endCursor) break;\n // A page that hands back the cursor it was asked for would replay forever.\n if (result.pageInfo.endCursor === after) throw invalidLinearCursor();\n after = result.pageInfo.endCursor;\n }\n return projects.map(project => ({ workspace, project }));\n }),\n );\n return projectGroups.flat();\n }\n\n async #listTeamSources(workspaceIds?: ReadonlySet<string>): Promise<TeamSource[]> {\n const workspaces = await this.#listWorkspaces();\n const scopedWorkspaces = workspaceIds\n ? workspaces.filter(workspace => workspaceIds.has(workspace.linearWorkspaceId))\n : workspaces;\n const teamGroups = await Promise.all(\n scopedWorkspaces.map(async workspace => {\n const teams: LinearTeam[] = [];\n let after: string | undefined;\n for (let page = 0; page < MAX_REFERENCE_PAGES; page += 1) {\n const query = new URLSearchParams({ first: '200' });\n if (after) query.set('after', after);\n const result = await this.#client.request<{ teams: LinearTeam[]; pageInfo: PageInfo }>(\n 'GET',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspace.linearWorkspaceId)}/teams?${query}`,\n );\n teams.push(...result.teams);\n if (!result.pageInfo.hasNextPage || !result.pageInfo.endCursor) break;\n // A page that hands back the cursor it was asked for would replay forever.\n if (result.pageInfo.endCursor === after) throw invalidLinearCursor();\n after = result.pageInfo.endCursor;\n }\n return teams.map(team => ({ workspace, team }));\n }),\n );\n return teamGroups.flat();\n }\n\n async listWorkspaces(): Promise<LinearWorkspace[]> {\n return this.#listWorkspaces();\n }\n\n async #listWorkspaces(): Promise<LinearWorkspace[]> {\n const result = await this.#client.request<{ workspaces: LinearWorkspace[] }>('GET', `${API_PREFIX}/workspaces`);\n return result.workspaces.filter(workspace => workspace.connected);\n }\n\n async #listIssues(sourceIds: string[], cursor?: string, labels?: string[], attributionSourceIds = sourceIds) {\n type ListedIssue = { issue: LinearIssue; sourceId: string; workspace: LinearWorkspace };\n if (sourceIds.length === 0) return { issues: [] as ListedIssue[], nextCursor: null };\n\n // Resolve every selected source to a concrete listing descriptor. Project\n // and team sources are listed separately: a project source filters by\n // `projectIds`, a team source filters by `teamId` (and returns projectless\n // issues too). Filters are never combined on one request.\n const parsedSources = sourceIds.map(sourceId => parseSourceId(sourceId));\n const projectWorkspaceIds = new Set(\n parsedSources.flatMap(source => (source.kind === 'project' ? [source.workspaceId] : [])),\n );\n const teamWorkspaceIds = new Set(\n parsedSources.flatMap(source => (source.kind === 'team' ? [source.workspaceId] : [])),\n );\n const [projectSources, teamSources] = await Promise.all([\n projectWorkspaceIds.size > 0\n ? this.#listProjectSources(projectWorkspaceIds)\n : Promise.resolve([] as ProjectSource[]),\n teamWorkspaceIds.size > 0 ? this.#listTeamSources(teamWorkspaceIds) : Promise.resolve([] as TeamSource[]),\n ]);\n const projectMap = new Map(\n projectSources.map(source => [encodeSourceId(source.workspace.linearWorkspaceId, source.project.id), source]),\n );\n const teamMap = new Map(\n teamSources.map(source => [encodeTeamSourceId(source.workspace.linearWorkspaceId, source.team.id), source]),\n );\n\n type Descriptor = {\n kind: 'project' | 'team';\n sourceId: string;\n workspace: LinearWorkspace;\n query: URLSearchParams;\n };\n const descriptors: Descriptor[] = [];\n const selectedProjectIdsByWorkspace = new Map<string, Set<string>>();\n for (const attributionSourceId of attributionSourceIds) {\n const source = parseSourceId(attributionSourceId);\n if (source.kind !== 'project') continue;\n const selectedProjectIds = selectedProjectIdsByWorkspace.get(source.workspaceId) ?? new Set<string>();\n selectedProjectIds.add(source.projectId);\n selectedProjectIdsByWorkspace.set(source.workspaceId, selectedProjectIds);\n }\n for (const sourceId of sourceIds) {\n const projectSource = projectMap.get(sourceId);\n if (projectSource) {\n const query = new URLSearchParams({\n first: String(PAGE_SIZE),\n projectIds: projectSource.project.id,\n stateType: 'triage,backlog,unstarted,started',\n orderBy: 'updatedAt',\n });\n const workspaceId = projectSource.workspace.linearWorkspaceId;\n const selectedProjectIds = selectedProjectIdsByWorkspace.get(workspaceId) ?? new Set<string>();\n selectedProjectIds.add(projectSource.project.id);\n selectedProjectIdsByWorkspace.set(workspaceId, selectedProjectIds);\n descriptors.push({ kind: 'project', sourceId, workspace: projectSource.workspace, query });\n continue;\n }\n const teamSource = teamMap.get(sourceId);\n if (teamSource) {\n const query = new URLSearchParams({\n first: String(PAGE_SIZE),\n teamId: teamSource.team.id,\n stateType: 'triage,backlog,unstarted,started',\n orderBy: 'updatedAt',\n });\n descriptors.push({ kind: 'team', sourceId, workspace: teamSource.workspace, query });\n }\n }\n\n const cursors = decodeCursor(cursor, sourceIds, attributionSourceIds);\n const normalizedLabels = normalizeLabels(labels);\n const nextState: Record<string, string | null> = {};\n let hasNextPage = false;\n const pages = await Promise.all(\n descriptors.map(async ({ kind, sourceId, workspace, query }) => {\n if (cursors[sourceId] === null) {\n nextState[sourceId] = null;\n return [] as ListedIssue[];\n }\n const after = cursors[sourceId];\n if (after) query.set('after', after);\n const result = await this.#client.request<{ issues: LinearIssue[]; pageInfo: PageInfo }>(\n 'GET',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspace.linearWorkspaceId)}/issues?${query}`,\n );\n const next = result.pageInfo.hasNextPage ? result.pageInfo.endCursor : null;\n // A page that hands back the cursor it was asked for would replay forever.\n if (next !== null && next === after) throw invalidLinearCursor();\n nextState[sourceId] = next;\n hasNextPage ||= next !== null;\n return result.issues\n .filter(\n issue => normalizedLabels.length === 0 || issue.labels.some(label => normalizedLabels.includes(label.name)),\n )\n .filter(\n issue =>\n kind === 'project' ||\n issue.project === null ||\n !selectedProjectIdsByWorkspace.get(workspace.linearWorkspaceId)?.has(issue.project.id),\n )\n .map(issue => ({ issue, sourceId, workspace }));\n }),\n );\n return {\n issues: dedupeIssuesBySource(pages.flat(), sourceIds),\n nextCursor: hasNextPage ? encodeCursor(nextState, sourceIds, attributionSourceIds) : null,\n };\n }\n\n async #findIssue(\n sourceId: string | undefined,\n issueId: string,\n sourceIds?: string[],\n routedSourceIds?: string[],\n ): Promise<{\n workspaceId: string;\n issue: LinearIssue & { comments?: { nodes: LinearComment[]; pageInfo: PageInfo } };\n } | null> {\n const scopedSourceIds = sourceIds ?? (sourceId ? [sourceId] : undefined);\n const scopedSources = scopedSourceIds?.map(sourceKey => ({\n sourceKey,\n source: parseSourceId(sourceKey),\n }));\n const routedSourceSet = routedSourceIds ? new Set(routedSourceIds) : undefined;\n const workspaceIds = routedSourceIds\n ? [...new Set(routedSourceIds.map(sourceKey => parseSourceId(sourceKey).workspaceId))]\n : scopedSources\n ? [...new Set(scopedSources.map(({ source }) => source.workspaceId))]\n : await this.#candidateWorkspaceIds(undefined);\n for (const workspaceId of workspaceIds) {\n try {\n const issue = await this.#client.request<\n LinearIssue & { comments?: { nodes: LinearComment[]; pageInfo: PageInfo } }\n >(\n 'GET',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspaceId)}/issues/${encodeURIComponent(issueId)}?include=comments`,\n );\n if (scopedSources) {\n const matching = scopedSources.filter(\n ({ source }) =>\n source.workspaceId === workspaceId &&\n (source.kind === 'team' ? issue.team.id === source.teamId : issue.project?.id === source.projectId),\n );\n const winner = matching.find(({ source }) => source.kind === 'project') ?? matching[0];\n if (!winner || (routedSourceSet && !routedSourceSet.has(winner.sourceKey))) continue;\n }\n return { workspaceId, issue };\n } catch (error) {\n if (!isNotFound(error)) throw error;\n }\n }\n return null;\n }\n\n async #resolveWorkspaceForIssue(sourceId: string | undefined, issueId: string): Promise<string | null> {\n const workspaceIds = await this.#candidateWorkspaceIds(sourceId);\n if (workspaceIds.length === 1) return workspaceIds[0]!;\n for (const workspaceId of workspaceIds) {\n try {\n await this.#client.request<LinearIssue>(\n 'GET',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspaceId)}/issues/${encodeURIComponent(issueId)}`,\n );\n return workspaceId;\n } catch (error) {\n if (!isNotFound(error)) throw error;\n }\n }\n return null;\n }\n\n async #listWorkflowStates(workspaceId: string, teamId: string): Promise<LinearWorkflowState[]> {\n const cacheKey = `${workspaceId}:${teamId}`;\n const cached = this.#workflowStatesByTeam.get(cacheKey);\n if (cached) return cached;\n const result = await this.#client.request<{ workflowStates: LinearWorkflowState[]; pageInfo: PageInfo }>(\n 'GET',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspaceId)}/workflow-states?teamId=${encodeURIComponent(teamId)}&first=100`,\n );\n this.#workflowStatesByTeam.set(cacheKey, result.workflowStates);\n return result.workflowStates;\n }\n\n async #candidateWorkspaceIds(sourceId: string | undefined): Promise<string[]> {\n if (sourceId) return [parseSourceId(sourceId).workspaceId];\n return (await this.#listWorkspaces()).map(workspace => workspace.linearWorkspaceId);\n }\n\n async #loadComments(\n workspaceId: string,\n issueId: string,\n embedded: { nodes: LinearComment[]; pageInfo: PageInfo } | undefined,\n ): Promise<LinearComment[]> {\n const comments = [...(embedded?.nodes ?? [])];\n let pageInfo = embedded?.pageInfo;\n let page = 0;\n while (pageInfo?.hasNextPage && pageInfo.endCursor && page < MAX_COMMENT_PAGES) {\n const result = await this.#client.request<{ comments: LinearComment[]; pageInfo: PageInfo }>(\n 'GET',\n `${API_PREFIX}/workspaces/${encodeURIComponent(workspaceId)}/issues/${encodeURIComponent(issueId)}/comments?first=200&after=${encodeURIComponent(pageInfo.endCursor)}`,\n );\n comments.push(...result.comments);\n if (result.pageInfo.hasNextPage && result.pageInfo.endCursor === pageInfo.endCursor) throw invalidLinearCursor();\n pageInfo = result.pageInfo;\n page += 1;\n }\n return comments;\n }\n}\n\nfunction parseIssue(issue: LinearIssue): IntakeIssue {\n return {\n id: issue.id,\n identifier: issue.identifier,\n title: issue.title,\n url: issue.url,\n author: issue.creator?.displayName ?? issue.creator?.name ?? null,\n state: issue.state.name,\n stateType: issue.state.type,\n priority: issue.priorityLabel,\n assignee: issue.assignee?.displayName ?? issue.assignee?.name ?? null,\n source: issue.team.key,\n labels: issue.labels.map(label => label.name),\n commentCount: null,\n createdAt: issue.createdAt,\n updatedAt: issue.updatedAt,\n };\n}\n\nfunction parseIssueDetail(issue: LinearIssue, comments: LinearComment[]): IntakeIssueDetail {\n return {\n ...parseIssue(issue),\n commentCount: comments.length,\n description: issue.description?.trim() ? issue.description : null,\n comments: comments.map(comment => ({\n author: comment.user?.displayName ?? comment.user?.name ?? null,\n body: comment.body,\n createdAt: comment.createdAt,\n })),\n };\n}\n\n/**\n * Deduplicate issues that surfaced from more than one selected source (a team\n * source and one of its projects both select the same issue). Keeps one entry\n * per Linear issue UUID.\n *\n * Tie-breaker — MOST-SPECIFIC-WINS: a project source beats a team source, so an\n * issue that belongs to a selected project is attributed to that project's\n * source (and therefore routes to the project's board). The team source covers\n * only the remainder — projectless issues and issues in projects that were not\n * separately selected. `selectionOrder` breaks a same-kind tie deterministically\n * by the order the sources were selected.\n *\n * This function is the single precedence point; change the comparison here to\n * change the routing policy.\n */\nfunction dedupeIssuesBySource<T extends { issue: { id: string }; sourceId: string }>(\n entries: T[],\n selectionOrder: string[],\n): T[] {\n const rank = new Map(selectionOrder.map((id, index) => [id, index]));\n const specificity = (sourceId: string): number => (parseSourceId(sourceId).kind === 'project' ? 0 : 1);\n const winners = new Map<string, T>();\n for (const entry of entries) {\n const existing = winners.get(entry.issue.id);\n if (!existing) {\n winners.set(entry.issue.id, entry);\n continue;\n }\n const bySpecificity = specificity(entry.sourceId) - specificity(existing.sourceId);\n const better =\n bySpecificity < 0 ||\n (bySpecificity === 0 &&\n (rank.get(entry.sourceId) ?? Number.MAX_SAFE_INTEGER) <\n (rank.get(existing.sourceId) ?? Number.MAX_SAFE_INTEGER));\n if (better) winners.set(entry.issue.id, entry);\n }\n // Preserve first-seen order of the surviving issues.\n const seen = new Set<string>();\n const ordered: T[] = [];\n for (const entry of entries) {\n if (seen.has(entry.issue.id)) continue;\n seen.add(entry.issue.id);\n ordered.push(winners.get(entry.issue.id)!);\n }\n return ordered;\n}\n\nexport function encodeSourceId(workspaceId: string, projectId: string): string {\n return `linear-project:${Buffer.from(JSON.stringify({ workspaceId, projectId })).toString('base64url')}`;\n}\n\nfunction decodeSourceId(sourceId: string): { workspaceId: string; projectId: string } {\n if (!sourceId.startsWith('linear-project:')) throw new Error('Linear project source id is invalid.');\n try {\n const parsed = JSON.parse(Buffer.from(sourceId.slice('linear-project:'.length), 'base64url').toString('utf8')) as {\n workspaceId?: unknown;\n projectId?: unknown;\n };\n if (typeof parsed.workspaceId !== 'string' || !parsed.workspaceId) throw new Error();\n if (typeof parsed.projectId !== 'string' || !parsed.projectId) throw new Error();\n return { workspaceId: parsed.workspaceId, projectId: parsed.projectId };\n } catch {\n throw new Error('Linear project source id is invalid.');\n }\n}\n\nconst TEAM_SOURCE_PREFIX = 'linear-team:';\n\nexport function encodeTeamSourceId(workspaceId: string, teamId: string): string {\n return `${TEAM_SOURCE_PREFIX}${Buffer.from(JSON.stringify({ workspaceId, teamId })).toString('base64url')}`;\n}\n\nexport function decodeTeamSourceId(sourceId: string): { workspaceId: string; teamId: string } {\n if (!sourceId.startsWith(TEAM_SOURCE_PREFIX)) throw new Error('Linear team source id is invalid.');\n try {\n const parsed = JSON.parse(Buffer.from(sourceId.slice(TEAM_SOURCE_PREFIX.length), 'base64url').toString('utf8')) as {\n workspaceId?: unknown;\n teamId?: unknown;\n };\n if (typeof parsed.workspaceId !== 'string' || !parsed.workspaceId) throw new Error();\n if (typeof parsed.teamId !== 'string' || !parsed.teamId) throw new Error();\n return { workspaceId: parsed.workspaceId, teamId: parsed.teamId };\n } catch {\n throw new Error('Linear team source id is invalid.');\n }\n}\n\n/**\n * A decoded intake source: either a Linear project or a whole Linear team.\n * Downstream code discriminates on `kind` to build the right issue-listing\n * filter and to apply most-specific-wins routing (project beats team).\n */\nexport type DecodedSource =\n { kind: 'project'; workspaceId: string; projectId: string } | { kind: 'team'; workspaceId: string; teamId: string };\n\n/** Discriminate any Linear source id by its prefix and decode it. */\nexport function parseSourceId(sourceId: string): DecodedSource {\n if (sourceId.startsWith(TEAM_SOURCE_PREFIX)) {\n return { kind: 'team', ...decodeTeamSourceId(sourceId) };\n }\n return { kind: 'project', ...decodeSourceId(sourceId) };\n}\n\nfunction normalizeLabels(labels: string[] | undefined): string[] {\n return [...new Set((labels ?? []).map(label => label.trim()).filter(Boolean))];\n}\n\ntype PlatformListCursor = { v: 1; sourceSet: string; cursors: Array<string | null> };\n\nfunction canonicalSourceIds(sourceIds: string[]): string[] {\n return [...new Set(sourceIds)].sort();\n}\n\nfunction linearSourceSetFingerprint(sourceIds: string[], attributionSourceIds: string[]): string {\n const scope = {\n sourceIds: canonicalSourceIds(sourceIds),\n attributionSourceIds: canonicalSourceIds(attributionSourceIds),\n };\n return createHash('sha256').update(JSON.stringify(scope)).digest('base64url');\n}\n\nfunction invalidLinearCursor(): Error {\n return Object.assign(new Error('Linear cursor is invalid or stale.'), { code: 'invalid_cursor' as const });\n}\n\nfunction decodeCursor(\n cursor: string | undefined,\n sourceIds: string[],\n attributionSourceIds: string[],\n): Record<string, string | null | undefined> {\n if (!cursor) return {};\n try {\n const parsed = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8')) as Partial<PlatformListCursor>;\n const canonical = canonicalSourceIds(sourceIds);\n if (\n parsed.v !== 1 ||\n parsed.sourceSet !== linearSourceSetFingerprint(sourceIds, attributionSourceIds) ||\n !Array.isArray(parsed.cursors) ||\n parsed.cursors.length !== canonical.length ||\n !parsed.cursors.every(value => value === null || typeof value === 'string')\n ) {\n throw invalidLinearCursor();\n }\n return Object.fromEntries(canonical.map((sourceId, index) => [sourceId, parsed.cursors![index]]));\n } catch {\n throw invalidLinearCursor();\n }\n}\n\nfunction encodeCursor(\n state: Record<string, string | null>,\n sourceIds: string[],\n attributionSourceIds: string[],\n): string {\n const canonical = canonicalSourceIds(sourceIds);\n const cursor: PlatformListCursor = {\n v: 1,\n sourceSet: linearSourceSetFingerprint(sourceIds, attributionSourceIds),\n cursors: canonical.map(sourceId => state[sourceId] ?? null),\n };\n return Buffer.from(JSON.stringify(cursor)).toString('base64url');\n}\n\nfunction requireLinearConnection(connection: IntegrationConnection): void {\n if (connection.type !== 'oauth') {\n throw new Error('Linear capabilities require an OAuth connection.');\n }\n}\n\nfunction isNotFound(error: unknown): boolean {\n return error instanceof PlatformApiError && error.status === 404;\n}\n\nfunction optionalPositiveIntegerEnv(name: string): number | undefined {\n const value = process.env[name]?.trim();\n if (!value) return undefined;\n const parsed = Number(value);\n if (!Number.isSafeInteger(parsed) || parsed <= 0) {\n throw new Error(`${name} must be a positive integer.`);\n }\n return parsed;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"intake.d.ts","sourceRoot":"","sources":["../../src/routes/intake.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAGpD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,OAAO,KAAK,EAAE,MAAM,EAAc,MAAM,2BAA2B,CAAC;AACpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAEvE,OAAO,KAAK,EAAE,YAAY,EAAoB,aAAa,EAAE,MAAM,mCAAmC,CAAC;AACvG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,aAAa,GAAG,WAAW,CAAC,CAAC;CACnD;AAYD,MAAM,WAAW,gBAAiB,SAAQ,iBAAiB;IACzD,KAAK,EAAE,YAAY,CAAC;IACpB,sCAAsC;IACtC,MAAM,EAAE,aAAa,CAAC;IACtB,uEAAuE;IACvE,QAAQ,CAAC,EAAE;QAAE,GAAG,CAAC,KAAK,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,EAAE,EAAE,MAAM,CAAA;SAAE,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAA;KAAE,CAAC;IAClF,YAAY,CAAC,EAAE,iBAAiB,EAAE,CAAC;IACnC,wGAAwG;IACxG,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,2GAA2G;IAC3G,SAAS,CAAC,EAAE,IAAI,CAAC,gBAAgB,EAAE,MAAM,GAAG,QAAQ,GAAG,+BAA+B,CAAC,CAAC;CACzF;
|
|
1
|
+
{"version":3,"file":"intake.d.ts","sourceRoot":"","sources":["../../src/routes/intake.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAGpD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,OAAO,KAAK,EAAE,MAAM,EAAc,MAAM,2BAA2B,CAAC;AACpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAEvE,OAAO,KAAK,EAAE,YAAY,EAAoB,aAAa,EAAE,MAAM,mCAAmC,CAAC;AACvG,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,aAAa,GAAG,WAAW,CAAC,CAAC;CACnD;AAYD,MAAM,WAAW,gBAAiB,SAAQ,iBAAiB;IACzD,KAAK,EAAE,YAAY,CAAC;IACpB,sCAAsC;IACtC,MAAM,EAAE,aAAa,CAAC;IACtB,uEAAuE;IACvE,QAAQ,CAAC,EAAE;QAAE,GAAG,CAAC,KAAK,EAAE;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,EAAE,EAAE,MAAM,CAAA;SAAE,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAA;KAAE,CAAC;IAClF,YAAY,CAAC,EAAE,iBAAiB,EAAE,CAAC;IACnC,wGAAwG;IACxG,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,2GAA2G;IAC3G,SAAS,CAAC,EAAE,IAAI,CAAC,gBAAgB,EAAE,MAAM,GAAG,QAAQ,GAAG,+BAA+B,CAAC,CAAC;CACzF;AAiID,+EAA+E;AAC/E,MAAM,WAAW,wBAAwB;IACvC,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;CACjB;AAkDD,UAAU,aAAa;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,+EAA+E;IAC/E,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,iEAAiE;AACjE,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,OAAO,GAAG,aAAa,GAAG,IAAI,CAatE;AAGD,QAAA,MAAM,oBAAoB;;;;;iBAUxB,CAAC;AACH,KAAK,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAE9D,qEAAqE;AACrE,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,OAAO,GAAG,gBAAgB,GAAG,IAAI,CAG5E;AAaD,+EAA+E;AAC/E,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,GAAG,YAAY,GAAG,IAAI,CAyBpE;AAmBD,qBAAa,YAAa,SAAQ,KAAK,CAAC,gBAAgB,CAAC;;IAgBvD,MAAM,IAAI,QAAQ,EAAE;CA+UrB"}
|
package/dist/routes/intake.js
CHANGED
|
@@ -26,7 +26,7 @@ function intakeItemSourceKeys(integrationId, item) {
|
|
|
26
26
|
* a current stage (a run owns them). Everything else moves, including cards parked in a working
|
|
27
27
|
* phase that never started a run — auto-ingested issues sit in Work › Triage that way.
|
|
28
28
|
*/
|
|
29
|
-
async function relocateSourceCards({ workItems, integration, boardRegistry, orgId, userId, factoryProjectId, sourceId, targetBoard }) {
|
|
29
|
+
async function relocateSourceCards({ workItems, integration, boardRegistry, orgId, userId, factoryProjectId, sourceId, targetBoard, attributionSourceIds }) {
|
|
30
30
|
if (!boardRegistry.has(targetBoard)) return {
|
|
31
31
|
moved: 0,
|
|
32
32
|
skipped: 0
|
|
@@ -39,9 +39,13 @@ async function relocateSourceCards({ workItems, integration, boardRegistry, orgI
|
|
|
39
39
|
orgId,
|
|
40
40
|
userId,
|
|
41
41
|
sourceIds: [sourceId],
|
|
42
|
+
attributionSourceIds,
|
|
42
43
|
cursor
|
|
43
44
|
}), deadline - Date.now());
|
|
44
|
-
for (const item of result.items)
|
|
45
|
+
for (const item of result.items) {
|
|
46
|
+
if (item.sourceId !== sourceId) continue;
|
|
47
|
+
for (const key of intakeItemSourceKeys(integration.id, item)) sourceKeys.add(key);
|
|
48
|
+
}
|
|
45
49
|
if (!result.nextCursor) break;
|
|
46
50
|
cursor = result.nextCursor;
|
|
47
51
|
}
|
|
@@ -239,7 +243,7 @@ var IntakeRoutes = class extends Route {
|
|
|
239
243
|
if ("response" in tenant) return tenant.response;
|
|
240
244
|
await intake.ensureReady();
|
|
241
245
|
const config = await intake.getConfig({
|
|
242
|
-
|
|
246
|
+
orgId: tenant.orgId,
|
|
243
247
|
integrationIds
|
|
244
248
|
});
|
|
245
249
|
return c.json({ config });
|
|
@@ -269,7 +273,7 @@ var IntakeRoutes = class extends Route {
|
|
|
269
273
|
}
|
|
270
274
|
await intake.ensureReady();
|
|
271
275
|
await intake.saveConfig({
|
|
272
|
-
|
|
276
|
+
orgId: tenant.orgId,
|
|
273
277
|
config: registeredConfig
|
|
274
278
|
});
|
|
275
279
|
await audit.emit({
|
|
@@ -348,6 +352,11 @@ var IntakeRoutes = class extends Route {
|
|
|
348
352
|
const nextBoard = binding.board;
|
|
349
353
|
const integration = integrations.find((candidate) => candidate.id === binding.integrationId);
|
|
350
354
|
if (workItems && boardRegistry && integration && previousBinding?.factoryProjectId === binding.factoryProjectId && nextBoard !== null && previousBinding.board !== nextBoard) try {
|
|
355
|
+
const configuredSourceIds = (await intake.getConfig({
|
|
356
|
+
orgId: tenant.orgId,
|
|
357
|
+
integrationIds: [binding.integrationId]
|
|
358
|
+
}))[binding.integrationId]?.sourceIds ?? [];
|
|
359
|
+
const attributionSourceIds = configuredSourceIds.includes(binding.sourceId) ? configuredSourceIds : [...configuredSourceIds, binding.sourceId];
|
|
351
360
|
relocated = await relocateSourceCards({
|
|
352
361
|
workItems,
|
|
353
362
|
integration,
|
|
@@ -356,6 +365,7 @@ var IntakeRoutes = class extends Route {
|
|
|
356
365
|
userId: tenant.userId,
|
|
357
366
|
factoryProjectId: binding.factoryProjectId,
|
|
358
367
|
sourceId: binding.sourceId,
|
|
368
|
+
attributionSourceIds,
|
|
359
369
|
targetBoard: nextBoard
|
|
360
370
|
});
|
|
361
371
|
} catch (error) {
|
|
@@ -507,7 +517,7 @@ var IntakeRoutes = class extends Route {
|
|
|
507
517
|
if (!cursors) return c.json({ error: "invalid_cursor" }, 400);
|
|
508
518
|
await intake.ensureReady();
|
|
509
519
|
const config = await intake.getConfig({
|
|
510
|
-
|
|
520
|
+
orgId: tenant.orgId,
|
|
511
521
|
integrationIds
|
|
512
522
|
});
|
|
513
523
|
const { pages, failures } = await settleByIntegration(integrations.flatMap((integration) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"intake.js","names":["#resolveTenant"],"sources":["../../src/routes/intake.ts"],"sourcesContent":["import type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\nimport { z } from 'zod';\n\nimport type { BoardRegistry } from '../boards/index.js';\nimport { cardLabels, moveCardToBoard } from '../boards/relocate.js';\nimport type { Intake, IntakeItem } from '../capabilities/intake.js';\nimport type { AuditEmitter } from '../storage/domains/audit/domain.js';\nimport { normalizeIntakeLabel, resolveIntakeLabelRoute } from '../storage/domains/intake/base.js';\nimport type { IntakeConfig, IntakeLabelRoute, IntakeStorage } from '../storage/domains/intake/base.js';\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport type { RouteDependencies } from './route.js';\nimport { Route } from './route.js';\n\nexport interface IntakeIntegration {\n id: string;\n intake: Pick<Intake, 'listSources' | 'listItems'>;\n}\n\ninterface AggregatedIntakeItem extends Omit<IntakeItem, 'source'> {\n integrationId: string;\n externalSource: {\n integrationId: string;\n type: string;\n externalId: string;\n url?: string;\n };\n}\n\nexport interface IntakeRoutesDeps extends RouteDependencies {\n audit: AuditEmitter;\n /** Intake selection domain handle. */\n intake: IntakeStorage;\n /** Factory project domain handle, used to validate binding targets. */\n projects?: { get(input: { orgId: string; id: string }): Promise<unknown | null> };\n integrations?: IntakeIntegration[];\n /** Installed boards, used to validate binding targets. Absent means only legacy routing is accepted. */\n boardRegistry?: BoardRegistry;\n /** Work items domain handle; when present, rebinding a source to another board moves its resting cards. */\n workItems?: Pick<WorkItemsStorage, 'list' | 'update' | 'supersedeDecisionsForWorkItem'>;\n}\n\n/** Upper bound on source pages read while relocating cards, so a huge source cannot stall the request. */\nconst REBIND_MAX_PAGES = 20;\n/**\n * One deadline for the whole rebind read, not one per page: the binding is already saved when we\n * get here, so the caller is only waiting on relocation and a slow provider must not hold the\n * response for pages × timeout.\n */\nconst REBIND_READ_BUDGET_MS = 30_000;\n\n/**\n * Keys under which an intake item may be persisted as a work item. Providers key items by their\n * own id, while materialization uses the human identifier (`linear:MAS-44`), so both are accepted.\n */\nfunction intakeItemSourceKeys(integrationId: string, item: IntakeItem): string[] {\n const identifier = item.metadata?.identifier;\n return typeof identifier === 'string' && identifier.length > 0\n ? [item.source.externalId, `${integrationId}:${identifier}`]\n : [item.source.externalId];\n}\n\n/**\n * Move the cards that came from a rebound source onto its new board's initial phase. Terminal cards\n * stay put (they are history that belongs where it finished), as do cards with a session attached to\n * a current stage (a run owns them). Everything else moves, including cards parked in a working\n * phase that never started a run — auto-ingested issues sit in Work › Triage that way.\n */\nasync function relocateSourceCards({\n workItems,\n integration,\n boardRegistry,\n orgId,\n userId,\n factoryProjectId,\n sourceId,\n targetBoard,\n}: {\n workItems: Pick<WorkItemsStorage, 'list' | 'update' | 'supersedeDecisionsForWorkItem'>;\n integration: IntakeIntegration;\n boardRegistry: BoardRegistry;\n orgId: string;\n userId: string;\n factoryProjectId: string;\n sourceId: string;\n targetBoard: string;\n}): Promise<{ moved: number; skipped: number }> {\n if (!boardRegistry.has(targetBoard)) return { moved: 0, skipped: 0 };\n\n const sourceKeys = new Set<string>();\n let cursor: string | undefined;\n const deadline = Date.now() + REBIND_READ_BUDGET_MS;\n for (let page = 0; page < REBIND_MAX_PAGES; page += 1) {\n const result = await withTimeout(\n integration.id,\n () => integration.intake.listItems({ orgId, userId, sourceIds: [sourceId], cursor }),\n deadline - Date.now(),\n );\n for (const item of result.items) {\n for (const key of intakeItemSourceKeys(integration.id, item)) sourceKeys.add(key);\n }\n if (!result.nextCursor) break;\n cursor = result.nextCursor;\n }\n if (sourceKeys.size === 0) return { moved: 0, skipped: 0 };\n\n let moved = 0;\n let skipped = 0;\n const items = await workItems.list({ orgId, factoryProjectId });\n for (const item of items) {\n const source = item.externalSource;\n if (!source || source.integrationId !== integration.id || !sourceKeys.has(source.externalId)) continue;\n const outcome = await moveCardToBoard({ workItems, boardRegistry, userId, item, targetBoard });\n if (outcome === 'moved') moved += 1;\n else if (outcome === 'skipped') skipped += 1;\n }\n return { moved, skipped };\n}\n\n/**\n * After a label route changes, every issue card in the project that carries `label` is re-routed\n * under the project's current routes: to the board its labels now select, or back to Work when none\n * of them is routed any more.\n */\nasync function relocateLabeledCards({\n workItems,\n boardRegistry,\n orgId,\n userId,\n factoryProjectId,\n integrationId,\n label,\n routes,\n}: {\n workItems: Pick<WorkItemsStorage, 'list' | 'update' | 'supersedeDecisionsForWorkItem'>;\n boardRegistry: BoardRegistry;\n orgId: string;\n userId: string;\n factoryProjectId: string;\n integrationId: string;\n label: string;\n routes: readonly IntakeLabelRoute[];\n}): Promise<{ moved: number; skipped: number }> {\n const changed = normalizeIntakeLabel(label);\n let moved = 0;\n let skipped = 0;\n for (const item of await workItems.list({ orgId, factoryProjectId })) {\n const source = item.externalSource;\n if (!source || source.integrationId !== integrationId || source.type !== 'issue') continue;\n const labels = cardLabels(item);\n if (!labels.some(candidate => normalizeIntakeLabel(candidate) === changed)) continue;\n const targetBoard = resolveIntakeLabelRoute(routes, labels)?.board ?? 'work';\n const outcome = await moveCardToBoard({ workItems, boardRegistry, userId, item, targetBoard });\n if (outcome === 'moved') moved += 1;\n else if (outcome === 'skipped') skipped += 1;\n }\n return { moved, skipped };\n}\n\n/** One integration that failed while the rest of the aggregation succeeded. */\nexport interface IntakeIntegrationFailure {\n integrationId: string;\n message: string;\n}\n\ntype SettledIntegration<T> = { integrationId: string; value: T } | IntakeIntegrationFailure;\n\nconst PROVIDER_READ_TIMEOUT_MS = 15_000;\n\n/** The Intake contract takes no abort signal, so a slow read is abandoned, not cancelled. */\nfunction withTimeout<T>(\n integrationId: string,\n read: () => Promise<T>,\n timeoutMs: number = PROVIDER_READ_TIMEOUT_MS,\n): Promise<T> {\n return new Promise((resolve, reject) => {\n const budget = Math.max(0, timeoutMs);\n const timer = setTimeout(\n () => reject(new Error(`${integrationId} did not answer within ${Math.round(budget / 1000)}s`)),\n budget,\n );\n read()\n .then(resolve, reject)\n .finally(() => clearTimeout(timer));\n });\n}\n\n/**\n * Read every integration concurrently and isolate the ones that throw or hang, so a single\n * unreachable provider degrades to a per-source error instead of failing the listing.\n */\nasync function settleByIntegration<T>(\n requests: Array<{ integrationId: string; read: () => Promise<T> }>,\n): Promise<{ pages: Array<{ integrationId: string; value: T }>; failures: IntakeIntegrationFailure[] }> {\n const settled = await Promise.all(\n requests.map(async ({ integrationId, read }): Promise<SettledIntegration<T>> => {\n try {\n return { integrationId, value: await withTimeout(integrationId, read) };\n } catch (error) {\n console.error(`[factory] intake integration ${integrationId} is unavailable:`, error);\n return { integrationId, message: error instanceof Error ? error.message : String(error) };\n }\n }),\n );\n const pages: Array<{ integrationId: string; value: T }> = [];\n const failures: IntakeIntegrationFailure[] = [];\n for (const entry of settled) {\n if ('value' in entry) pages.push(entry);\n else failures.push(entry);\n }\n return { pages, failures };\n}\n\ninterface ParsedBinding {\n integrationId: string;\n sourceId: string;\n factoryProjectId: string | null;\n /** Installed board target; `null`/omitted keeps legacy source-type routing. */\n board: string | null;\n}\n\n/** Validate a binding request body, rejecting unknown shapes. */\nexport function parseIntakeBinding(body: unknown): ParsedBinding | null {\n if (typeof body !== 'object' || body === null || Array.isArray(body)) return null;\n const { integrationId, sourceId, factoryProjectId, board } = body as Record<string, unknown>;\n const isId = (value: unknown) => typeof value === 'string' && value.length > 0 && value.length <= 256;\n if (!isId(integrationId) || !isId(sourceId)) return null;\n if (factoryProjectId !== null && !isId(factoryProjectId)) return null;\n if (board !== undefined && board !== null && !isId(board)) return null;\n return {\n integrationId: integrationId as string,\n sourceId: sourceId as string,\n factoryProjectId: factoryProjectId as string | null,\n board: (board as string | null | undefined) ?? null,\n };\n}\n\nconst identifier = z.string().min(1).max(256);\nconst labelRouteBodySchema = z.object({\n factoryProjectId: identifier,\n integrationId: identifier,\n label: z\n .string()\n .max(256)\n .transform(normalizeIntakeLabel)\n .refine(label => label.length > 0, 'label must not be blank'),\n /** Installed board target; `null` removes the route so the label falls back to Work. */\n board: identifier.nullish().transform(board => board ?? null),\n});\ntype ParsedLabelRoute = z.output<typeof labelRouteBodySchema>;\n\n/** Validate a label-route request body, rejecting unknown shapes. */\nexport function parseIntakeLabelRoute(body: unknown): ParsedLabelRoute | null {\n const parsed = labelRouteBodySchema.safeParse(body);\n return parsed.success ? parsed.data : null;\n}\n\nfunction loose(c: unknown): Context {\n return c as Context;\n}\n\nfunction sanitizeIdList(value: unknown): string[] | null | undefined {\n if (value === null) return null;\n if (!Array.isArray(value) || value.length > 200) return undefined;\n const ids = value.filter((item): item is string => typeof item === 'string' && item.length > 0 && item.length <= 256);\n return ids.length === value.length && new Set(ids).size === ids.length ? ids : undefined;\n}\n\n/** Validate a request body into an intake config, rejecting unknown shapes. */\nexport function parseIntakeConfig(body: unknown): IntakeConfig | null {\n if (typeof body !== 'object' || body === null || Array.isArray(body)) return null;\n const entries = Object.entries(body);\n if (entries.length > 50) return null;\n\n // Null-prototype so an `__proto__` key lands as a real entry instead of silently\n // reassigning the prototype and disappearing from the validation below.\n const config: IntakeConfig = Object.create(null);\n for (const [integrationId, value] of entries) {\n if (\n !integrationId ||\n integrationId.length > 128 ||\n typeof value !== 'object' ||\n value === null ||\n Array.isArray(value)\n ) {\n return null;\n }\n const selection = value as { enabled?: unknown; sourceIds?: unknown };\n if (typeof selection.enabled !== 'boolean') return null;\n const sourceIds = sanitizeIdList(selection.sourceIds ?? null);\n if (sourceIds === undefined) return null;\n config[integrationId] = { enabled: selection.enabled, sourceIds };\n }\n return config;\n}\n\nfunction encodeCursor(cursors: Record<string, string>): string | null {\n return Object.keys(cursors).length > 0 ? Buffer.from(JSON.stringify(cursors)).toString('base64url') : null;\n}\n\nfunction decodeCursor(value: string | undefined): Record<string, string> | null {\n if (!value) return {};\n try {\n const parsed = JSON.parse(Buffer.from(value, 'base64url').toString('utf8')) as unknown;\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) return null;\n const entries = Object.entries(parsed);\n if (entries.some(([key, cursor]) => !key || typeof cursor !== 'string')) return null;\n return Object.fromEntries(entries) as Record<string, string>;\n } catch {\n return null;\n }\n}\n\nexport class IntakeRoutes extends Route<IntakeRoutesDeps> {\n async #resolveTenant(c: Context): Promise<{ orgId: string; userId: string } | { response: Response }> {\n await this.deps.auth.ensureUser(c);\n const tenant = this.deps.auth.tenant(c);\n if (!tenant) return { response: c.json({ error: 'unauthorized' }, 401) };\n if (!tenant.orgId) {\n return {\n response: c.json(\n { error: 'organization_required', message: 'Intake configuration requires an organization.' },\n 403,\n ),\n };\n }\n return { orgId: tenant.orgId, userId: tenant.userId };\n }\n\n routes(): ApiRoute[] {\n const { audit, intake, projects, integrations = [], boardRegistry, workItems } = this.deps;\n const integrationIds = integrations.map(integration => integration.id);\n\n return [\n registerApiRoute('/web/intake/config', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n await intake.ensureReady();\n const config = await intake.getConfig({ ...tenant, integrationIds });\n return c.json({ config });\n },\n }),\n registerApiRoute('/web/intake/config', {\n method: 'PUT',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n\n let body: unknown;\n try {\n body = await c.req.json();\n } catch {\n return c.json({ error: 'Invalid JSON body' }, 400);\n }\n const config = parseIntakeConfig(body);\n if (!config) {\n return c.json({ error: 'invalid_config' }, 400);\n }\n\n const registeredConfig: IntakeConfig = Object.create(null);\n for (const [integrationId, selection] of Object.entries(config)) {\n if (integrationIds.includes(integrationId)) {\n registeredConfig[integrationId] = selection;\n continue;\n }\n if (selection.enabled || selection.sourceIds?.length) {\n return c.json({ error: 'invalid_config' }, 400);\n }\n }\n\n await intake.ensureReady();\n await intake.saveConfig({ ...tenant, config: registeredConfig });\n await audit.emit({\n context: loose(c),\n input: {\n action: 'factory.intake.config_updated',\n targets: [{ type: 'intake_config', id: tenant.orgId }],\n metadata: Object.fromEntries(\n Object.entries(registeredConfig).map(([integrationId, selection]) => [\n integrationId,\n { enabled: selection.enabled, sources: selection.sourceIds?.length ?? null },\n ]),\n ),\n },\n });\n return c.json({ config: registeredConfig });\n },\n }),\n registerApiRoute('/web/intake/bindings', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n await intake.ensureReady();\n return c.json({ bindings: await intake.listBindings({ orgId: tenant.orgId }) });\n },\n }),\n registerApiRoute('/web/intake/bindings', {\n method: 'PUT',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n\n let body: unknown;\n try {\n body = await c.req.json();\n } catch {\n return c.json({ error: 'Invalid JSON body' }, 400);\n }\n const binding = parseIntakeBinding(body);\n if (!binding || !integrationIds.includes(binding.integrationId)) {\n return c.json({ error: 'invalid_binding' }, 400);\n }\n if (binding.factoryProjectId && projects) {\n const project = await projects.get({ orgId: tenant.orgId, id: binding.factoryProjectId });\n if (!project) return c.json({ error: 'factory_project_not_found' }, 404);\n }\n if (binding.board !== null && !boardRegistry?.has(binding.board)) {\n return c.json({ error: 'invalid_board', message: `Board '${binding.board}' is not installed.` }, 422);\n }\n\n await intake.ensureReady();\n let auditFactoryProjectId = binding.factoryProjectId;\n let relocated: { moved: number; skipped: number } | null = null;\n if (binding.factoryProjectId === null) {\n const previousBinding = await intake.clearBinding({\n orgId: tenant.orgId,\n integrationId: binding.integrationId,\n sourceId: binding.sourceId,\n });\n auditFactoryProjectId = previousBinding?.factoryProjectId ?? null;\n } else {\n const previousBinding = await intake.getBinding({\n orgId: tenant.orgId,\n integrationId: binding.integrationId,\n sourceId: binding.sourceId,\n });\n await intake.setBinding({\n orgId: tenant.orgId,\n userId: tenant.userId,\n integrationId: binding.integrationId,\n sourceId: binding.sourceId,\n factoryProjectId: binding.factoryProjectId,\n board: binding.board,\n });\n // A source that stays in the same project but is pointed at a different board\n // takes its resting cards along. Bindings that predate persisted boards have a\n // null board yet still fed Work/Review, so relocation compares each card's\n // effective board rather than trusting the previous binding value.\n const nextBoard = binding.board;\n const integration = integrations.find(candidate => candidate.id === binding.integrationId);\n if (\n workItems &&\n boardRegistry &&\n integration &&\n previousBinding?.factoryProjectId === binding.factoryProjectId &&\n nextBoard !== null &&\n previousBinding.board !== nextBoard\n ) {\n try {\n relocated = await relocateSourceCards({\n workItems,\n integration,\n boardRegistry,\n orgId: tenant.orgId,\n userId: tenant.userId,\n factoryProjectId: binding.factoryProjectId,\n sourceId: binding.sourceId,\n targetBoard: nextBoard,\n });\n } catch (error) {\n // The binding is saved; the cards can be moved by hand if the provider was unreachable.\n console.error(`[factory] intake rebind could not relocate ${binding.integrationId} cards:`, error);\n }\n }\n }\n await audit.emit({\n context: loose(c),\n input: {\n action: 'factory.intake.binding_updated',\n ...(auditFactoryProjectId ? { factoryProjectId: auditFactoryProjectId } : {}),\n targets: [{ type: 'intake_source', id: `${binding.integrationId}:${binding.sourceId}` }],\n metadata: {\n factoryProjectId: binding.factoryProjectId,\n board: binding.board,\n ...(relocated ? { relocated } : {}),\n },\n },\n });\n return c.json({\n bindings: await intake.listBindings({ orgId: tenant.orgId }),\n ...(relocated ? { relocated } : {}),\n });\n },\n }),\n registerApiRoute('/web/intake/label-routes', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n const factoryProjectId = c.req.query('factoryProjectId');\n await intake.ensureReady();\n return c.json({\n routes: await intake.listLabelRoutes({\n orgId: tenant.orgId,\n ...(factoryProjectId ? { factoryProjectId } : {}),\n }),\n });\n },\n }),\n registerApiRoute('/web/intake/label-routes', {\n method: 'PUT',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n\n let body: unknown;\n try {\n body = await c.req.json();\n } catch {\n return c.json({ error: 'Invalid JSON body' }, 400);\n }\n const route = parseIntakeLabelRoute(body);\n if (!route || !integrationIds.includes(route.integrationId)) {\n return c.json({ error: 'invalid_label_route' }, 400);\n }\n if (projects) {\n const project = await projects.get({ orgId: tenant.orgId, id: route.factoryProjectId });\n if (!project) return c.json({ error: 'factory_project_not_found' }, 404);\n }\n if (route.board !== null && !boardRegistry?.has(route.board)) {\n return c.json({ error: 'invalid_board', message: `Board '${route.board}' is not installed.` }, 422);\n }\n\n await intake.ensureReady();\n const scope = {\n orgId: tenant.orgId,\n factoryProjectId: route.factoryProjectId,\n integrationId: route.integrationId,\n };\n const previous = (await intake.listLabelRoutes(scope)).find(existing => existing.label === route.label);\n if (route.board === null) {\n await intake.clearLabelRoute({ ...scope, label: route.label });\n } else {\n await intake.setLabelRoute({ ...scope, label: route.label, board: route.board, userId: tenant.userId });\n }\n\n // Cards already carrying the label follow the route: onto the new board, or back to\n // Work when the label is no longer routed anywhere.\n let relocated: { moved: number; skipped: number } | null = null;\n if (workItems && boardRegistry && (previous?.board ?? null) !== route.board) {\n try {\n relocated = await relocateLabeledCards({\n workItems,\n boardRegistry,\n orgId: tenant.orgId,\n userId: tenant.userId,\n factoryProjectId: route.factoryProjectId,\n integrationId: route.integrationId,\n label: route.label,\n routes: await intake.listLabelRoutes(scope),\n });\n } catch (error) {\n // The route is saved; the cards can be moved by hand.\n console.error(`[factory] intake label route could not relocate ${route.integrationId} cards:`, error);\n }\n }\n await audit.emit({\n context: loose(c),\n input: {\n action: 'factory.intake.label_route_updated',\n factoryProjectId: route.factoryProjectId,\n targets: [{ type: 'intake_label_route', id: `${route.integrationId}:${route.label}` }],\n metadata: { board: route.board, ...(relocated ? { relocated } : {}) },\n },\n });\n return c.json({\n routes: await intake.listLabelRoutes({ orgId: tenant.orgId, factoryProjectId: route.factoryProjectId }),\n ...(relocated ? { relocated } : {}),\n });\n },\n }),\n registerApiRoute('/web/intake/sources', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n const { pages, failures } = await settleByIntegration(\n integrations.map(integration => ({\n integrationId: integration.id,\n read: () => integration.intake.listSources(tenant),\n })),\n );\n return c.json({\n sources: pages.flatMap(({ integrationId, value }) => value.map(source => ({ integrationId, ...source }))),\n failures,\n });\n },\n }),\n registerApiRoute('/web/intake/items', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n const cursors = decodeCursor(c.req.query('cursor'));\n if (!cursors) return c.json({ error: 'invalid_cursor' }, 400);\n\n await intake.ensureReady();\n const config = await intake.getConfig({ ...tenant, integrationIds });\n const { pages, failures } = await settleByIntegration(\n integrations.flatMap(integration => {\n const selection = config[integration.id];\n if (!selection?.enabled || !selection.sourceIds?.length) return [];\n const sourceIds = selection.sourceIds;\n const cursor = cursors[integration.id];\n return [\n {\n integrationId: integration.id,\n read: () => integration.intake.listItems({ ...tenant, sourceIds, ...(cursor ? { cursor } : {}) }),\n },\n ];\n }),\n );\n\n const items: AggregatedIntakeItem[] = [];\n const nextCursors: Record<string, string> = {};\n for (const { integrationId, value } of pages) {\n items.push(\n ...value.items.map(item => {\n const { source, ...candidate } = item;\n return { ...candidate, integrationId, externalSource: { integrationId, ...source } };\n }),\n );\n if (value.nextCursor) nextCursors[integrationId] = value.nextCursor;\n }\n // Keep the cursor an unavailable integration came in with, so the next page resumes there instead of replaying it.\n for (const { integrationId } of failures) {\n const cursor = cursors[integrationId];\n if (cursor) nextCursors[integrationId] = cursor;\n }\n return c.json({ items, nextCursor: encodeCursor(nextCursors), failures });\n },\n }),\n ];\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"intake.js","names":["#resolveTenant"],"sources":["../../src/routes/intake.ts"],"sourcesContent":["import type { ApiRoute } from '@mastra/core/server';\nimport { registerApiRoute } from '@mastra/core/server';\nimport type { Context } from 'hono';\nimport { z } from 'zod';\n\nimport type { BoardRegistry } from '../boards/index.js';\nimport { cardLabels, moveCardToBoard } from '../boards/relocate.js';\nimport type { Intake, IntakeItem } from '../capabilities/intake.js';\nimport type { AuditEmitter } from '../storage/domains/audit/domain.js';\nimport { normalizeIntakeLabel, resolveIntakeLabelRoute } from '../storage/domains/intake/base.js';\nimport type { IntakeConfig, IntakeLabelRoute, IntakeStorage } from '../storage/domains/intake/base.js';\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport type { RouteDependencies } from './route.js';\nimport { Route } from './route.js';\n\nexport interface IntakeIntegration {\n id: string;\n intake: Pick<Intake, 'listSources' | 'listItems'>;\n}\n\ninterface AggregatedIntakeItem extends Omit<IntakeItem, 'source'> {\n integrationId: string;\n externalSource: {\n integrationId: string;\n type: string;\n externalId: string;\n url?: string;\n };\n}\n\nexport interface IntakeRoutesDeps extends RouteDependencies {\n audit: AuditEmitter;\n /** Intake selection domain handle. */\n intake: IntakeStorage;\n /** Factory project domain handle, used to validate binding targets. */\n projects?: { get(input: { orgId: string; id: string }): Promise<unknown | null> };\n integrations?: IntakeIntegration[];\n /** Installed boards, used to validate binding targets. Absent means only legacy routing is accepted. */\n boardRegistry?: BoardRegistry;\n /** Work items domain handle; when present, rebinding a source to another board moves its resting cards. */\n workItems?: Pick<WorkItemsStorage, 'list' | 'update' | 'supersedeDecisionsForWorkItem'>;\n}\n\n/** Upper bound on source pages read while relocating cards, so a huge source cannot stall the request. */\nconst REBIND_MAX_PAGES = 20;\n/**\n * One deadline for the whole rebind read, not one per page: the binding is already saved when we\n * get here, so the caller is only waiting on relocation and a slow provider must not hold the\n * response for pages × timeout.\n */\nconst REBIND_READ_BUDGET_MS = 30_000;\n\n/**\n * Keys under which an intake item may be persisted as a work item. Providers key items by their\n * own id, while materialization uses the human identifier (`linear:MAS-44`), so both are accepted.\n */\nfunction intakeItemSourceKeys(integrationId: string, item: IntakeItem): string[] {\n const identifier = item.metadata?.identifier;\n return typeof identifier === 'string' && identifier.length > 0\n ? [item.source.externalId, `${integrationId}:${identifier}`]\n : [item.source.externalId];\n}\n\n/**\n * Move the cards that came from a rebound source onto its new board's initial phase. Terminal cards\n * stay put (they are history that belongs where it finished), as do cards with a session attached to\n * a current stage (a run owns them). Everything else moves, including cards parked in a working\n * phase that never started a run — auto-ingested issues sit in Work › Triage that way.\n */\nasync function relocateSourceCards({\n workItems,\n integration,\n boardRegistry,\n orgId,\n userId,\n factoryProjectId,\n sourceId,\n targetBoard,\n attributionSourceIds,\n}: {\n workItems: Pick<WorkItemsStorage, 'list' | 'update' | 'supersedeDecisionsForWorkItem'>;\n integration: IntakeIntegration;\n boardRegistry: BoardRegistry;\n orgId: string;\n userId: string;\n factoryProjectId: string;\n sourceId: string;\n targetBoard: string;\n attributionSourceIds: string[];\n}): Promise<{ moved: number; skipped: number }> {\n if (!boardRegistry.has(targetBoard)) return { moved: 0, skipped: 0 };\n\n const sourceKeys = new Set<string>();\n let cursor: string | undefined;\n const deadline = Date.now() + REBIND_READ_BUDGET_MS;\n for (let page = 0; page < REBIND_MAX_PAGES; page += 1) {\n const result = await withTimeout(\n integration.id,\n () =>\n integration.intake.listItems({\n orgId,\n userId,\n sourceIds: [sourceId],\n attributionSourceIds,\n cursor,\n }),\n deadline - Date.now(),\n );\n for (const item of result.items) {\n if (item.sourceId !== sourceId) continue;\n for (const key of intakeItemSourceKeys(integration.id, item)) sourceKeys.add(key);\n }\n if (!result.nextCursor) break;\n cursor = result.nextCursor;\n }\n if (sourceKeys.size === 0) return { moved: 0, skipped: 0 };\n\n let moved = 0;\n let skipped = 0;\n const items = await workItems.list({ orgId, factoryProjectId });\n for (const item of items) {\n const source = item.externalSource;\n if (!source || source.integrationId !== integration.id || !sourceKeys.has(source.externalId)) continue;\n const outcome = await moveCardToBoard({ workItems, boardRegistry, userId, item, targetBoard });\n if (outcome === 'moved') moved += 1;\n else if (outcome === 'skipped') skipped += 1;\n }\n return { moved, skipped };\n}\n\n/**\n * After a label route changes, every issue card in the project that carries `label` is re-routed\n * under the project's current routes: to the board its labels now select, or back to Work when none\n * of them is routed any more.\n */\nasync function relocateLabeledCards({\n workItems,\n boardRegistry,\n orgId,\n userId,\n factoryProjectId,\n integrationId,\n label,\n routes,\n}: {\n workItems: Pick<WorkItemsStorage, 'list' | 'update' | 'supersedeDecisionsForWorkItem'>;\n boardRegistry: BoardRegistry;\n orgId: string;\n userId: string;\n factoryProjectId: string;\n integrationId: string;\n label: string;\n routes: readonly IntakeLabelRoute[];\n}): Promise<{ moved: number; skipped: number }> {\n const changed = normalizeIntakeLabel(label);\n let moved = 0;\n let skipped = 0;\n for (const item of await workItems.list({ orgId, factoryProjectId })) {\n const source = item.externalSource;\n if (!source || source.integrationId !== integrationId || source.type !== 'issue') continue;\n const labels = cardLabels(item);\n if (!labels.some(candidate => normalizeIntakeLabel(candidate) === changed)) continue;\n const targetBoard = resolveIntakeLabelRoute(routes, labels)?.board ?? 'work';\n const outcome = await moveCardToBoard({ workItems, boardRegistry, userId, item, targetBoard });\n if (outcome === 'moved') moved += 1;\n else if (outcome === 'skipped') skipped += 1;\n }\n return { moved, skipped };\n}\n\n/** One integration that failed while the rest of the aggregation succeeded. */\nexport interface IntakeIntegrationFailure {\n integrationId: string;\n message: string;\n}\n\ntype SettledIntegration<T> = { integrationId: string; value: T } | IntakeIntegrationFailure;\n\nconst PROVIDER_READ_TIMEOUT_MS = 15_000;\n\n/** The Intake contract takes no abort signal, so a slow read is abandoned, not cancelled. */\nfunction withTimeout<T>(\n integrationId: string,\n read: () => Promise<T>,\n timeoutMs: number = PROVIDER_READ_TIMEOUT_MS,\n): Promise<T> {\n return new Promise((resolve, reject) => {\n const budget = Math.max(0, timeoutMs);\n const timer = setTimeout(\n () => reject(new Error(`${integrationId} did not answer within ${Math.round(budget / 1000)}s`)),\n budget,\n );\n read()\n .then(resolve, reject)\n .finally(() => clearTimeout(timer));\n });\n}\n\n/**\n * Read every integration concurrently and isolate the ones that throw or hang, so a single\n * unreachable provider degrades to a per-source error instead of failing the listing.\n */\nasync function settleByIntegration<T>(\n requests: Array<{ integrationId: string; read: () => Promise<T> }>,\n): Promise<{ pages: Array<{ integrationId: string; value: T }>; failures: IntakeIntegrationFailure[] }> {\n const settled = await Promise.all(\n requests.map(async ({ integrationId, read }): Promise<SettledIntegration<T>> => {\n try {\n return { integrationId, value: await withTimeout(integrationId, read) };\n } catch (error) {\n console.error(`[factory] intake integration ${integrationId} is unavailable:`, error);\n return { integrationId, message: error instanceof Error ? error.message : String(error) };\n }\n }),\n );\n const pages: Array<{ integrationId: string; value: T }> = [];\n const failures: IntakeIntegrationFailure[] = [];\n for (const entry of settled) {\n if ('value' in entry) pages.push(entry);\n else failures.push(entry);\n }\n return { pages, failures };\n}\n\ninterface ParsedBinding {\n integrationId: string;\n sourceId: string;\n factoryProjectId: string | null;\n /** Installed board target; `null`/omitted keeps legacy source-type routing. */\n board: string | null;\n}\n\n/** Validate a binding request body, rejecting unknown shapes. */\nexport function parseIntakeBinding(body: unknown): ParsedBinding | null {\n if (typeof body !== 'object' || body === null || Array.isArray(body)) return null;\n const { integrationId, sourceId, factoryProjectId, board } = body as Record<string, unknown>;\n const isId = (value: unknown) => typeof value === 'string' && value.length > 0 && value.length <= 256;\n if (!isId(integrationId) || !isId(sourceId)) return null;\n if (factoryProjectId !== null && !isId(factoryProjectId)) return null;\n if (board !== undefined && board !== null && !isId(board)) return null;\n return {\n integrationId: integrationId as string,\n sourceId: sourceId as string,\n factoryProjectId: factoryProjectId as string | null,\n board: (board as string | null | undefined) ?? null,\n };\n}\n\nconst identifier = z.string().min(1).max(256);\nconst labelRouteBodySchema = z.object({\n factoryProjectId: identifier,\n integrationId: identifier,\n label: z\n .string()\n .max(256)\n .transform(normalizeIntakeLabel)\n .refine(label => label.length > 0, 'label must not be blank'),\n /** Installed board target; `null` removes the route so the label falls back to Work. */\n board: identifier.nullish().transform(board => board ?? null),\n});\ntype ParsedLabelRoute = z.output<typeof labelRouteBodySchema>;\n\n/** Validate a label-route request body, rejecting unknown shapes. */\nexport function parseIntakeLabelRoute(body: unknown): ParsedLabelRoute | null {\n const parsed = labelRouteBodySchema.safeParse(body);\n return parsed.success ? parsed.data : null;\n}\n\nfunction loose(c: unknown): Context {\n return c as Context;\n}\n\nfunction sanitizeIdList(value: unknown): string[] | null | undefined {\n if (value === null) return null;\n if (!Array.isArray(value) || value.length > 200) return undefined;\n const ids = value.filter((item): item is string => typeof item === 'string' && item.length > 0 && item.length <= 256);\n return ids.length === value.length && new Set(ids).size === ids.length ? ids : undefined;\n}\n\n/** Validate a request body into an intake config, rejecting unknown shapes. */\nexport function parseIntakeConfig(body: unknown): IntakeConfig | null {\n if (typeof body !== 'object' || body === null || Array.isArray(body)) return null;\n const entries = Object.entries(body);\n if (entries.length > 50) return null;\n\n // Null-prototype so an `__proto__` key lands as a real entry instead of silently\n // reassigning the prototype and disappearing from the validation below.\n const config: IntakeConfig = Object.create(null);\n for (const [integrationId, value] of entries) {\n if (\n !integrationId ||\n integrationId.length > 128 ||\n typeof value !== 'object' ||\n value === null ||\n Array.isArray(value)\n ) {\n return null;\n }\n const selection = value as { enabled?: unknown; sourceIds?: unknown };\n if (typeof selection.enabled !== 'boolean') return null;\n const sourceIds = sanitizeIdList(selection.sourceIds ?? null);\n if (sourceIds === undefined) return null;\n config[integrationId] = { enabled: selection.enabled, sourceIds };\n }\n return config;\n}\n\nfunction encodeCursor(cursors: Record<string, string>): string | null {\n return Object.keys(cursors).length > 0 ? Buffer.from(JSON.stringify(cursors)).toString('base64url') : null;\n}\n\nfunction decodeCursor(value: string | undefined): Record<string, string> | null {\n if (!value) return {};\n try {\n const parsed = JSON.parse(Buffer.from(value, 'base64url').toString('utf8')) as unknown;\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) return null;\n const entries = Object.entries(parsed);\n if (entries.some(([key, cursor]) => !key || typeof cursor !== 'string')) return null;\n return Object.fromEntries(entries) as Record<string, string>;\n } catch {\n return null;\n }\n}\n\nexport class IntakeRoutes extends Route<IntakeRoutesDeps> {\n async #resolveTenant(c: Context): Promise<{ orgId: string; userId: string } | { response: Response }> {\n await this.deps.auth.ensureUser(c);\n const tenant = this.deps.auth.tenant(c);\n if (!tenant) return { response: c.json({ error: 'unauthorized' }, 401) };\n if (!tenant.orgId) {\n return {\n response: c.json(\n { error: 'organization_required', message: 'Intake configuration requires an organization.' },\n 403,\n ),\n };\n }\n return { orgId: tenant.orgId, userId: tenant.userId };\n }\n\n routes(): ApiRoute[] {\n const { audit, intake, projects, integrations = [], boardRegistry, workItems } = this.deps;\n const integrationIds = integrations.map(integration => integration.id);\n\n return [\n registerApiRoute('/web/intake/config', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n await intake.ensureReady();\n const config = await intake.getConfig({ orgId: tenant.orgId, integrationIds });\n return c.json({ config });\n },\n }),\n registerApiRoute('/web/intake/config', {\n method: 'PUT',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n\n let body: unknown;\n try {\n body = await c.req.json();\n } catch {\n return c.json({ error: 'Invalid JSON body' }, 400);\n }\n const config = parseIntakeConfig(body);\n if (!config) {\n return c.json({ error: 'invalid_config' }, 400);\n }\n\n const registeredConfig: IntakeConfig = Object.create(null);\n for (const [integrationId, selection] of Object.entries(config)) {\n if (integrationIds.includes(integrationId)) {\n registeredConfig[integrationId] = selection;\n continue;\n }\n if (selection.enabled || selection.sourceIds?.length) {\n return c.json({ error: 'invalid_config' }, 400);\n }\n }\n\n await intake.ensureReady();\n await intake.saveConfig({ orgId: tenant.orgId, config: registeredConfig });\n await audit.emit({\n context: loose(c),\n input: {\n action: 'factory.intake.config_updated',\n targets: [{ type: 'intake_config', id: tenant.orgId }],\n metadata: Object.fromEntries(\n Object.entries(registeredConfig).map(([integrationId, selection]) => [\n integrationId,\n { enabled: selection.enabled, sources: selection.sourceIds?.length ?? null },\n ]),\n ),\n },\n });\n return c.json({ config: registeredConfig });\n },\n }),\n registerApiRoute('/web/intake/bindings', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n await intake.ensureReady();\n return c.json({ bindings: await intake.listBindings({ orgId: tenant.orgId }) });\n },\n }),\n registerApiRoute('/web/intake/bindings', {\n method: 'PUT',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n\n let body: unknown;\n try {\n body = await c.req.json();\n } catch {\n return c.json({ error: 'Invalid JSON body' }, 400);\n }\n const binding = parseIntakeBinding(body);\n if (!binding || !integrationIds.includes(binding.integrationId)) {\n return c.json({ error: 'invalid_binding' }, 400);\n }\n if (binding.factoryProjectId && projects) {\n const project = await projects.get({ orgId: tenant.orgId, id: binding.factoryProjectId });\n if (!project) return c.json({ error: 'factory_project_not_found' }, 404);\n }\n if (binding.board !== null && !boardRegistry?.has(binding.board)) {\n return c.json({ error: 'invalid_board', message: `Board '${binding.board}' is not installed.` }, 422);\n }\n\n await intake.ensureReady();\n let auditFactoryProjectId = binding.factoryProjectId;\n let relocated: { moved: number; skipped: number } | null = null;\n if (binding.factoryProjectId === null) {\n const previousBinding = await intake.clearBinding({\n orgId: tenant.orgId,\n integrationId: binding.integrationId,\n sourceId: binding.sourceId,\n });\n auditFactoryProjectId = previousBinding?.factoryProjectId ?? null;\n } else {\n const previousBinding = await intake.getBinding({\n orgId: tenant.orgId,\n integrationId: binding.integrationId,\n sourceId: binding.sourceId,\n });\n await intake.setBinding({\n orgId: tenant.orgId,\n userId: tenant.userId,\n integrationId: binding.integrationId,\n sourceId: binding.sourceId,\n factoryProjectId: binding.factoryProjectId,\n board: binding.board,\n });\n // A source that stays in the same project but is pointed at a different board\n // takes its resting cards along. Bindings that predate persisted boards have a\n // null board yet still fed Work/Review, so relocation compares each card's\n // effective board rather than trusting the previous binding value.\n const nextBoard = binding.board;\n const integration = integrations.find(candidate => candidate.id === binding.integrationId);\n if (\n workItems &&\n boardRegistry &&\n integration &&\n previousBinding?.factoryProjectId === binding.factoryProjectId &&\n nextBoard !== null &&\n previousBinding.board !== nextBoard\n ) {\n try {\n const config = await intake.getConfig({\n orgId: tenant.orgId,\n integrationIds: [binding.integrationId],\n });\n const configuredSourceIds = config[binding.integrationId]?.sourceIds ?? [];\n const attributionSourceIds = configuredSourceIds.includes(binding.sourceId)\n ? configuredSourceIds\n : [...configuredSourceIds, binding.sourceId];\n relocated = await relocateSourceCards({\n workItems,\n integration,\n boardRegistry,\n orgId: tenant.orgId,\n userId: tenant.userId,\n factoryProjectId: binding.factoryProjectId,\n sourceId: binding.sourceId,\n attributionSourceIds,\n targetBoard: nextBoard,\n });\n } catch (error) {\n // The binding is saved; the cards can be moved by hand if the provider was unreachable.\n console.error(`[factory] intake rebind could not relocate ${binding.integrationId} cards:`, error);\n }\n }\n }\n await audit.emit({\n context: loose(c),\n input: {\n action: 'factory.intake.binding_updated',\n ...(auditFactoryProjectId ? { factoryProjectId: auditFactoryProjectId } : {}),\n targets: [{ type: 'intake_source', id: `${binding.integrationId}:${binding.sourceId}` }],\n metadata: {\n factoryProjectId: binding.factoryProjectId,\n board: binding.board,\n ...(relocated ? { relocated } : {}),\n },\n },\n });\n return c.json({\n bindings: await intake.listBindings({ orgId: tenant.orgId }),\n ...(relocated ? { relocated } : {}),\n });\n },\n }),\n registerApiRoute('/web/intake/label-routes', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n const factoryProjectId = c.req.query('factoryProjectId');\n await intake.ensureReady();\n return c.json({\n routes: await intake.listLabelRoutes({\n orgId: tenant.orgId,\n ...(factoryProjectId ? { factoryProjectId } : {}),\n }),\n });\n },\n }),\n registerApiRoute('/web/intake/label-routes', {\n method: 'PUT',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n\n let body: unknown;\n try {\n body = await c.req.json();\n } catch {\n return c.json({ error: 'Invalid JSON body' }, 400);\n }\n const route = parseIntakeLabelRoute(body);\n if (!route || !integrationIds.includes(route.integrationId)) {\n return c.json({ error: 'invalid_label_route' }, 400);\n }\n if (projects) {\n const project = await projects.get({ orgId: tenant.orgId, id: route.factoryProjectId });\n if (!project) return c.json({ error: 'factory_project_not_found' }, 404);\n }\n if (route.board !== null && !boardRegistry?.has(route.board)) {\n return c.json({ error: 'invalid_board', message: `Board '${route.board}' is not installed.` }, 422);\n }\n\n await intake.ensureReady();\n const scope = {\n orgId: tenant.orgId,\n factoryProjectId: route.factoryProjectId,\n integrationId: route.integrationId,\n };\n const previous = (await intake.listLabelRoutes(scope)).find(existing => existing.label === route.label);\n if (route.board === null) {\n await intake.clearLabelRoute({ ...scope, label: route.label });\n } else {\n await intake.setLabelRoute({ ...scope, label: route.label, board: route.board, userId: tenant.userId });\n }\n\n // Cards already carrying the label follow the route: onto the new board, or back to\n // Work when the label is no longer routed anywhere.\n let relocated: { moved: number; skipped: number } | null = null;\n if (workItems && boardRegistry && (previous?.board ?? null) !== route.board) {\n try {\n relocated = await relocateLabeledCards({\n workItems,\n boardRegistry,\n orgId: tenant.orgId,\n userId: tenant.userId,\n factoryProjectId: route.factoryProjectId,\n integrationId: route.integrationId,\n label: route.label,\n routes: await intake.listLabelRoutes(scope),\n });\n } catch (error) {\n // The route is saved; the cards can be moved by hand.\n console.error(`[factory] intake label route could not relocate ${route.integrationId} cards:`, error);\n }\n }\n await audit.emit({\n context: loose(c),\n input: {\n action: 'factory.intake.label_route_updated',\n factoryProjectId: route.factoryProjectId,\n targets: [{ type: 'intake_label_route', id: `${route.integrationId}:${route.label}` }],\n metadata: { board: route.board, ...(relocated ? { relocated } : {}) },\n },\n });\n return c.json({\n routes: await intake.listLabelRoutes({ orgId: tenant.orgId, factoryProjectId: route.factoryProjectId }),\n ...(relocated ? { relocated } : {}),\n });\n },\n }),\n registerApiRoute('/web/intake/sources', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n const { pages, failures } = await settleByIntegration(\n integrations.map(integration => ({\n integrationId: integration.id,\n read: () => integration.intake.listSources(tenant),\n })),\n );\n return c.json({\n sources: pages.flatMap(({ integrationId, value }) => value.map(source => ({ integrationId, ...source }))),\n failures,\n });\n },\n }),\n registerApiRoute('/web/intake/items', {\n method: 'GET',\n requiresAuth: false,\n handler: async c => {\n const tenant = await this.#resolveTenant(loose(c));\n if ('response' in tenant) return tenant.response;\n const cursors = decodeCursor(c.req.query('cursor'));\n if (!cursors) return c.json({ error: 'invalid_cursor' }, 400);\n\n await intake.ensureReady();\n const config = await intake.getConfig({ orgId: tenant.orgId, integrationIds });\n const { pages, failures } = await settleByIntegration(\n integrations.flatMap(integration => {\n const selection = config[integration.id];\n if (!selection?.enabled || !selection.sourceIds?.length) return [];\n const sourceIds = selection.sourceIds;\n const cursor = cursors[integration.id];\n return [\n {\n integrationId: integration.id,\n read: () => integration.intake.listItems({ ...tenant, sourceIds, ...(cursor ? { cursor } : {}) }),\n },\n ];\n }),\n );\n\n const items: AggregatedIntakeItem[] = [];\n const nextCursors: Record<string, string> = {};\n for (const { integrationId, value } of pages) {\n items.push(\n ...value.items.map(item => {\n const { source, ...candidate } = item;\n return { ...candidate, integrationId, externalSource: { integrationId, ...source } };\n }),\n );\n if (value.nextCursor) nextCursors[integrationId] = value.nextCursor;\n }\n // Keep the cursor an unavailable integration came in with, so the next page resumes there instead of replaying it.\n for (const { integrationId } of failures) {\n const cursor = cursors[integrationId];\n if (cursor) nextCursors[integrationId] = cursor;\n }\n return c.json({ items, nextCursor: encodeCursor(nextCursors), failures });\n },\n }),\n ];\n }\n}\n"],"mappings":";;;;;;;AA4CA,MAAM,mBAAmB;;;;;;AAMzB,MAAM,wBAAwB;;;;;AAM9B,SAAS,qBAAqB,eAAuB,MAA4B;CAC/E,MAAM,aAAa,KAAK,UAAU;CAClC,OAAO,OAAO,eAAe,YAAY,WAAW,SAAS,IACzD,CAAC,KAAK,OAAO,YAAY,GAAG,cAAc,GAAG,YAAY,IACzD,CAAC,KAAK,OAAO,UAAU;AAC7B;;;;;;;AAQA,eAAe,oBAAoB,EACjC,WACA,aACA,eACA,OACA,QACA,kBACA,UACA,aACA,wBAW8C;CAC9C,IAAI,CAAC,cAAc,IAAI,WAAW,GAAG,OAAO;EAAE,OAAO;EAAG,SAAS;CAAE;CAEnE,MAAM,6BAAa,IAAI,IAAY;CACnC,IAAI;CACJ,MAAM,WAAW,KAAK,IAAI,IAAI;CAC9B,KAAK,IAAI,OAAO,GAAG,OAAO,kBAAkB,QAAQ,GAAG;EACrD,MAAM,SAAS,MAAM,YACnB,YAAY,UAEV,YAAY,OAAO,UAAU;GAC3B;GACA;GACA,WAAW,CAAC,QAAQ;GACpB;GACA;EACF,CAAC,GACH,WAAW,KAAK,IAAI,CACtB;EACA,KAAK,MAAM,QAAQ,OAAO,OAAO;GAC/B,IAAI,KAAK,aAAa,UAAU;GAChC,KAAK,MAAM,OAAO,qBAAqB,YAAY,IAAI,IAAI,GAAG,WAAW,IAAI,GAAG;EAClF;EACA,IAAI,CAAC,OAAO,YAAY;EACxB,SAAS,OAAO;CAClB;CACA,IAAI,WAAW,SAAS,GAAG,OAAO;EAAE,OAAO;EAAG,SAAS;CAAE;CAEzD,IAAI,QAAQ;CACZ,IAAI,UAAU;CACd,MAAM,QAAQ,MAAM,UAAU,KAAK;EAAE;EAAO;CAAiB,CAAC;CAC9D,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,SAAS,KAAK;EACpB,IAAI,CAAC,UAAU,OAAO,kBAAkB,YAAY,MAAM,CAAC,WAAW,IAAI,OAAO,UAAU,GAAG;EAC9F,MAAM,UAAU,MAAM,gBAAgB;GAAE;GAAW;GAAe;GAAQ;GAAM;EAAY,CAAC;EAC7F,IAAI,YAAY,SAAS,SAAS;OAC7B,IAAI,YAAY,WAAW,WAAW;CAC7C;CACA,OAAO;EAAE;EAAO;CAAQ;AAC1B;;;;;;AAOA,eAAe,qBAAqB,EAClC,WACA,eACA,OACA,QACA,kBACA,eACA,OACA,UAU8C;CAC9C,MAAM,UAAU,qBAAqB,KAAK;CAC1C,IAAI,QAAQ;CACZ,IAAI,UAAU;CACd,KAAK,MAAM,QAAQ,MAAM,UAAU,KAAK;EAAE;EAAO;CAAiB,CAAC,GAAG;EACpE,MAAM,SAAS,KAAK;EACpB,IAAI,CAAC,UAAU,OAAO,kBAAkB,iBAAiB,OAAO,SAAS,SAAS;EAClF,MAAM,SAAS,WAAW,IAAI;EAC9B,IAAI,CAAC,OAAO,MAAK,cAAa,qBAAqB,SAAS,MAAM,OAAO,GAAG;EAE5E,MAAM,UAAU,MAAM,gBAAgB;GAAE;GAAW;GAAe;GAAQ;GAAM,aAD5D,wBAAwB,QAAQ,MAAM,CAAC,EAAE,SAAS;EACsB,CAAC;EAC7F,IAAI,YAAY,SAAS,SAAS;OAC7B,IAAI,YAAY,WAAW,WAAW;CAC7C;CACA,OAAO;EAAE;EAAO;CAAQ;AAC1B;AAUA,MAAM,2BAA2B;;AAGjC,SAAS,YACP,eACA,MACA,YAAoB,0BACR;CACZ,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,MAAM,SAAS,KAAK,IAAI,GAAG,SAAS;EACpC,MAAM,QAAQ,iBACN,uBAAO,IAAI,MAAM,GAAG,cAAc,yBAAyB,KAAK,MAAM,SAAS,GAAI,EAAE,EAAE,CAAC,GAC9F,MACF;EACA,KAAK,CAAC,CACH,KAAK,SAAS,MAAM,CAAC,CACrB,cAAc,aAAa,KAAK,CAAC;CACtC,CAAC;AACH;;;;;AAMA,eAAe,oBACb,UACsG;CACtG,MAAM,UAAU,MAAM,QAAQ,IAC5B,SAAS,IAAI,OAAO,EAAE,eAAe,WAA2C;EAC9E,IAAI;GACF,OAAO;IAAE;IAAe,OAAO,MAAM,YAAY,eAAe,IAAI;GAAE;EACxE,SAAS,OAAO;GACd,QAAQ,MAAM,gCAAgC,cAAc,mBAAmB,KAAK;GACpF,OAAO;IAAE;IAAe,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;GAAE;EAC1F;CACF,CAAC,CACH;CACA,MAAM,QAAoD,CAAC;CAC3D,MAAM,WAAuC,CAAC;CAC9C,KAAK,MAAM,SAAS,SAClB,IAAI,WAAW,OAAO,MAAM,KAAK,KAAK;MACjC,SAAS,KAAK,KAAK;CAE1B,OAAO;EAAE;EAAO;CAAS;AAC3B;;AAWA,SAAgB,mBAAmB,MAAqC;CACtE,IAAI,OAAO,SAAS,YAAY,SAAS,QAAQ,MAAM,QAAQ,IAAI,GAAG,OAAO;CAC7E,MAAM,EAAE,eAAe,UAAU,kBAAkB,UAAU;CAC7D,MAAM,QAAQ,UAAmB,OAAO,UAAU,YAAY,MAAM,SAAS,KAAK,MAAM,UAAU;CAClG,IAAI,CAAC,KAAK,aAAa,KAAK,CAAC,KAAK,QAAQ,GAAG,OAAO;CACpD,IAAI,qBAAqB,QAAQ,CAAC,KAAK,gBAAgB,GAAG,OAAO;CACjE,IAAI,UAAU,KAAA,KAAa,UAAU,QAAQ,CAAC,KAAK,KAAK,GAAG,OAAO;CAClE,OAAO;EACU;EACL;EACQ;EAClB,OAAQ,SAAuC;CACjD;AACF;AAEA,MAAM,aAAa,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG;AAC5C,MAAM,uBAAuB,EAAE,OAAO;CACpC,kBAAkB;CAClB,eAAe;CACf,OAAO,EACJ,OAAO,CAAC,CACR,IAAI,GAAG,CAAC,CACR,UAAU,oBAAoB,CAAC,CAC/B,QAAO,UAAS,MAAM,SAAS,GAAG,yBAAyB;;CAE9D,OAAO,WAAW,QAAQ,CAAC,CAAC,WAAU,UAAS,SAAS,IAAI;AAC9D,CAAC;;AAID,SAAgB,sBAAsB,MAAwC;CAC5E,MAAM,SAAS,qBAAqB,UAAU,IAAI;CAClD,OAAO,OAAO,UAAU,OAAO,OAAO;AACxC;AAEA,SAAS,MAAM,GAAqB;CAClC,OAAO;AACT;AAEA,SAAS,eAAe,OAA6C;CACnE,IAAI,UAAU,MAAM,OAAO;CAC3B,IAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,MAAM,SAAS,KAAK,OAAO,KAAA;CACxD,MAAM,MAAM,MAAM,QAAQ,SAAyB,OAAO,SAAS,YAAY,KAAK,SAAS,KAAK,KAAK,UAAU,GAAG;CACpH,OAAO,IAAI,WAAW,MAAM,UAAU,IAAI,IAAI,GAAG,CAAC,CAAC,SAAS,IAAI,SAAS,MAAM,KAAA;AACjF;;AAGA,SAAgB,kBAAkB,MAAoC;CACpE,IAAI,OAAO,SAAS,YAAY,SAAS,QAAQ,MAAM,QAAQ,IAAI,GAAG,OAAO;CAC7E,MAAM,UAAU,OAAO,QAAQ,IAAI;CACnC,IAAI,QAAQ,SAAS,IAAI,OAAO;CAIhC,MAAM,SAAuB,OAAO,OAAO,IAAI;CAC/C,KAAK,MAAM,CAAC,eAAe,UAAU,SAAS;EAC5C,IACE,CAAC,iBACD,cAAc,SAAS,OACvB,OAAO,UAAU,YACjB,UAAU,QACV,MAAM,QAAQ,KAAK,GAEnB,OAAO;EAET,MAAM,YAAY;EAClB,IAAI,OAAO,UAAU,YAAY,WAAW,OAAO;EACnD,MAAM,YAAY,eAAe,UAAU,aAAa,IAAI;EAC5D,IAAI,cAAc,KAAA,GAAW,OAAO;EACpC,OAAO,iBAAiB;GAAE,SAAS,UAAU;GAAS;EAAU;CAClE;CACA,OAAO;AACT;AAEA,SAAS,aAAa,SAAgD;CACpE,OAAO,OAAO,KAAK,OAAO,CAAC,CAAC,SAAS,IAAI,OAAO,KAAK,KAAK,UAAU,OAAO,CAAC,CAAC,CAAC,SAAS,WAAW,IAAI;AACxG;AAEA,SAAS,aAAa,OAA0D;CAC9E,IAAI,CAAC,OAAO,OAAO,CAAC;CACpB,IAAI;EACF,MAAM,SAAS,KAAK,MAAM,OAAO,KAAK,OAAO,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC;EAC1E,IAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,MAAM,QAAQ,MAAM,GAAG,OAAO;EACnF,MAAM,UAAU,OAAO,QAAQ,MAAM;EACrC,IAAI,QAAQ,MAAM,CAAC,KAAK,YAAY,CAAC,OAAO,OAAO,WAAW,QAAQ,GAAG,OAAO;EAChF,OAAO,OAAO,YAAY,OAAO;CACnC,QAAQ;EACN,OAAO;CACT;AACF;AAEA,IAAa,eAAb,cAAkC,MAAwB;CACxD,MAAMA,eAAe,GAAiF;EACpG,MAAM,KAAK,KAAK,KAAK,WAAW,CAAC;EACjC,MAAM,SAAS,KAAK,KAAK,KAAK,OAAO,CAAC;EACtC,IAAI,CAAC,QAAQ,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,eAAe,GAAG,GAAG,EAAE;EACvE,IAAI,CAAC,OAAO,OACV,OAAO,EACL,UAAU,EAAE,KACV;GAAE,OAAO;GAAyB,SAAS;EAAiD,GAC5F,GACF,EACF;EAEF,OAAO;GAAE,OAAO,OAAO;GAAO,QAAQ,OAAO;EAAO;CACtD;CAEA,SAAqB;EACnB,MAAM,EAAE,OAAO,QAAQ,UAAU,eAAe,CAAC,GAAG,eAAe,cAAc,KAAK;EACtF,MAAM,iBAAiB,aAAa,KAAI,gBAAe,YAAY,EAAE;EAErE,OAAO;GACL,iBAAiB,sBAAsB;IACrC,QAAQ;IACR,cAAc;IACd,SAAS,OAAM,MAAK;KAClB,MAAM,SAAS,MAAM,KAAKA,eAAe,MAAM,CAAC,CAAC;KACjD,IAAI,cAAc,QAAQ,OAAO,OAAO;KACxC,MAAM,OAAO,YAAY;KACzB,MAAM,SAAS,MAAM,OAAO,UAAU;MAAE,OAAO,OAAO;MAAO;KAAe,CAAC;KAC7E,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC;IAC1B;GACF,CAAC;GACD,iBAAiB,sBAAsB;IACrC,QAAQ;IACR,cAAc;IACd,SAAS,OAAM,MAAK;KAClB,MAAM,SAAS,MAAM,KAAKA,eAAe,MAAM,CAAC,CAAC;KACjD,IAAI,cAAc,QAAQ,OAAO,OAAO;KAExC,IAAI;KACJ,IAAI;MACF,OAAO,MAAM,EAAE,IAAI,KAAK;KAC1B,QAAQ;MACN,OAAO,EAAE,KAAK,EAAE,OAAO,oBAAoB,GAAG,GAAG;KACnD;KACA,MAAM,SAAS,kBAAkB,IAAI;KACrC,IAAI,CAAC,QACH,OAAO,EAAE,KAAK,EAAE,OAAO,iBAAiB,GAAG,GAAG;KAGhD,MAAM,mBAAiC,OAAO,OAAO,IAAI;KACzD,KAAK,MAAM,CAAC,eAAe,cAAc,OAAO,QAAQ,MAAM,GAAG;MAC/D,IAAI,eAAe,SAAS,aAAa,GAAG;OAC1C,iBAAiB,iBAAiB;OAClC;MACF;MACA,IAAI,UAAU,WAAW,UAAU,WAAW,QAC5C,OAAO,EAAE,KAAK,EAAE,OAAO,iBAAiB,GAAG,GAAG;KAElD;KAEA,MAAM,OAAO,YAAY;KACzB,MAAM,OAAO,WAAW;MAAE,OAAO,OAAO;MAAO,QAAQ;KAAiB,CAAC;KACzE,MAAM,MAAM,KAAK;MACf,SAAS,MAAM,CAAC;MAChB,OAAO;OACL,QAAQ;OACR,SAAS,CAAC;QAAE,MAAM;QAAiB,IAAI,OAAO;OAAM,CAAC;OACrD,UAAU,OAAO,YACf,OAAO,QAAQ,gBAAgB,CAAC,CAAC,KAAK,CAAC,eAAe,eAAe,CACnE,eACA;QAAE,SAAS,UAAU;QAAS,SAAS,UAAU,WAAW,UAAU;OAAK,CAC7E,CAAC,CACH;MACF;KACF,CAAC;KACD,OAAO,EAAE,KAAK,EAAE,QAAQ,iBAAiB,CAAC;IAC5C;GACF,CAAC;GACD,iBAAiB,wBAAwB;IACvC,QAAQ;IACR,cAAc;IACd,SAAS,OAAM,MAAK;KAClB,MAAM,SAAS,MAAM,KAAKA,eAAe,MAAM,CAAC,CAAC;KACjD,IAAI,cAAc,QAAQ,OAAO,OAAO;KACxC,MAAM,OAAO,YAAY;KACzB,OAAO,EAAE,KAAK,EAAE,UAAU,MAAM,OAAO,aAAa,EAAE,OAAO,OAAO,MAAM,CAAC,EAAE,CAAC;IAChF;GACF,CAAC;GACD,iBAAiB,wBAAwB;IACvC,QAAQ;IACR,cAAc;IACd,SAAS,OAAM,MAAK;KAClB,MAAM,SAAS,MAAM,KAAKA,eAAe,MAAM,CAAC,CAAC;KACjD,IAAI,cAAc,QAAQ,OAAO,OAAO;KAExC,IAAI;KACJ,IAAI;MACF,OAAO,MAAM,EAAE,IAAI,KAAK;KAC1B,QAAQ;MACN,OAAO,EAAE,KAAK,EAAE,OAAO,oBAAoB,GAAG,GAAG;KACnD;KACA,MAAM,UAAU,mBAAmB,IAAI;KACvC,IAAI,CAAC,WAAW,CAAC,eAAe,SAAS,QAAQ,aAAa,GAC5D,OAAO,EAAE,KAAK,EAAE,OAAO,kBAAkB,GAAG,GAAG;KAEjD,IAAI,QAAQ,oBAAoB,UAE1B;UAAA,CAAC,MADiB,SAAS,IAAI;OAAE,OAAO,OAAO;OAAO,IAAI,QAAQ;MAAiB,CAAC,GAC1E,OAAO,EAAE,KAAK,EAAE,OAAO,4BAA4B,GAAG,GAAG;KAAA;KAEzE,IAAI,QAAQ,UAAU,QAAQ,CAAC,eAAe,IAAI,QAAQ,KAAK,GAC7D,OAAO,EAAE,KAAK;MAAE,OAAO;MAAiB,SAAS,UAAU,QAAQ,MAAM;KAAqB,GAAG,GAAG;KAGtG,MAAM,OAAO,YAAY;KACzB,IAAI,wBAAwB,QAAQ;KACpC,IAAI,YAAuD;KAC3D,IAAI,QAAQ,qBAAqB,MAM/B,yBAAwB,MALM,OAAO,aAAa;MAChD,OAAO,OAAO;MACd,eAAe,QAAQ;MACvB,UAAU,QAAQ;KACpB,CAAC,EAAA,EACwC,oBAAoB;UACxD;MACL,MAAM,kBAAkB,MAAM,OAAO,WAAW;OAC9C,OAAO,OAAO;OACd,eAAe,QAAQ;OACvB,UAAU,QAAQ;MACpB,CAAC;MACD,MAAM,OAAO,WAAW;OACtB,OAAO,OAAO;OACd,QAAQ,OAAO;OACf,eAAe,QAAQ;OACvB,UAAU,QAAQ;OAClB,kBAAkB,QAAQ;OAC1B,OAAO,QAAQ;MACjB,CAAC;MAKD,MAAM,YAAY,QAAQ;MAC1B,MAAM,cAAc,aAAa,MAAK,cAAa,UAAU,OAAO,QAAQ,aAAa;MACzF,IACE,aACA,iBACA,eACA,iBAAiB,qBAAqB,QAAQ,oBAC9C,cAAc,QACd,gBAAgB,UAAU,WAE1B,IAAI;OAKF,MAAM,uBAAsB,MAJP,OAAO,UAAU;QACpC,OAAO,OAAO;QACd,gBAAgB,CAAC,QAAQ,aAAa;OACxC,CAAC,EAAA,CACkC,QAAQ,cAAc,EAAE,aAAa,CAAC;OACzE,MAAM,uBAAuB,oBAAoB,SAAS,QAAQ,QAAQ,IACtE,sBACA,CAAC,GAAG,qBAAqB,QAAQ,QAAQ;OAC7C,YAAY,MAAM,oBAAoB;QACpC;QACA;QACA;QACA,OAAO,OAAO;QACd,QAAQ,OAAO;QACf,kBAAkB,QAAQ;QAC1B,UAAU,QAAQ;QAClB;QACA,aAAa;OACf,CAAC;MACH,SAAS,OAAO;OAEd,QAAQ,MAAM,8CAA8C,QAAQ,cAAc,UAAU,KAAK;MACnG;KAEJ;KACA,MAAM,MAAM,KAAK;MACf,SAAS,MAAM,CAAC;MAChB,OAAO;OACL,QAAQ;OACR,GAAI,wBAAwB,EAAE,kBAAkB,sBAAsB,IAAI,CAAC;OAC3E,SAAS,CAAC;QAAE,MAAM;QAAiB,IAAI,GAAG,QAAQ,cAAc,GAAG,QAAQ;OAAW,CAAC;OACvF,UAAU;QACR,kBAAkB,QAAQ;QAC1B,OAAO,QAAQ;QACf,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;OACnC;MACF;KACF,CAAC;KACD,OAAO,EAAE,KAAK;MACZ,UAAU,MAAM,OAAO,aAAa,EAAE,OAAO,OAAO,MAAM,CAAC;MAC3D,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;KACnC,CAAC;IACH;GACF,CAAC;GACD,iBAAiB,4BAA4B;IAC3C,QAAQ;IACR,cAAc;IACd,SAAS,OAAM,MAAK;KAClB,MAAM,SAAS,MAAM,KAAKA,eAAe,MAAM,CAAC,CAAC;KACjD,IAAI,cAAc,QAAQ,OAAO,OAAO;KACxC,MAAM,mBAAmB,EAAE,IAAI,MAAM,kBAAkB;KACvD,MAAM,OAAO,YAAY;KACzB,OAAO,EAAE,KAAK,EACZ,QAAQ,MAAM,OAAO,gBAAgB;MACnC,OAAO,OAAO;MACd,GAAI,mBAAmB,EAAE,iBAAiB,IAAI,CAAC;KACjD,CAAC,EACH,CAAC;IACH;GACF,CAAC;GACD,iBAAiB,4BAA4B;IAC3C,QAAQ;IACR,cAAc;IACd,SAAS,OAAM,MAAK;KAClB,MAAM,SAAS,MAAM,KAAKA,eAAe,MAAM,CAAC,CAAC;KACjD,IAAI,cAAc,QAAQ,OAAO,OAAO;KAExC,IAAI;KACJ,IAAI;MACF,OAAO,MAAM,EAAE,IAAI,KAAK;KAC1B,QAAQ;MACN,OAAO,EAAE,KAAK,EAAE,OAAO,oBAAoB,GAAG,GAAG;KACnD;KACA,MAAM,QAAQ,sBAAsB,IAAI;KACxC,IAAI,CAAC,SAAS,CAAC,eAAe,SAAS,MAAM,aAAa,GACxD,OAAO,EAAE,KAAK,EAAE,OAAO,sBAAsB,GAAG,GAAG;KAErD,IAAI,UAEE;UAAA,CAAC,MADiB,SAAS,IAAI;OAAE,OAAO,OAAO;OAAO,IAAI,MAAM;MAAiB,CAAC,GACxE,OAAO,EAAE,KAAK,EAAE,OAAO,4BAA4B,GAAG,GAAG;KAAA;KAEzE,IAAI,MAAM,UAAU,QAAQ,CAAC,eAAe,IAAI,MAAM,KAAK,GACzD,OAAO,EAAE,KAAK;MAAE,OAAO;MAAiB,SAAS,UAAU,MAAM,MAAM;KAAqB,GAAG,GAAG;KAGpG,MAAM,OAAO,YAAY;KACzB,MAAM,QAAQ;MACZ,OAAO,OAAO;MACd,kBAAkB,MAAM;MACxB,eAAe,MAAM;KACvB;KACA,MAAM,YAAY,MAAM,OAAO,gBAAgB,KAAK,EAAA,CAAG,MAAK,aAAY,SAAS,UAAU,MAAM,KAAK;KACtG,IAAI,MAAM,UAAU,MAClB,MAAM,OAAO,gBAAgB;MAAE,GAAG;MAAO,OAAO,MAAM;KAAM,CAAC;UAE7D,MAAM,OAAO,cAAc;MAAE,GAAG;MAAO,OAAO,MAAM;MAAO,OAAO,MAAM;MAAO,QAAQ,OAAO;KAAO,CAAC;KAKxG,IAAI,YAAuD;KAC3D,IAAI,aAAa,kBAAkB,UAAU,SAAS,UAAU,MAAM,OACpE,IAAI;MACF,YAAY,MAAM,qBAAqB;OACrC;OACA;OACA,OAAO,OAAO;OACd,QAAQ,OAAO;OACf,kBAAkB,MAAM;OACxB,eAAe,MAAM;OACrB,OAAO,MAAM;OACb,QAAQ,MAAM,OAAO,gBAAgB,KAAK;MAC5C,CAAC;KACH,SAAS,OAAO;MAEd,QAAQ,MAAM,mDAAmD,MAAM,cAAc,UAAU,KAAK;KACtG;KAEF,MAAM,MAAM,KAAK;MACf,SAAS,MAAM,CAAC;MAChB,OAAO;OACL,QAAQ;OACR,kBAAkB,MAAM;OACxB,SAAS,CAAC;QAAE,MAAM;QAAsB,IAAI,GAAG,MAAM,cAAc,GAAG,MAAM;OAAQ,CAAC;OACrF,UAAU;QAAE,OAAO,MAAM;QAAO,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;OAAG;MACtE;KACF,CAAC;KACD,OAAO,EAAE,KAAK;MACZ,QAAQ,MAAM,OAAO,gBAAgB;OAAE,OAAO,OAAO;OAAO,kBAAkB,MAAM;MAAiB,CAAC;MACtG,GAAI,YAAY,EAAE,UAAU,IAAI,CAAC;KACnC,CAAC;IACH;GACF,CAAC;GACD,iBAAiB,uBAAuB;IACtC,QAAQ;IACR,cAAc;IACd,SAAS,OAAM,MAAK;KAClB,MAAM,SAAS,MAAM,KAAKA,eAAe,MAAM,CAAC,CAAC;KACjD,IAAI,cAAc,QAAQ,OAAO,OAAO;KACxC,MAAM,EAAE,OAAO,aAAa,MAAM,oBAChC,aAAa,KAAI,iBAAgB;MAC/B,eAAe,YAAY;MAC3B,YAAY,YAAY,OAAO,YAAY,MAAM;KACnD,EAAE,CACJ;KACA,OAAO,EAAE,KAAK;MACZ,SAAS,MAAM,SAAS,EAAE,eAAe,YAAY,MAAM,KAAI,YAAW;OAAE;OAAe,GAAG;MAAO,EAAE,CAAC;MACxG;KACF,CAAC;IACH;GACF,CAAC;GACD,iBAAiB,qBAAqB;IACpC,QAAQ;IACR,cAAc;IACd,SAAS,OAAM,MAAK;KAClB,MAAM,SAAS,MAAM,KAAKA,eAAe,MAAM,CAAC,CAAC;KACjD,IAAI,cAAc,QAAQ,OAAO,OAAO;KACxC,MAAM,UAAU,aAAa,EAAE,IAAI,MAAM,QAAQ,CAAC;KAClD,IAAI,CAAC,SAAS,OAAO,EAAE,KAAK,EAAE,OAAO,iBAAiB,GAAG,GAAG;KAE5D,MAAM,OAAO,YAAY;KACzB,MAAM,SAAS,MAAM,OAAO,UAAU;MAAE,OAAO,OAAO;MAAO;KAAe,CAAC;KAC7E,MAAM,EAAE,OAAO,aAAa,MAAM,oBAChC,aAAa,SAAQ,gBAAe;MAClC,MAAM,YAAY,OAAO,YAAY;MACrC,IAAI,CAAC,WAAW,WAAW,CAAC,UAAU,WAAW,QAAQ,OAAO,CAAC;MACjE,MAAM,YAAY,UAAU;MAC5B,MAAM,SAAS,QAAQ,YAAY;MACnC,OAAO,CACL;OACE,eAAe,YAAY;OAC3B,YAAY,YAAY,OAAO,UAAU;QAAE,GAAG;QAAQ;QAAW,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;OAAG,CAAC;MAClG,CACF;KACF,CAAC,CACH;KAEA,MAAM,QAAgC,CAAC;KACvC,MAAM,cAAsC,CAAC;KAC7C,KAAK,MAAM,EAAE,eAAe,WAAW,OAAO;MAC5C,MAAM,KACJ,GAAG,MAAM,MAAM,KAAI,SAAQ;OACzB,MAAM,EAAE,QAAQ,GAAG,cAAc;OACjC,OAAO;QAAE,GAAG;QAAW;QAAe,gBAAgB;SAAE;SAAe,GAAG;QAAO;OAAE;MACrF,CAAC,CACH;MACA,IAAI,MAAM,YAAY,YAAY,iBAAiB,MAAM;KAC3D;KAEA,KAAK,MAAM,EAAE,mBAAmB,UAAU;MACxC,MAAM,SAAS,QAAQ;MACvB,IAAI,QAAQ,YAAY,iBAAiB;KAC3C;KACA,OAAO,EAAE,KAAK;MAAE;MAAO,YAAY,aAAa,WAAW;MAAG;KAAS,CAAC;IAC1E;GACF,CAAC;EACH;CACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatch-errors.d.ts","sourceRoot":"","sources":["../../src/rules/dispatch-errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,0BAA0B,EAC1B,gCAAgC,EACjC,MAAM,uCAAuC,CAAC;AAE/C,UAAU,8BAA8B;IACtC,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf;
|
|
1
|
+
{"version":3,"file":"dispatch-errors.d.ts","sourceRoot":"","sources":["../../src/rules/dispatch-errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,0BAA0B,EAC1B,gCAAgC,EACjC,MAAM,uCAAuC,CAAC;AAE/C,UAAU,8BAA8B;IACtC,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf;AA0BD,qBAAa,oBAAqB,SAAQ,KAAK;IAE3C,QAAQ,CAAC,IAAI,EAAE,0BAA0B;gBAAhC,IAAI,EAAE,0BAA0B,EACzC,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,YAAY;CAKzB;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,0BAA0B,CAErF;AAED,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,gCAAgC,GAAG,IAAI,GAC5C,8BAA8B,CAEhC"}
|
|
@@ -20,6 +20,14 @@ const FAILURE_METADATA = {
|
|
|
20
20
|
canRetry: true,
|
|
21
21
|
label: "Factory message delivery failed"
|
|
22
22
|
},
|
|
23
|
+
run_terminal_event_missing: {
|
|
24
|
+
canRetry: false,
|
|
25
|
+
label: "Agent run terminal event was not observed"
|
|
26
|
+
},
|
|
27
|
+
skill_delivery_ambiguous: {
|
|
28
|
+
canRetry: false,
|
|
29
|
+
label: "Factory skill delivery could not be confirmed"
|
|
30
|
+
},
|
|
23
31
|
run_overdue: {
|
|
24
32
|
canRetry: false,
|
|
25
33
|
label: "Agent run is overdue"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatch-errors.js","names":[],"sources":["../../src/rules/dispatch-errors.ts"],"sourcesContent":["import type {\n FactoryDispatchFailureCode,\n StoredFactoryDispatchFailureCode,\n} from '../storage/domains/work-items/base.js';\n\ninterface FactoryDispatchFailureMetadata {\n canRetry: boolean;\n label: string;\n}\n\nconst FAILURE_METADATA = {\n session_unavailable: { canRetry: true, label: 'Factory session unavailable' },\n source_control_missing: { canRetry: true, label: 'Source-control connection unavailable' },\n source_repository_missing: { canRetry: true, label: 'Source repository unavailable' },\n unsupported_provider_item: { canRetry: false, label: 'Unsupported provider work item' },\n notification_delivery_failed: { canRetry: true, label: 'Factory message delivery failed' },\n run_overdue: { canRetry: false, label: 'Agent run is overdue' },\n repository_git_missing: { canRetry: false, label: 'Git is unavailable in the workspace' },\n repository_egress_blocked: { canRetry: false, label: 'Repository network access is blocked' },\n repository_clone_failed: { canRetry: true, label: 'Repository clone failed' },\n repository_pull_failed: { canRetry: true, label: 'Repository update failed' },\n repository_push_failed: { canRetry: true, label: 'Repository push failed' },\n repository_commit_failed: { canRetry: true, label: 'Repository commit failed' },\n repository_cli_missing: { canRetry: false, label: 'GitHub CLI is unavailable in the workspace' },\n repository_pr_failed: { canRetry: true, label: 'Pull request creation failed' },\n run_configuration_invalid: { canRetry: false, label: 'Run configuration rejected by provider' },\n unknown: { canRetry: true, label: 'Factory automation failed' },\n // Retired: no path writes these any more, stored rows still read through here.\n plan_awaiting_approval: { canRetry: false, label: 'Plan waiting for review' },\n run_awaiting_input: { canRetry: false, label: 'Agent is waiting for an answer' },\n} satisfies Record<StoredFactoryDispatchFailureCode, FactoryDispatchFailureMetadata>;\n\nexport class FactoryDispatchError extends Error {\n constructor(\n readonly code: FactoryDispatchFailureCode,\n message: string,\n options?: ErrorOptions,\n ) {\n super(message, options);\n this.name = 'FactoryDispatchError';\n }\n}\n\nexport function factoryDispatchFailureCode(error: unknown): FactoryDispatchFailureCode {\n return error instanceof FactoryDispatchError ? error.code : 'unknown';\n}\n\nexport function factoryDispatchFailureMetadata(\n code: StoredFactoryDispatchFailureCode | null,\n): FactoryDispatchFailureMetadata {\n return code === null ? FAILURE_METADATA.unknown : FAILURE_METADATA[code];\n}\n"],"mappings":";AAUA,MAAM,mBAAmB;CACvB,qBAAqB;EAAE,UAAU;EAAM,OAAO;CAA8B;CAC5E,wBAAwB;EAAE,UAAU;EAAM,OAAO;CAAwC;CACzF,2BAA2B;EAAE,UAAU;EAAM,OAAO;CAAgC;CACpF,2BAA2B;EAAE,UAAU;EAAO,OAAO;CAAiC;CACtF,8BAA8B;EAAE,UAAU;EAAM,OAAO;CAAkC;CACzF,aAAa;EAAE,UAAU;EAAO,OAAO;CAAuB;CAC9D,wBAAwB;EAAE,UAAU;EAAO,OAAO;CAAsC;CACxF,2BAA2B;EAAE,UAAU;EAAO,OAAO;CAAuC;CAC5F,yBAAyB;EAAE,UAAU;EAAM,OAAO;CAA0B;CAC5E,wBAAwB;EAAE,UAAU;EAAM,OAAO;CAA2B;CAC5E,wBAAwB;EAAE,UAAU;EAAM,OAAO;CAAyB;CAC1E,0BAA0B;EAAE,UAAU;EAAM,OAAO;CAA2B;CAC9E,wBAAwB;EAAE,UAAU;EAAO,OAAO;CAA6C;CAC/F,sBAAsB;EAAE,UAAU;EAAM,OAAO;CAA+B;CAC9E,2BAA2B;EAAE,UAAU;EAAO,OAAO;CAAyC;CAC9F,SAAS;EAAE,UAAU;EAAM,OAAO;CAA4B;CAE9D,wBAAwB;EAAE,UAAU;EAAO,OAAO;CAA0B;CAC5E,oBAAoB;EAAE,UAAU;EAAO,OAAO;CAAiC;AACjF;AAEA,IAAa,uBAAb,cAA0C,MAAM;CAEnC;CADX,YACE,MACA,SACA,SACA;EACA,MAAM,SAAS,OAAO;EAJb,KAAA,OAAA;EAKT,KAAK,OAAO;CACd;AACF;AAEA,SAAgB,2BAA2B,OAA4C;CACrF,OAAO,iBAAiB,uBAAuB,MAAM,OAAO;AAC9D;AAEA,SAAgB,+BACd,MACgC;CAChC,OAAO,SAAS,OAAO,iBAAiB,UAAU,iBAAiB;AACrE"}
|
|
1
|
+
{"version":3,"file":"dispatch-errors.js","names":[],"sources":["../../src/rules/dispatch-errors.ts"],"sourcesContent":["import type {\n FactoryDispatchFailureCode,\n StoredFactoryDispatchFailureCode,\n} from '../storage/domains/work-items/base.js';\n\ninterface FactoryDispatchFailureMetadata {\n canRetry: boolean;\n label: string;\n}\n\nconst FAILURE_METADATA = {\n session_unavailable: { canRetry: true, label: 'Factory session unavailable' },\n source_control_missing: { canRetry: true, label: 'Source-control connection unavailable' },\n source_repository_missing: { canRetry: true, label: 'Source repository unavailable' },\n unsupported_provider_item: { canRetry: false, label: 'Unsupported provider work item' },\n notification_delivery_failed: { canRetry: true, label: 'Factory message delivery failed' },\n run_terminal_event_missing: { canRetry: false, label: 'Agent run terminal event was not observed' },\n skill_delivery_ambiguous: { canRetry: false, label: 'Factory skill delivery could not be confirmed' },\n run_overdue: { canRetry: false, label: 'Agent run is overdue' },\n repository_git_missing: { canRetry: false, label: 'Git is unavailable in the workspace' },\n repository_egress_blocked: { canRetry: false, label: 'Repository network access is blocked' },\n repository_clone_failed: { canRetry: true, label: 'Repository clone failed' },\n repository_pull_failed: { canRetry: true, label: 'Repository update failed' },\n repository_push_failed: { canRetry: true, label: 'Repository push failed' },\n repository_commit_failed: { canRetry: true, label: 'Repository commit failed' },\n repository_cli_missing: { canRetry: false, label: 'GitHub CLI is unavailable in the workspace' },\n repository_pr_failed: { canRetry: true, label: 'Pull request creation failed' },\n run_configuration_invalid: { canRetry: false, label: 'Run configuration rejected by provider' },\n unknown: { canRetry: true, label: 'Factory automation failed' },\n // Retired: no path writes these any more, stored rows still read through here.\n plan_awaiting_approval: { canRetry: false, label: 'Plan waiting for review' },\n run_awaiting_input: { canRetry: false, label: 'Agent is waiting for an answer' },\n} satisfies Record<StoredFactoryDispatchFailureCode, FactoryDispatchFailureMetadata>;\n\nexport class FactoryDispatchError extends Error {\n constructor(\n readonly code: FactoryDispatchFailureCode,\n message: string,\n options?: ErrorOptions,\n ) {\n super(message, options);\n this.name = 'FactoryDispatchError';\n }\n}\n\nexport function factoryDispatchFailureCode(error: unknown): FactoryDispatchFailureCode {\n return error instanceof FactoryDispatchError ? error.code : 'unknown';\n}\n\nexport function factoryDispatchFailureMetadata(\n code: StoredFactoryDispatchFailureCode | null,\n): FactoryDispatchFailureMetadata {\n return code === null ? FAILURE_METADATA.unknown : FAILURE_METADATA[code];\n}\n"],"mappings":";AAUA,MAAM,mBAAmB;CACvB,qBAAqB;EAAE,UAAU;EAAM,OAAO;CAA8B;CAC5E,wBAAwB;EAAE,UAAU;EAAM,OAAO;CAAwC;CACzF,2BAA2B;EAAE,UAAU;EAAM,OAAO;CAAgC;CACpF,2BAA2B;EAAE,UAAU;EAAO,OAAO;CAAiC;CACtF,8BAA8B;EAAE,UAAU;EAAM,OAAO;CAAkC;CACzF,4BAA4B;EAAE,UAAU;EAAO,OAAO;CAA4C;CAClG,0BAA0B;EAAE,UAAU;EAAO,OAAO;CAAgD;CACpG,aAAa;EAAE,UAAU;EAAO,OAAO;CAAuB;CAC9D,wBAAwB;EAAE,UAAU;EAAO,OAAO;CAAsC;CACxF,2BAA2B;EAAE,UAAU;EAAO,OAAO;CAAuC;CAC5F,yBAAyB;EAAE,UAAU;EAAM,OAAO;CAA0B;CAC5E,wBAAwB;EAAE,UAAU;EAAM,OAAO;CAA2B;CAC5E,wBAAwB;EAAE,UAAU;EAAM,OAAO;CAAyB;CAC1E,0BAA0B;EAAE,UAAU;EAAM,OAAO;CAA2B;CAC9E,wBAAwB;EAAE,UAAU;EAAO,OAAO;CAA6C;CAC/F,sBAAsB;EAAE,UAAU;EAAM,OAAO;CAA+B;CAC9E,2BAA2B;EAAE,UAAU;EAAO,OAAO;CAAyC;CAC9F,SAAS;EAAE,UAAU;EAAM,OAAO;CAA4B;CAE9D,wBAAwB;EAAE,UAAU;EAAO,OAAO;CAA0B;CAC5E,oBAAoB;EAAE,UAAU;EAAO,OAAO;CAAiC;AACjF;AAEA,IAAa,uBAAb,cAA0C,MAAM;CAEnC;CADX,YACE,MACA,SACA,SACA;EACA,MAAM,SAAS,OAAO;EAJb,KAAA,OAAA;EAKT,KAAK,OAAO;CACd;AACF;AAEA,SAAgB,2BAA2B,OAA4C;CACrF,OAAO,iBAAiB,uBAAuB,MAAM,OAAO;AAC9D;AAEA,SAAgB,+BACd,MACgC;CAChC,OAAO,SAAS,OAAO,iBAAiB,UAAU,iBAAiB;AACrE"}
|
|
@@ -85,6 +85,7 @@ export declare const FACTORY_DISPATCH_CONSTANTS: {
|
|
|
85
85
|
readonly maxBackoffMs: 60000;
|
|
86
86
|
readonly skillCompletionObservationTimeoutMs: number;
|
|
87
87
|
readonly runRegistryHeartbeatMs: number;
|
|
88
|
+
readonly openRunStaleMs: 30000;
|
|
88
89
|
readonly maxInFlight: 25;
|
|
89
90
|
readonly stages: readonly ["intake", "triage", "planning", "execute", "review", "done", "canceled"];
|
|
90
91
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/rules/dispatcher.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAgC,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAU5G,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/rules/dispatcher.ts"],"names":[],"mappings":"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"}
|