@mastra/factory 0.21.1-alpha.3 → 0.22.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/dist/capabilities/version-control.d.ts +13 -8
  2. package/dist/capabilities/version-control.d.ts.map +1 -1
  3. package/dist/environment/build-runner.d.ts +72 -0
  4. package/dist/environment/build-runner.d.ts.map +1 -0
  5. package/dist/environment/build-runner.js +131 -0
  6. package/dist/environment/build-runner.js.map +1 -0
  7. package/dist/environment/build-schedule.d.ts +40 -0
  8. package/dist/environment/build-schedule.d.ts.map +1 -0
  9. package/dist/environment/build-schedule.js +94 -0
  10. package/dist/environment/build-schedule.js.map +1 -0
  11. package/dist/environment/build-workflow.d.ts +62 -0
  12. package/dist/environment/build-workflow.d.ts.map +1 -0
  13. package/dist/environment/build-workflow.js +149 -0
  14. package/dist/environment/build-workflow.js.map +1 -0
  15. package/dist/environment/build.d.ts +111 -0
  16. package/dist/environment/build.d.ts.map +1 -0
  17. package/dist/environment/build.js +344 -0
  18. package/dist/environment/build.js.map +1 -0
  19. package/dist/environment/heads.d.ts +36 -0
  20. package/dist/environment/heads.d.ts.map +1 -0
  21. package/dist/environment/heads.js +60 -0
  22. package/dist/environment/heads.js.map +1 -0
  23. package/dist/factory.d.ts +18 -4
  24. package/dist/factory.d.ts.map +1 -1
  25. package/dist/factory.js +89 -15
  26. package/dist/factory.js.map +1 -1
  27. package/dist/index.d.ts +2 -1
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +3 -2
  30. package/dist/integrations/base.d.ts +17 -2
  31. package/dist/integrations/base.d.ts.map +1 -1
  32. package/dist/integrations/github/commits.d.ts +11 -0
  33. package/dist/integrations/github/commits.d.ts.map +1 -1
  34. package/dist/integrations/github/commits.js +20 -1
  35. package/dist/integrations/github/commits.js.map +1 -1
  36. package/dist/integrations/github/config.d.ts +3 -3
  37. package/dist/integrations/github/config.d.ts.map +1 -1
  38. package/dist/integrations/github/config.js +1 -1
  39. package/dist/integrations/github/config.js.map +1 -1
  40. package/dist/integrations/github/routes.d.ts +2 -2
  41. package/dist/integrations/github/routes.d.ts.map +1 -1
  42. package/dist/integrations/github/routes.js +6 -5
  43. package/dist/integrations/github/routes.js.map +1 -1
  44. package/dist/integrations/github/rules.d.ts +3 -1
  45. package/dist/integrations/github/rules.d.ts.map +1 -1
  46. package/dist/integrations/github/rules.js +31 -1
  47. package/dist/integrations/github/rules.js.map +1 -1
  48. package/dist/integrations/github/sandbox.d.ts +35 -0
  49. package/dist/integrations/github/sandbox.d.ts.map +1 -1
  50. package/dist/integrations/github/sandbox.js +203 -2
  51. package/dist/integrations/github/sandbox.js.map +1 -1
  52. package/dist/integrations/github/session-subscriptions.d.ts.map +1 -1
  53. package/dist/integrations/github/session-subscriptions.js +117 -62
  54. package/dist/integrations/github/session-subscriptions.js.map +1 -1
  55. package/dist/integrations/github/token-refresh.d.ts +7 -0
  56. package/dist/integrations/github/token-refresh.d.ts.map +1 -1
  57. package/dist/integrations/github/token-refresh.js.map +1 -1
  58. package/dist/integrations/github/webhook.js.map +1 -1
  59. package/dist/integrations/gitlab/agent-tools.js +1 -1
  60. package/dist/integrations/gitlab/routes.d.ts +2 -2
  61. package/dist/integrations/gitlab/routes.d.ts.map +1 -1
  62. package/dist/integrations/gitlab/routes.js.map +1 -1
  63. package/dist/integrations/gitlab/session-subscriptions.d.ts.map +1 -1
  64. package/dist/integrations/gitlab/session-subscriptions.js +8 -7
  65. package/dist/integrations/gitlab/session-subscriptions.js.map +1 -1
  66. package/dist/integrations/incidentio/agent-tools.js +1 -1
  67. package/dist/integrations/jira/agent-tools.js +1 -1
  68. package/dist/integrations/linear/agent-tools.js +1 -1
  69. package/dist/integrations/platform/github/integration.d.ts.map +1 -1
  70. package/dist/integrations/platform/github/integration.js +46 -2
  71. package/dist/integrations/platform/github/integration.js.map +1 -1
  72. package/dist/integrations/platform/jira/agent-tools.js +1 -1
  73. package/dist/integrations/slack/slack.d.ts.map +1 -1
  74. package/dist/integrations/slack/slack.js +15 -16
  75. package/dist/integrations/slack/slack.js.map +1 -1
  76. package/dist/integrations/subscription-session.js +1 -1
  77. package/dist/packages/_internals/workspace/dist/index.js +21 -1
  78. package/dist/packages/_internals/workspace/dist/index.js.map +1 -1
  79. package/dist/routes/contracts.d.ts +386 -2
  80. package/dist/routes/contracts.d.ts.map +1 -1
  81. package/dist/routes/contracts.js +166 -1
  82. package/dist/routes/contracts.js.map +1 -1
  83. package/dist/routes/fs.d.ts +25 -0
  84. package/dist/routes/fs.d.ts.map +1 -1
  85. package/dist/routes/fs.js +142 -46
  86. package/dist/routes/fs.js.map +1 -1
  87. package/dist/routes/projects.d.ts +6 -0
  88. package/dist/routes/projects.d.ts.map +1 -1
  89. package/dist/routes/projects.js +344 -1
  90. package/dist/routes/projects.js.map +1 -1
  91. package/dist/routes/source-control-sessions.d.ts +2 -0
  92. package/dist/routes/source-control-sessions.d.ts.map +1 -1
  93. package/dist/routes/source-control-sessions.js +36 -1
  94. package/dist/routes/source-control-sessions.js.map +1 -1
  95. package/dist/routes/supervisor.js +1 -1
  96. package/dist/routes/surface.d.ts +6 -3
  97. package/dist/routes/surface.d.ts.map +1 -1
  98. package/dist/routes/surface.js +21 -34
  99. package/dist/routes/surface.js.map +1 -1
  100. package/dist/rules/binding-context.d.ts +1 -1
  101. package/dist/rules/binding-context.d.ts.map +1 -1
  102. package/dist/rules/binding-context.js +1 -1
  103. package/dist/rules/binding-context.js.map +1 -1
  104. package/dist/rules/review-source-tool.js +1 -1
  105. package/dist/rules/start-coordinator.d.ts.map +1 -1
  106. package/dist/rules/start-coordinator.js +7 -2
  107. package/dist/rules/start-coordinator.js.map +1 -1
  108. package/dist/rules/tools.js +1 -1
  109. package/dist/sandbox/callback-factory-sandbox.d.ts +15 -0
  110. package/dist/sandbox/callback-factory-sandbox.d.ts.map +1 -0
  111. package/dist/sandbox/callback-factory-sandbox.js +22 -0
  112. package/dist/sandbox/callback-factory-sandbox.js.map +1 -0
  113. package/dist/sandbox/local-factory-sandbox.d.ts +20 -0
  114. package/dist/sandbox/local-factory-sandbox.d.ts.map +1 -0
  115. package/dist/sandbox/local-factory-sandbox.js +27 -0
  116. package/dist/sandbox/local-factory-sandbox.js.map +1 -0
  117. package/dist/sandbox/session-retirement.d.ts.map +1 -1
  118. package/dist/sandbox/session-retirement.js +23 -3
  119. package/dist/sandbox/session-retirement.js.map +1 -1
  120. package/dist/sandbox/session-sandbox.d.ts +82 -40
  121. package/dist/sandbox/session-sandbox.d.ts.map +1 -1
  122. package/dist/sandbox/session-sandbox.js +130 -13
  123. package/dist/sandbox/session-sandbox.js.map +1 -1
  124. package/dist/sandbox/workdir.d.ts +10 -0
  125. package/dist/sandbox/workdir.d.ts.map +1 -1
  126. package/dist/sandbox/workdir.js +10 -1
  127. package/dist/sandbox/workdir.js.map +1 -1
  128. package/dist/session/environment-repositories.d.ts +31 -0
  129. package/dist/session/environment-repositories.d.ts.map +1 -0
  130. package/dist/session/environment-repositories.js +89 -0
  131. package/dist/session/environment-repositories.js.map +1 -0
  132. package/dist/session/environment-state-processor.d.ts +59 -0
  133. package/dist/session/environment-state-processor.d.ts.map +1 -0
  134. package/dist/session/environment-state-processor.js +105 -0
  135. package/dist/session/environment-state-processor.js.map +1 -0
  136. package/dist/session/factory-session.d.ts +26 -35
  137. package/dist/session/factory-session.d.ts.map +1 -1
  138. package/dist/session/factory-session.js +34 -71
  139. package/dist/session/factory-session.js.map +1 -1
  140. package/dist/session/filesystem-capture.d.ts +13 -0
  141. package/dist/session/filesystem-capture.d.ts.map +1 -1
  142. package/dist/session/filesystem-capture.js +73 -14
  143. package/dist/session/filesystem-capture.js.map +1 -1
  144. package/dist/session/source-control-tools.d.ts +1 -1
  145. package/dist/session/source-control-tools.d.ts.map +1 -1
  146. package/dist/session/source-control-tools.js +144 -71
  147. package/dist/session/source-control-tools.js.map +1 -1
  148. package/dist/spa-static.js +2 -2
  149. package/dist/storage/domains/audit/agent-audit.d.ts +6 -1
  150. package/dist/storage/domains/audit/agent-audit.d.ts.map +1 -1
  151. package/dist/storage/domains/audit/agent-audit.js +31 -13
  152. package/dist/storage/domains/audit/agent-audit.js.map +1 -1
  153. package/dist/storage/domains/projects/base.d.ts +115 -2
  154. package/dist/storage/domains/projects/base.d.ts.map +1 -1
  155. package/dist/storage/domains/projects/base.js +219 -12
  156. package/dist/storage/domains/projects/base.js.map +1 -1
  157. package/dist/storage/domains/source-control/base.d.ts +123 -7
  158. package/dist/storage/domains/source-control/base.d.ts.map +1 -1
  159. package/dist/storage/domains/source-control/base.js +406 -33
  160. package/dist/storage/domains/source-control/base.js.map +1 -1
  161. package/dist/storage/domains/source-control/inmemory.d.ts +19 -6
  162. package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
  163. package/dist/storage/domains/source-control/inmemory.js +90 -2
  164. package/dist/storage/domains/source-control/inmemory.js.map +1 -1
  165. package/dist/storage/domains/work-items/base.d.ts.map +1 -1
  166. package/dist/storage/domains/work-items/base.js +12 -5
  167. package/dist/storage/domains/work-items/base.js.map +1 -1
  168. package/dist/supervisor/read-tools.js +1 -1
  169. package/dist/supervisor/write-tools.js +1 -1
  170. package/dist/workspace.d.ts +50 -7
  171. package/dist/workspace.d.ts.map +1 -1
  172. package/dist/workspace.js +344 -24
  173. package/dist/workspace.js.map +1 -1
  174. package/package.json +7 -7
@@ -0,0 +1,149 @@
1
+ import { ENVIRONMENT_BUILD_TRIGGERS, environmentBuildContext, errorReason, redactCredentials, runEnvironmentBuild } from "./build.js";
2
+ import { resolveProjectEnvironment } from "../workspace.js";
3
+ import { createStep, createWorkflow } from "@mastra/core/workflows";
4
+ import { z } from "zod";
5
+ //#region src/environment/build-workflow.ts
6
+ /**
7
+ * The `factory-environment-build` workflow: step one decides and starts a
8
+ * build (`runEnvironmentBuild`), step two polls the provider until the build
9
+ * settles and pins the active template on the project row. Registered by
10
+ * `MastraFactory.prepare()` only when the sandbox has `builds`, and driven by
11
+ * the Build now route, the per-project cron schedule and the GitHub push
12
+ * hand-off. Saving settings never starts it.
13
+ */
14
+ const ENVIRONMENT_BUILD_WORKFLOW_ID = "factory-environment-build";
15
+ const DEFAULT_BUILD_POLL_MS = 15e3;
16
+ const MAX_BUILD_WAIT_MS = 30 * 6e4;
17
+ const environmentBuildInputSchema = z.object({
18
+ projectId: z.string().min(1),
19
+ trigger: z.enum(ENVIRONMENT_BUILD_TRIGGERS)
20
+ });
21
+ const startOutputSchema = z.object({
22
+ projectId: z.string(),
23
+ outcome: z.enum([
24
+ "started",
25
+ "skipped",
26
+ "unavailable",
27
+ "failed"
28
+ ]),
29
+ buildId: z.string().optional(),
30
+ templateId: z.string().optional(),
31
+ reason: z.string().optional(),
32
+ heads: z.record(z.string(), z.string()).optional()
33
+ });
34
+ const environmentBuildOutputSchema = z.object({
35
+ outcome: z.enum([
36
+ "started",
37
+ "skipped",
38
+ "unavailable",
39
+ "failed"
40
+ ]),
41
+ buildId: z.string().optional(),
42
+ templateId: z.string().optional(),
43
+ reason: z.string().optional(),
44
+ /** Where the started build ended: `ready`, `failed`, or `unknown` when the provider lost it or the wait ran out. */
45
+ status: z.enum([
46
+ "ready",
47
+ "failed",
48
+ "unknown"
49
+ ]).optional()
50
+ });
51
+ function createEnvironmentBuildWorkflow(deps, options = {}) {
52
+ const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
53
+ const pollMs = options.pollMs ?? 15e3;
54
+ const maxWaitMs = options.maxWaitMs ?? 18e5;
55
+ const now = deps.now ?? (() => /* @__PURE__ */ new Date());
56
+ const start = createStep({
57
+ id: "start",
58
+ inputSchema: environmentBuildInputSchema,
59
+ outputSchema: startOutputSchema,
60
+ execute: async ({ inputData, runId }) => {
61
+ const outcome = await runEnvironmentBuild(deps, inputData);
62
+ options.onStart?.(runId, outcome);
63
+ return {
64
+ projectId: inputData.projectId,
65
+ ...outcome
66
+ };
67
+ }
68
+ });
69
+ const awaitBuild = createStep({
70
+ id: "await-build",
71
+ inputSchema: startOutputSchema,
72
+ outputSchema: environmentBuildOutputSchema,
73
+ execute: async ({ inputData }) => {
74
+ const { projectId, heads, ...result } = inputData;
75
+ if (result.outcome !== "started" || !result.buildId || !heads) return result;
76
+ const builds = deps.sandbox?.builds;
77
+ const project = await deps.projects.getById({ id: projectId });
78
+ if (!builds || !deps.sourceControl || !project) return {
79
+ ...result,
80
+ status: "unknown"
81
+ };
82
+ const environment = await resolveProjectEnvironment(deps.sourceControl.storage, project);
83
+ const ctx = environmentBuildContext(project, environment, heads, deps.sourceControl.versionControl);
84
+ const startedAt = now().getTime();
85
+ try {
86
+ for (;;) {
87
+ const build = await builds.get(ctx, environment.settings, result.buildId);
88
+ if (build.status === "ready") {
89
+ const templateId = build.templateId ?? result.templateId ?? result.buildId;
90
+ const pinned = await deps.projects.pinActiveTemplate({
91
+ id: project.id,
92
+ buildId: result.buildId,
93
+ templateId,
94
+ heads
95
+ });
96
+ deps.logger?.info(pinned ? "environment build ready" : "environment build ready but superseded", {
97
+ factoryProjectId: project.id,
98
+ templateId
99
+ });
100
+ return {
101
+ ...result,
102
+ templateId,
103
+ status: "ready"
104
+ };
105
+ }
106
+ if (build.status === "failed" || build.status === "unknown") {
107
+ deps.logger?.info("environment build did not complete", {
108
+ factoryProjectId: project.id,
109
+ buildId: result.buildId,
110
+ status: build.status,
111
+ ...build.error ? { reason: redactCredentials(build.error) } : {}
112
+ });
113
+ return {
114
+ ...result,
115
+ status: build.status,
116
+ ...build.error ? { reason: redactCredentials(build.error) } : {}
117
+ };
118
+ }
119
+ if (now().getTime() - startedAt >= maxWaitMs) return {
120
+ ...result,
121
+ status: "unknown",
122
+ reason: "Build wait timed out."
123
+ };
124
+ await sleep(pollMs);
125
+ }
126
+ } catch (error) {
127
+ const reason = errorReason(error);
128
+ deps.logger?.info("environment build status read failed", {
129
+ factoryProjectId: project.id,
130
+ reason
131
+ });
132
+ return {
133
+ ...result,
134
+ status: "unknown",
135
+ reason
136
+ };
137
+ }
138
+ }
139
+ });
140
+ return createWorkflow({
141
+ id: ENVIRONMENT_BUILD_WORKFLOW_ID,
142
+ inputSchema: environmentBuildInputSchema,
143
+ outputSchema: environmentBuildOutputSchema
144
+ }).then(start).then(awaitBuild).commit();
145
+ }
146
+ //#endregion
147
+ export { DEFAULT_BUILD_POLL_MS, ENVIRONMENT_BUILD_WORKFLOW_ID, MAX_BUILD_WAIT_MS, createEnvironmentBuildWorkflow, environmentBuildInputSchema, environmentBuildOutputSchema };
148
+
149
+ //# sourceMappingURL=build-workflow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-workflow.js","names":[],"sources":["../../src/environment/build-workflow.ts"],"sourcesContent":["/**\n * The `factory-environment-build` workflow: step one decides and starts a\n * build (`runEnvironmentBuild`), step two polls the provider until the build\n * settles and pins the active template on the project row. Registered by\n * `MastraFactory.prepare()` only when the sandbox has `builds`, and driven by\n * the Build now route, the per-project cron schedule and the GitHub push\n * hand-off. Saving settings never starts it.\n */\n\nimport { createStep, createWorkflow } from '@mastra/core/workflows';\nimport { z } from 'zod';\nimport { resolveProjectEnvironment } from '../workspace.js';\nimport {\n ENVIRONMENT_BUILD_TRIGGERS,\n environmentBuildContext,\n errorReason,\n redactCredentials,\n runEnvironmentBuild,\n} from './build.js';\nimport type { EnvironmentBuildDeps, EnvironmentBuildOutcome } from './build.js';\n\nexport const ENVIRONMENT_BUILD_WORKFLOW_ID = 'factory-environment-build';\nexport const DEFAULT_BUILD_POLL_MS = 15_000;\nexport const MAX_BUILD_WAIT_MS = 30 * 60_000;\n\nexport const environmentBuildInputSchema = z.object({\n projectId: z.string().min(1),\n trigger: z.enum(ENVIRONMENT_BUILD_TRIGGERS),\n});\n\nconst startOutputSchema = z.object({\n projectId: z.string(),\n outcome: z.enum(['started', 'skipped', 'unavailable', 'failed']),\n buildId: z.string().optional(),\n templateId: z.string().optional(),\n reason: z.string().optional(),\n heads: z.record(z.string(), z.string()).optional(),\n});\n\nexport const environmentBuildOutputSchema = z.object({\n outcome: z.enum(['started', 'skipped', 'unavailable', 'failed']),\n buildId: z.string().optional(),\n templateId: z.string().optional(),\n reason: z.string().optional(),\n /** Where the started build ended: `ready`, `failed`, or `unknown` when the provider lost it or the wait ran out. */\n status: z.enum(['ready', 'failed', 'unknown']).optional(),\n});\n\nexport type EnvironmentBuildWorkflowResult = z.infer<typeof environmentBuildOutputSchema>;\n\nexport interface EnvironmentBuildWorkflowOptions {\n sleep?: (ms: number) => Promise<void>;\n pollMs?: number;\n maxWaitMs?: number;\n /** Called with the first step's answer so a caller can return it without waiting for the poll. */\n onStart?: (runId: string, outcome: EnvironmentBuildOutcome) => void;\n}\n\nexport function createEnvironmentBuildWorkflow(\n deps: EnvironmentBuildDeps,\n options: EnvironmentBuildWorkflowOptions = {},\n) {\n const sleep = options.sleep ?? (ms => new Promise<void>(resolve => setTimeout(resolve, ms)));\n const pollMs = options.pollMs ?? DEFAULT_BUILD_POLL_MS;\n const maxWaitMs = options.maxWaitMs ?? MAX_BUILD_WAIT_MS;\n const now = deps.now ?? (() => new Date());\n\n const start = createStep({\n id: 'start',\n inputSchema: environmentBuildInputSchema,\n outputSchema: startOutputSchema,\n execute: async ({ inputData, runId }) => {\n const outcome = await runEnvironmentBuild(deps, inputData);\n options.onStart?.(runId, outcome);\n return { projectId: inputData.projectId, ...outcome };\n },\n });\n\n const awaitBuild = createStep({\n id: 'await-build',\n inputSchema: startOutputSchema,\n outputSchema: environmentBuildOutputSchema,\n execute: async ({ inputData }): Promise<EnvironmentBuildWorkflowResult> => {\n const { projectId, heads, ...result } = inputData;\n if (result.outcome !== 'started' || !result.buildId || !heads) return result;\n const builds = deps.sandbox?.builds;\n const project = await deps.projects.getById({ id: projectId });\n if (!builds || !deps.sourceControl || !project) return { ...result, status: 'unknown' };\n const environment = await resolveProjectEnvironment(deps.sourceControl.storage, project);\n const ctx = environmentBuildContext(project, environment, heads, deps.sourceControl.versionControl);\n const startedAt = now().getTime();\n try {\n for (;;) {\n const build = await builds.get(ctx, environment.settings, result.buildId);\n if (build.status === 'ready') {\n const templateId = build.templateId ?? result.templateId ?? result.buildId;\n // A newer run may have replaced `last_build_id` while this one\n // polled; the pin is skipped then so the stale image never wins.\n const pinned = await deps.projects.pinActiveTemplate({\n id: project.id,\n buildId: result.buildId,\n templateId,\n heads,\n });\n deps.logger?.info(pinned ? 'environment build ready' : 'environment build ready but superseded', {\n factoryProjectId: project.id,\n templateId,\n });\n return { ...result, templateId, status: 'ready' };\n }\n if (build.status === 'failed' || build.status === 'unknown') {\n deps.logger?.info('environment build did not complete', {\n factoryProjectId: project.id,\n buildId: result.buildId,\n status: build.status,\n ...(build.error ? { reason: redactCredentials(build.error) } : {}),\n });\n return {\n ...result,\n status: build.status,\n ...(build.error ? { reason: redactCredentials(build.error) } : {}),\n };\n }\n if (now().getTime() - startedAt >= maxWaitMs) {\n return { ...result, status: 'unknown', reason: 'Build wait timed out.' };\n }\n await sleep(pollMs);\n }\n } catch (error) {\n const reason = errorReason(error);\n deps.logger?.info('environment build status read failed', { factoryProjectId: project.id, reason });\n return { ...result, status: 'unknown', reason };\n }\n },\n });\n\n return createWorkflow({\n id: ENVIRONMENT_BUILD_WORKFLOW_ID,\n inputSchema: environmentBuildInputSchema,\n outputSchema: environmentBuildOutputSchema,\n })\n .then(start)\n .then(awaitBuild)\n .commit();\n}\n"],"mappings":"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"}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * One proactive environment build: decide whether a trigger should build,
3
+ * hand the sandbox's `builds` capability a context identical to a session's,
4
+ * and record the started build on the project row. Polling the build to its
5
+ * end and pinning the active template is the workflow's second step
6
+ * (`build-workflow.ts`), so a request that only needs the start answer never
7
+ * waits on the provider.
8
+ */
9
+ import type { FactorySandbox, FactorySandboxContext } from '@mastra/core/workspace';
10
+ import type { VersionControl } from '../capabilities/version-control.js';
11
+ import type { FactoryProject, FactoryProjectsStorage } from '../storage/domains/projects/base.js';
12
+ import type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';
13
+ import type { SessionEnvironment } from '../workspace.js';
14
+ import type { EnvironmentHeads } from './heads.js';
15
+ export declare const ENVIRONMENT_BUILD_TRIGGERS: readonly ["manual", "schedule", "push"];
16
+ export type EnvironmentBuildTrigger = (typeof ENVIRONMENT_BUILD_TRIGGERS)[number];
17
+ export interface EnvironmentBuildSourceControl {
18
+ storage: Pick<SourceControlStorageHandle, 'projectRepositories' | 'repositories'>;
19
+ versionControl: Pick<VersionControl, 'getRepositoryAccess'>;
20
+ }
21
+ export interface EnvironmentBuildDeps {
22
+ /** The factory's normalized sandbox; builds are unavailable without its `builds`. */
23
+ sandbox: FactorySandbox | undefined;
24
+ projects: Pick<FactoryProjectsStorage, 'getById' | 'claimBuildAttempt' | 'claimBuildStart' | 'renewBuildStart' | 'releaseBuildStart' | 'pinActiveTemplate'>;
25
+ /** Absent when the factory has no source-control integration to resolve heads with. */
26
+ sourceControl: EnvironmentBuildSourceControl | undefined;
27
+ logger?: {
28
+ info: (msg: string, meta?: Record<string, unknown>) => void;
29
+ };
30
+ now?: () => Date;
31
+ /** How often a run renews its build-start lease; a third of `BUILD_START_LEASE_MS` by default. */
32
+ leaseRenewMs?: number;
33
+ /** Waits between tries at recording a started build. */
34
+ sleep?: (ms: number) => Promise<void>;
35
+ }
36
+ export interface EnvironmentBuildOutcome {
37
+ outcome: 'started' | 'skipped' | 'unavailable' | 'failed';
38
+ buildId?: string;
39
+ templateId?: string;
40
+ /** Why nothing started: `debounced`, `starting` (another run is starting a build), `in_progress`, `unchanged`, `unresolved` (a lapsed run may have started a build the provider cannot list), `no_environment`, `no_builds`, `no_source_control`; why a run failed: `not_recorded`, `superseded`, or an error. */
41
+ reason?: string;
42
+ /** The heads the started build pins; the workflow stores them once the build is ready. */
43
+ heads?: EnvironmentHeads;
44
+ }
45
+ /** The synthetic session id a project's build runs under; never a session. */
46
+ export declare function environmentBuildSessionId(projectId: string): string;
47
+ /**
48
+ * A build or setup error may echo a clone URL or a token; strip URL userinfo
49
+ * and GitHub token shapes before the message is stored, logged or shown.
50
+ */
51
+ export declare function redactCredentials(message: string): string;
52
+ /** An error's message with credentials redacted, safe to log, store or return. */
53
+ export declare function errorReason(error: unknown): string;
54
+ /**
55
+ * The context an environment build (or a read of its status) hands the
56
+ * provider: the same list form a session boots with, under the build's own
57
+ * session id, with heads served from `heads` so the provider never resolves
58
+ * them itself.
59
+ */
60
+ export declare function environmentBuildContext(project: Pick<FactoryProject, 'id' | 'orgId'>, environment: SessionEnvironment, heads: EnvironmentHeads, versionControl: Pick<VersionControl, 'getRepositoryAccess'>): FactorySandboxContext;
61
+ /**
62
+ * Fingerprint of everything a build bakes into the template besides the
63
+ * repository heads: provider settings, the repositories and their order,
64
+ * their setup commands, the workspace setup command and the working
65
+ * directory. A schedule or push skips a ready build only while this matches.
66
+ */
67
+ export declare function environmentBuildInputs(environment: SessionEnvironment): string;
68
+ /**
69
+ * The current head of every environment repository's template branch, keyed
70
+ * by slug. The template clones the repository default branch (a link's own
71
+ * branch is the session's base, checked out after boot), so that is the
72
+ * branch a build pins; sessions let the provider resolve the same head.
73
+ */
74
+ export declare function resolveEnvironmentHeads(sourceControl: EnvironmentBuildSourceControl, project: Pick<FactoryProject, 'orgId'>, environment: SessionEnvironment): Promise<EnvironmentHeads>;
75
+ /**
76
+ * How long a build-start lease lasts without renewal. The run holding it
77
+ * renews it every third of that while it works, so a lease lapses only when
78
+ * its run died or stalled.
79
+ */
80
+ export declare const BUILD_START_LEASE_MS: number;
81
+ /**
82
+ * Start a build for `projectId` when `trigger` calls for one. Every trigger
83
+ * takes the project's build-start lease before reading build state and renews
84
+ * it until the started build is recorded, so overlapping manual, scheduled and
85
+ * push runs never both start a build, however long the provider takes; a run
86
+ * that finds the lease held skips as `starting`. Every trigger skips while the
87
+ * last build is still running; `schedule` and `push` also skip when the last
88
+ * build is ready and neither a head nor any other template input changed;
89
+ * `manual` otherwise always builds. A `push` that would build then takes the
90
+ * leading-edge debounce claim, so only a push that starts a build opens the
91
+ * window. Saving settings never builds.
92
+ *
93
+ * Taking over a lapsed lease leaves the start uncertain: the lapsed run may
94
+ * have started a build it never recorded. The mark persists until resolved.
95
+ * While it stands, a provider that lists builds is asked for a running one
96
+ * (and every trigger skips as `in_progress` on it); with a provider that
97
+ * cannot list, every trigger skips as `unresolved`.
98
+ *
99
+ * The result is written as the lease is released, and only while this run
100
+ * still holds it. A failed `builds.start` clears the last build id, so a later
101
+ * `schedule` or `push` cannot mistake the previous ready build for the current
102
+ * template; any other error keeps a running build's id. A started build whose
103
+ * recording keeps failing yields `failed` (`not_recorded`) with the lease left
104
+ * to lapse rather than released, and a run that lost its lease while the
105
+ * provider started yields `failed` (`superseded`); neither is polled or pinned.
106
+ */
107
+ export declare function runEnvironmentBuild(deps: EnvironmentBuildDeps, input: {
108
+ projectId: string;
109
+ trigger: EnvironmentBuildTrigger;
110
+ }): Promise<EnvironmentBuildOutcome>;
111
+ //# sourceMappingURL=build.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/environment/build.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,344 @@
1
+ import { headsChanged, recordedHeadResolver, resolveCurrentHeads } from "./heads.js";
2
+ import { environmentSandboxContext, resolveProjectEnvironment } from "../workspace.js";
3
+ import { createHash, randomUUID } from "crypto";
4
+ //#region src/environment/build.ts
5
+ /**
6
+ * One proactive environment build: decide whether a trigger should build,
7
+ * hand the sandbox's `builds` capability a context identical to a session's,
8
+ * and record the started build on the project row. Polling the build to its
9
+ * end and pinning the active template is the workflow's second step
10
+ * (`build-workflow.ts`), so a request that only needs the start answer never
11
+ * waits on the provider.
12
+ */
13
+ const ENVIRONMENT_BUILD_TRIGGERS = [
14
+ "manual",
15
+ "schedule",
16
+ "push"
17
+ ];
18
+ /** The synthetic session id a project's build runs under; never a session. */
19
+ function environmentBuildSessionId(projectId) {
20
+ return `environment-build:${projectId}`;
21
+ }
22
+ /**
23
+ * A build or setup error may echo a clone URL or a token; strip URL userinfo
24
+ * and GitHub token shapes before the message is stored, logged or shown.
25
+ */
26
+ function redactCredentials(message) {
27
+ return message.replace(/\/\/[^/\s@]+@/g, "//***@").replace(/\b(?:gh[pousr]|github_pat)_[A-Za-z0-9_]+/g, "***");
28
+ }
29
+ /** An error's message with credentials redacted, safe to log, store or return. */
30
+ function errorReason(error) {
31
+ return redactCredentials(error instanceof Error ? error.message : String(error));
32
+ }
33
+ /**
34
+ * The context an environment build (or a read of its status) hands the
35
+ * provider: the same list form a session boots with, under the build's own
36
+ * session id, with heads served from `heads` so the provider never resolves
37
+ * them itself.
38
+ */
39
+ function environmentBuildContext(project, environment, heads, versionControl) {
40
+ return {
41
+ sessionId: environmentBuildSessionId(project.id),
42
+ ...environmentSandboxContext(environment, {
43
+ orgId: project.orgId,
44
+ getRepositoryAccess: (args) => versionControl.getRepositoryAccess(args),
45
+ resolveHead: recordedHeadResolver(heads)
46
+ })
47
+ };
48
+ }
49
+ /**
50
+ * Fingerprint of everything a build bakes into the template besides the
51
+ * repository heads: provider settings, the repositories and their order,
52
+ * their setup commands, the workspace setup command and the working
53
+ * directory. A schedule or push skips a ready build only while this matches.
54
+ */
55
+ function environmentBuildInputs(environment) {
56
+ const inputs = {
57
+ settings: environment.settings,
58
+ workspaceSetupCommand: environment.workspaceSetupCommand ?? null,
59
+ workingDirectory: environment.workingDirectory ?? null,
60
+ repos: environment.repos.map((repo) => ({
61
+ repositoryId: repo.repositoryId,
62
+ templateBranch: repo.templateBranch,
63
+ setupCommand: repo.setupCommand ?? null
64
+ }))
65
+ };
66
+ return createHash("sha256").update(JSON.stringify(inputs)).digest("hex");
67
+ }
68
+ /**
69
+ * The current head of every environment repository's template branch, keyed
70
+ * by slug. The template clones the repository default branch (a link's own
71
+ * branch is the session's base, checked out after boot), so that is the
72
+ * branch a build pins; sessions let the provider resolve the same head.
73
+ */
74
+ async function resolveEnvironmentHeads(sourceControl, project, environment) {
75
+ return resolveCurrentHeads(sourceControl, {
76
+ orgId: project.orgId,
77
+ repositories: environment.repos.map((repo) => ({
78
+ id: repo.repositoryId,
79
+ slug: repo.slug,
80
+ branch: repo.templateBranch
81
+ }))
82
+ });
83
+ }
84
+ /**
85
+ * How long a build-start lease lasts without renewal. The run holding it
86
+ * renews it every third of that while it works, so a lease lapses only when
87
+ * its run died or stalled.
88
+ */
89
+ const BUILD_START_LEASE_MS = 10 * 6e4;
90
+ /** Tries at recording a started build before the run leaves its lease to lapse. */
91
+ const RECORD_ATTEMPTS = 5;
92
+ /**
93
+ * Start a build for `projectId` when `trigger` calls for one. Every trigger
94
+ * takes the project's build-start lease before reading build state and renews
95
+ * it until the started build is recorded, so overlapping manual, scheduled and
96
+ * push runs never both start a build, however long the provider takes; a run
97
+ * that finds the lease held skips as `starting`. Every trigger skips while the
98
+ * last build is still running; `schedule` and `push` also skip when the last
99
+ * build is ready and neither a head nor any other template input changed;
100
+ * `manual` otherwise always builds. A `push` that would build then takes the
101
+ * leading-edge debounce claim, so only a push that starts a build opens the
102
+ * window. Saving settings never builds.
103
+ *
104
+ * Taking over a lapsed lease leaves the start uncertain: the lapsed run may
105
+ * have started a build it never recorded. The mark persists until resolved.
106
+ * While it stands, a provider that lists builds is asked for a running one
107
+ * (and every trigger skips as `in_progress` on it); with a provider that
108
+ * cannot list, every trigger skips as `unresolved`.
109
+ *
110
+ * The result is written as the lease is released, and only while this run
111
+ * still holds it. A failed `builds.start` clears the last build id, so a later
112
+ * `schedule` or `push` cannot mistake the previous ready build for the current
113
+ * template; any other error keeps a running build's id. A started build whose
114
+ * recording keeps failing yields `failed` (`not_recorded`) with the lease left
115
+ * to lapse rather than released, and a run that lost its lease while the
116
+ * provider started yields `failed` (`superseded`); neither is polled or pinned.
117
+ */
118
+ async function runEnvironmentBuild(deps, input) {
119
+ const builds = deps.sandbox?.builds;
120
+ if (!builds) return {
121
+ outcome: "unavailable",
122
+ reason: "no_builds"
123
+ };
124
+ if (!deps.sourceControl) return {
125
+ outcome: "unavailable",
126
+ reason: "no_source_control"
127
+ };
128
+ const sourceControl = deps.sourceControl;
129
+ const initial = await deps.projects.getById({ id: input.projectId });
130
+ if (!initial) return {
131
+ outcome: "skipped",
132
+ reason: "no_environment"
133
+ };
134
+ const now = deps.now ?? (() => /* @__PURE__ */ new Date());
135
+ const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
136
+ const pushDebounced = (project, at) => project.lastPushBuildAt !== null && at.getTime() - project.lastPushBuildAt.getTime() < project.buildPushDebounceMinutes * 6e4;
137
+ if (input.trigger === "push" && pushDebounced(initial, now())) return {
138
+ outcome: "skipped",
139
+ reason: "debounced"
140
+ };
141
+ const token = randomUUID();
142
+ const lease = {
143
+ id: initial.id,
144
+ token,
145
+ leaseMs: BUILD_START_LEASE_MS
146
+ };
147
+ const claim = await deps.projects.claimBuildStart({
148
+ ...lease,
149
+ now: now()
150
+ });
151
+ if (!claim.claimed) return {
152
+ outcome: "skipped",
153
+ reason: "starting"
154
+ };
155
+ let held = true;
156
+ const renewal = setInterval(() => {
157
+ deps.projects.renewBuildStart({
158
+ ...lease,
159
+ now: now()
160
+ }).then((renewed) => {
161
+ if (!renewed) held = false;
162
+ }, () => {});
163
+ }, deps.leaseRenewMs ?? 6e5 / 3);
164
+ renewal.unref?.();
165
+ const attempted = { lastBuildAttemptedAt: now() };
166
+ let resolved = false;
167
+ const release = (changes = {}) => deps.projects.releaseBuildStart({
168
+ id: initial.id,
169
+ token,
170
+ changes,
171
+ resolved,
172
+ now: now()
173
+ });
174
+ const failed = async (error, changes) => {
175
+ const reason = errorReason(error);
176
+ await release(changes);
177
+ deps.logger?.info("environment build failed", {
178
+ factoryProjectId: initial.id,
179
+ trigger: input.trigger,
180
+ reason
181
+ });
182
+ return {
183
+ outcome: "failed",
184
+ reason
185
+ };
186
+ };
187
+ try {
188
+ let project;
189
+ let environment;
190
+ let heads;
191
+ let ctx;
192
+ let buildInputs;
193
+ try {
194
+ project = await deps.projects.getById({ id: initial.id }) ?? initial;
195
+ environment = await resolveProjectEnvironment(sourceControl.storage, project);
196
+ if (environment.repos.length === 0) {
197
+ await release();
198
+ return {
199
+ outcome: "skipped",
200
+ reason: "no_environment"
201
+ };
202
+ }
203
+ heads = await resolveEnvironmentHeads(sourceControl, project, environment);
204
+ ctx = environmentBuildContext(project, environment, heads, sourceControl.versionControl);
205
+ buildInputs = environmentBuildInputs(environment);
206
+ if (claim.uncertain) if (builds.list) {
207
+ const running = (await builds.list(ctx, environment.settings)).find((build) => build.status === "pending" || build.status === "building");
208
+ if (running) {
209
+ await release();
210
+ return {
211
+ outcome: "skipped",
212
+ reason: "in_progress",
213
+ buildId: running.buildId
214
+ };
215
+ }
216
+ resolved = true;
217
+ } else {
218
+ await release();
219
+ return {
220
+ outcome: "skipped",
221
+ reason: "unresolved"
222
+ };
223
+ }
224
+ if (project.lastBuildId) {
225
+ const last = await builds.get(ctx, environment.settings, project.lastBuildId);
226
+ if (last.status === "pending" || last.status === "building") {
227
+ await release();
228
+ return {
229
+ outcome: "skipped",
230
+ reason: "in_progress",
231
+ buildId: project.lastBuildId
232
+ };
233
+ }
234
+ let pinnedHeads = project.activeTemplateHeads;
235
+ const lastTemplateId = last.templateId ?? project.lastBuildId;
236
+ if (last.status === "ready" && project.lastBuildHeads && project.activeTemplateId !== lastTemplateId) {
237
+ if (await deps.projects.pinActiveTemplate({
238
+ id: project.id,
239
+ buildId: project.lastBuildId,
240
+ templateId: lastTemplateId,
241
+ heads: project.lastBuildHeads
242
+ })) pinnedHeads = project.lastBuildHeads;
243
+ }
244
+ if ((input.trigger === "schedule" || input.trigger === "push") && last.status === "ready" && !headsChanged(pinnedHeads, heads) && project.lastBuildInputs === buildInputs) {
245
+ await release();
246
+ return {
247
+ outcome: "skipped",
248
+ reason: "unchanged"
249
+ };
250
+ }
251
+ }
252
+ } catch (error) {
253
+ return await failed(error, attempted);
254
+ }
255
+ if (!held) return {
256
+ outcome: "skipped",
257
+ reason: "starting"
258
+ };
259
+ if (input.trigger === "push") {
260
+ let claimed;
261
+ try {
262
+ claimed = await deps.projects.claimBuildAttempt({
263
+ id: project.id,
264
+ debounceMinutes: project.buildPushDebounceMinutes,
265
+ now: now()
266
+ });
267
+ } catch (error) {
268
+ return await failed(error, {});
269
+ }
270
+ if (!claimed) {
271
+ await release();
272
+ return {
273
+ outcome: "skipped",
274
+ reason: "debounced"
275
+ };
276
+ }
277
+ }
278
+ let started;
279
+ try {
280
+ started = await builds.start(ctx, environment.settings);
281
+ } catch (error) {
282
+ return await failed(error, {
283
+ lastBuildId: null,
284
+ lastBuildHeads: null,
285
+ lastBuildInputs: null,
286
+ ...attempted
287
+ });
288
+ }
289
+ const log = {
290
+ factoryProjectId: project.id,
291
+ trigger: input.trigger,
292
+ buildId: started.buildId
293
+ };
294
+ const changes = {
295
+ lastBuildId: started.buildId,
296
+ lastBuildHeads: heads,
297
+ lastBuildInputs: buildInputs,
298
+ ...attempted
299
+ };
300
+ let recorded;
301
+ for (let attempt = 1; recorded === void 0; attempt++) try {
302
+ recorded = await release(changes);
303
+ } catch (error) {
304
+ if (attempt >= RECORD_ATTEMPTS) {
305
+ deps.logger?.info("environment build started but not recorded", {
306
+ ...log,
307
+ reason: errorReason(error)
308
+ });
309
+ return {
310
+ outcome: "failed",
311
+ reason: "not_recorded",
312
+ buildId: started.buildId
313
+ };
314
+ }
315
+ await sleep(1e3 * 2 ** (attempt - 1));
316
+ }
317
+ if (!recorded) {
318
+ deps.logger?.info("environment build superseded", log);
319
+ return {
320
+ outcome: "failed",
321
+ reason: "superseded",
322
+ buildId: started.buildId
323
+ };
324
+ }
325
+ try {
326
+ deps.logger?.info("environment build started", {
327
+ ...log,
328
+ templateId: started.templateId
329
+ });
330
+ } catch {}
331
+ return {
332
+ outcome: "started",
333
+ buildId: started.buildId,
334
+ ...started.templateId ? { templateId: started.templateId } : {},
335
+ heads
336
+ };
337
+ } finally {
338
+ clearInterval(renewal);
339
+ }
340
+ }
341
+ //#endregion
342
+ export { BUILD_START_LEASE_MS, ENVIRONMENT_BUILD_TRIGGERS, environmentBuildContext, environmentBuildInputs, environmentBuildSessionId, errorReason, redactCredentials, resolveEnvironmentHeads, runEnvironmentBuild };
343
+
344
+ //# sourceMappingURL=build.js.map