@mastra/factory 0.24.0-alpha.3 → 0.24.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.
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +9 -3
- package/dist/factory.js.map +1 -1
- package/dist/integrations/github/default-rules.d.ts.map +1 -1
- package/dist/integrations/github/default-rules.js +1 -16
- package/dist/integrations/github/default-rules.js.map +1 -1
- package/dist/integrations/github/sandbox-release.d.ts +15 -3
- package/dist/integrations/github/sandbox-release.d.ts.map +1 -1
- package/dist/integrations/github/sandbox-release.js +20 -3
- package/dist/integrations/github/sandbox-release.js.map +1 -1
- package/dist/integrations/github/webhook.d.ts +4 -2
- package/dist/integrations/github/webhook.d.ts.map +1 -1
- package/dist/integrations/github/webhook.js +59 -36
- package/dist/integrations/github/webhook.js.map +1 -1
- package/dist/integrations/gitlab/api.d.ts +1 -1
- package/dist/integrations/gitlab/api.d.ts.map +1 -1
- package/dist/integrations/gitlab/api.js.map +1 -1
- package/dist/integrations/incidentio/intake.d.ts +1 -1
- package/dist/integrations/incidentio/intake.d.ts.map +1 -1
- package/dist/integrations/incidentio/intake.js.map +1 -1
- package/dist/integrations/platform/connect/routes.d.ts +1 -1
- package/dist/integrations/platform/connect/routes.d.ts.map +1 -1
- package/dist/integrations/platform/connect/routes.js.map +1 -1
- package/dist/integrations/platform/incidentio/integration.d.ts +1 -1
- package/dist/integrations/platform/incidentio/integration.d.ts.map +1 -1
- package/dist/integrations/platform/incidentio/integration.js.map +1 -1
- package/dist/integrations/platform/jira/integration.d.ts +1 -1
- package/dist/integrations/platform/jira/integration.d.ts.map +1 -1
- package/dist/integrations/platform/jira/integration.js.map +1 -1
- package/dist/integrations/platform/linear/event-worker.d.ts +1 -1
- package/dist/integrations/platform/linear/event-worker.d.ts.map +1 -1
- package/dist/integrations/platform/linear/event-worker.js.map +1 -1
- package/dist/review-verdict.d.ts +11 -0
- package/dist/review-verdict.d.ts.map +1 -1
- package/dist/review-verdict.js +16 -1
- package/dist/review-verdict.js.map +1 -1
- package/dist/routes/source-control-sessions.d.ts +7 -0
- package/dist/routes/source-control-sessions.d.ts.map +1 -1
- package/dist/routes/source-control-sessions.js +121 -3
- package/dist/routes/source-control-sessions.js.map +1 -1
- package/dist/routes/surface.d.ts +2 -2
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +2 -0
- package/dist/routes/surface.js.map +1 -1
- package/dist/rules/dispatcher.d.ts.map +1 -1
- package/dist/rules/dispatcher.js +10 -1
- package/dist/rules/dispatcher.js.map +1 -1
- package/dist/sandbox/session-retirement.d.ts +27 -3
- package/dist/sandbox/session-retirement.d.ts.map +1 -1
- package/dist/sandbox/session-retirement.js +158 -20
- package/dist/sandbox/session-retirement.js.map +1 -1
- package/dist/session/credential-policy.d.ts +1 -1
- package/dist/session/credential-policy.d.ts.map +1 -1
- package/dist/session/credential-policy.js.map +1 -1
- package/dist/session/factory-session.d.ts +1 -1
- package/dist/session/factory-session.d.ts.map +1 -1
- package/dist/session/factory-session.js.map +1 -1
- package/dist/session/work-item-repository.d.ts +1 -1
- package/dist/session/work-item-repository.d.ts.map +1 -1
- package/dist/session/work-item-repository.js.map +1 -1
- package/dist/storage/domains/filesystem/base.d.ts +4 -0
- package/dist/storage/domains/filesystem/base.d.ts.map +1 -1
- package/dist/storage/domains/filesystem/base.js +5 -0
- package/dist/storage/domains/filesystem/base.js.map +1 -1
- package/dist/storage/domains/work-items/base.d.ts +7 -5
- package/dist/storage/domains/work-items/base.d.ts.map +1 -1
- package/dist/storage/domains/work-items/base.js +5 -5
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/dist/timing.d.ts +23 -0
- package/dist/timing.d.ts.map +1 -1
- package/dist/timing.js +85 -6
- package/dist/timing.js.map +1 -1
- package/dist/tools/reports.d.ts +11 -0
- package/dist/tools/reports.d.ts.map +1 -1
- package/dist/tools/reports.js +20 -1
- package/dist/tools/reports.js.map +1 -1
- package/dist/tools/run-watch.d.ts +1 -1
- package/dist/tools/run-watch.d.ts.map +1 -1
- package/dist/tools/run-watch.js.map +1 -1
- package/dist/tools/session-tools.d.ts +1 -1
- package/dist/tools/session-tools.d.ts.map +1 -1
- package/dist/tools/session-tools.js.map +1 -1
- package/dist/workspace.d.ts +1 -1
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +10 -4
- package/dist/workspace.js.map +1 -1
- package/package.json +12 -14
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
import { DEFAULT_COMMAND_TIMEOUT_MS, runTeardownCommand } from "../integrations/github/sandbox.js";
|
|
2
2
|
import { requireExec } from "./materialization.js";
|
|
3
|
-
import { peekSessionSandbox } from "./session-sandbox.js";
|
|
3
|
+
import { evictSessionSandbox, peekSessionSandbox } from "./session-sandbox.js";
|
|
4
4
|
import { peekSessionEnvironmentTeardown } from "../session/environment-state-processor.js";
|
|
5
|
-
import { releaseSessionSandbox } from "../integrations/github/sandbox-release.js";
|
|
5
|
+
import { destroyDetachedSessionSandbox, releaseSessionSandbox } from "../integrations/github/sandbox-release.js";
|
|
6
6
|
//#region src/sandbox/session-retirement.ts
|
|
7
|
+
const DEFAULT_DESTROY_TIMEOUT_MS = 3e4;
|
|
8
|
+
/**
|
|
9
|
+
* A provider error meaning the VM is already gone: 410, or a 404 the proxy
|
|
10
|
+
* marks `not_found` (or leaves uncoded). A routing 404 with another code is
|
|
11
|
+
* not proof the VM is gone.
|
|
12
|
+
*/
|
|
13
|
+
function isSandboxGoneError(error) {
|
|
14
|
+
if (typeof error !== "object" || error === null) return false;
|
|
15
|
+
const { status, code } = error;
|
|
16
|
+
if (status === 410) return true;
|
|
17
|
+
return status === 404 && (code === void 0 || code === null || code === "not_found");
|
|
18
|
+
}
|
|
7
19
|
function boundedError(error) {
|
|
8
20
|
const detail = error instanceof Error ? error.message : String(error);
|
|
9
21
|
if (detail.length <= 2e3) return detail;
|
|
@@ -16,25 +28,44 @@ function boundedError(error) {
|
|
|
16
28
|
*
|
|
17
29
|
* Sandbox identity is the session id: retirement stops (or, for deleted
|
|
18
30
|
* sessions, destroys) the sandbox this process holds in the session memo.
|
|
19
|
-
* Sessions opened on another replica have no memo entry
|
|
20
|
-
* left to the provider's idle lifecycle
|
|
21
|
-
*
|
|
31
|
+
* Sessions opened on another replica (or before a restart) have no memo entry
|
|
32
|
+
* here: stopping them is left to the provider's idle lifecycle, but deleting a
|
|
33
|
+
* materialized session destroys its VM by identity through the configured
|
|
34
|
+
* sandbox. Providers such as E2B keep paused VMs forever, so the row's
|
|
35
|
+
* deletion must never orphan one.
|
|
22
36
|
*/
|
|
23
37
|
var SessionRetirementCoordinator = class {
|
|
24
38
|
#invalidateSession;
|
|
39
|
+
#sandbox;
|
|
25
40
|
#warn;
|
|
41
|
+
#destroyTimeoutMs;
|
|
26
42
|
#locks = /* @__PURE__ */ new Map();
|
|
27
43
|
constructor(options = {}) {
|
|
28
44
|
this.#invalidateSession = options.invalidateSession ?? (() => {});
|
|
45
|
+
this.#sandbox = options.sandbox;
|
|
29
46
|
this.#warn = options.warn ?? ((message, details) => console.warn(`[Mastra Factory] ${message}`, details));
|
|
47
|
+
this.#destroyTimeoutMs = options.destroyTimeoutMs ?? DEFAULT_DESTROY_TIMEOUT_MS;
|
|
30
48
|
}
|
|
31
49
|
async retireSession(input) {
|
|
32
|
-
|
|
33
|
-
|
|
50
|
+
await this.#withSessionLock(input.sessionId, () => this.#retireSession(input));
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Destroy a session's sandbox so its next message boots a fresh VM from the
|
|
54
|
+
* template, keeping the session row, its conversation, and work-item refs.
|
|
55
|
+
* Skips teardown commands, and works without a memo entry in this process
|
|
56
|
+
* by destroying a detached instance built from the persisted `sandboxId`.
|
|
57
|
+
* A destroy failure keeps the persisted id so a retry can reach the VM.
|
|
58
|
+
*/
|
|
59
|
+
async recreateSessionSandbox(input) {
|
|
60
|
+
return this.#withSessionLock(input.sessionId, () => this.#recreateSessionSandbox(input));
|
|
61
|
+
}
|
|
62
|
+
async #withSessionLock(sessionId, run) {
|
|
63
|
+
const current = (this.#locks.get(sessionId) ?? Promise.resolve()).catch(() => {}).then(run);
|
|
64
|
+
this.#locks.set(sessionId, current);
|
|
34
65
|
try {
|
|
35
|
-
await current;
|
|
66
|
+
return await current;
|
|
36
67
|
} finally {
|
|
37
|
-
if (this.#locks.get(
|
|
68
|
+
if (this.#locks.get(sessionId) === current) this.#locks.delete(sessionId);
|
|
38
69
|
}
|
|
39
70
|
}
|
|
40
71
|
async retireWorkItemSessions(options) {
|
|
@@ -67,16 +98,7 @@ var SessionRetirementCoordinator = class {
|
|
|
67
98
|
try {
|
|
68
99
|
await this.#teardownSessionSandbox(input, session);
|
|
69
100
|
} finally {
|
|
70
|
-
|
|
71
|
-
await this.#invalidateSession(session.sessionId);
|
|
72
|
-
} catch (error) {
|
|
73
|
-
this.#warn("Factory session workspace cache invalidation failed", {
|
|
74
|
-
orgId: session.orgId,
|
|
75
|
-
sessionId: session.sessionId,
|
|
76
|
-
projectRepositoryId: session.projectRepositoryId,
|
|
77
|
-
error: boundedError(error)
|
|
78
|
-
});
|
|
79
|
-
}
|
|
101
|
+
await this.#invalidate(session);
|
|
80
102
|
if (input.deleteSession) {
|
|
81
103
|
await input.workItems?.clearSessionReferences({
|
|
82
104
|
orgId: input.orgId,
|
|
@@ -88,7 +110,10 @@ var SessionRetirementCoordinator = class {
|
|
|
88
110
|
}
|
|
89
111
|
async #teardownSessionSandbox(input, session) {
|
|
90
112
|
const entry = peekSessionSandbox(session.id);
|
|
91
|
-
if (!entry)
|
|
113
|
+
if (!entry) {
|
|
114
|
+
await this.#destroyDetachedSandbox(input, session);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
92
117
|
const environmentTeardown = peekSessionEnvironmentTeardown(session.sessionId);
|
|
93
118
|
if (environmentTeardown.length > 0) {
|
|
94
119
|
for (const repo of environmentTeardown) try {
|
|
@@ -131,6 +156,119 @@ var SessionRetirementCoordinator = class {
|
|
|
131
156
|
}
|
|
132
157
|
await this.#releaseSandbox(input, session);
|
|
133
158
|
}
|
|
159
|
+
/**
|
|
160
|
+
* Destroy a deleted session's VM that this process never attached to. Only
|
|
161
|
+
* for sessions that materialized (a persisted `sandboxId`): nothing is
|
|
162
|
+
* started, no teardown command runs — the provider kills by identity.
|
|
163
|
+
*/
|
|
164
|
+
async #destroyDetachedSandbox(input, session) {
|
|
165
|
+
if (!input.deleteSession || !session.sandboxId || !this.#sandbox) return;
|
|
166
|
+
try {
|
|
167
|
+
await destroyDetachedSessionSandbox({
|
|
168
|
+
sandbox: this.#sandbox,
|
|
169
|
+
session
|
|
170
|
+
});
|
|
171
|
+
} catch (error) {
|
|
172
|
+
this.#warn("Factory detached session sandbox destroy failed", {
|
|
173
|
+
orgId: session.orgId,
|
|
174
|
+
sessionId: session.sessionId,
|
|
175
|
+
projectRepositoryId: session.projectRepositoryId,
|
|
176
|
+
error: boundedError(error)
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/** Bump the session's workspace generation; a failing unregister is logged, never fatal. */
|
|
181
|
+
async #invalidate(session) {
|
|
182
|
+
try {
|
|
183
|
+
await this.#invalidateSession(session.sessionId);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
this.#warn("Factory session workspace cache invalidation failed", {
|
|
186
|
+
orgId: session.orgId,
|
|
187
|
+
sessionId: session.sessionId,
|
|
188
|
+
projectRepositoryId: session.projectRepositoryId,
|
|
189
|
+
error: boundedError(error)
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
async #recreateSessionSandbox(input) {
|
|
194
|
+
const session = await input.sourceControl.sessions.getBySessionId(input.sessionId);
|
|
195
|
+
if (!session || session.orgId !== input.orgId) return "not-found";
|
|
196
|
+
await this.#invalidate(session);
|
|
197
|
+
const entry = peekSessionSandbox(session.id);
|
|
198
|
+
evictSessionSandbox(session.id);
|
|
199
|
+
await this.#destroyForRecreate(session, entry?.sandbox);
|
|
200
|
+
await input.sourceControl.sessions.setSandbox({
|
|
201
|
+
id: session.id,
|
|
202
|
+
sandboxId: null,
|
|
203
|
+
sandboxWorkdir: ""
|
|
204
|
+
});
|
|
205
|
+
evictSessionSandbox(session.id);
|
|
206
|
+
await this.#invalidate(session);
|
|
207
|
+
return "recreated";
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Destroy the session's VM. `_destroy()` silently no-ops on a never-started
|
|
211
|
+
* (`pending`) instance, so pending and detached instances call the
|
|
212
|
+
* provider's `destroy()` directly; a started instance goes through the
|
|
213
|
+
* lifecycle wrapper, bounded so a hung start can't hold the session lock.
|
|
214
|
+
*/
|
|
215
|
+
async #destroyForRecreate(session, memo) {
|
|
216
|
+
const lifecycle = memo;
|
|
217
|
+
if (memo && memo.status !== "pending" && lifecycle?._destroy) {
|
|
218
|
+
const destroying = lifecycle._destroy.call(memo);
|
|
219
|
+
if (await this.#settledWithin(destroying)) {
|
|
220
|
+
memo.status = "destroyed";
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
destroying.catch((error) => this.#warn("Factory sandbox destroy failed after recreate stopped waiting", {
|
|
224
|
+
orgId: session.orgId,
|
|
225
|
+
sessionId: session.sessionId,
|
|
226
|
+
error: boundedError(error)
|
|
227
|
+
}));
|
|
228
|
+
if (!this.#sandbox) throw new Error("Timed out destroying the session sandbox, and no sandbox provider is configured to retry by identity.");
|
|
229
|
+
await this.#destroyDetachedForRecreate(this.#sandbox, session);
|
|
230
|
+
memo.status = "destroyed";
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (memo) {
|
|
234
|
+
await this.#destroyDirectly(memo);
|
|
235
|
+
memo.status = "destroyed";
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (this.#sandbox) await this.#destroyDetachedForRecreate(this.#sandbox, session);
|
|
239
|
+
}
|
|
240
|
+
async #destroyDetachedForRecreate(sandbox, session) {
|
|
241
|
+
try {
|
|
242
|
+
await destroyDetachedSessionSandbox({
|
|
243
|
+
sandbox,
|
|
244
|
+
session
|
|
245
|
+
});
|
|
246
|
+
} catch (error) {
|
|
247
|
+
if (!isSandboxGoneError(error)) throw error;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/** True when the destroy finished in time; false on timeout. Gone-VM errors count as finished. */
|
|
251
|
+
async #settledWithin(destroying) {
|
|
252
|
+
let timer;
|
|
253
|
+
const timedOut = new Promise((resolve) => {
|
|
254
|
+
timer = setTimeout(() => resolve(false), this.#destroyTimeoutMs);
|
|
255
|
+
});
|
|
256
|
+
try {
|
|
257
|
+
return await Promise.race([destroying.then(() => true), timedOut]);
|
|
258
|
+
} catch (error) {
|
|
259
|
+
if (isSandboxGoneError(error)) return true;
|
|
260
|
+
throw error;
|
|
261
|
+
} finally {
|
|
262
|
+
clearTimeout(timer);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
async #destroyDirectly(sandbox) {
|
|
266
|
+
try {
|
|
267
|
+
await sandbox.destroy?.();
|
|
268
|
+
} catch (error) {
|
|
269
|
+
if (!isSandboxGoneError(error)) throw error;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
134
272
|
async #releaseSandbox(input, session) {
|
|
135
273
|
try {
|
|
136
274
|
await releaseSessionSandbox({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-retirement.js","names":["#invalidateSession","#warn","#locks","#retireSession","#teardownSessionSandbox","#releaseSandbox"],"sources":["../../src/sandbox/session-retirement.ts"],"sourcesContent":["import { releaseSessionSandbox } from '../integrations/github/sandbox-release.js';\nimport { DEFAULT_COMMAND_TIMEOUT_MS, runTeardownCommand } from '../integrations/github/sandbox.js';\nimport { peekSessionEnvironmentTeardown } from '../session/environment-state-processor.js';\nimport type {\n ProjectRepository,\n SourceControlSession,\n SourceControlStorageHandle,\n} from '../storage/domains/source-control/base.js';\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport { requireExec } from './materialization.js';\nimport { peekSessionSandbox } from './session-sandbox.js';\n\ntype WarningLogger = (message: string, details: Record<string, unknown>) => void;\n\nexport interface SessionRetirementCoordinatorOptions {\n invalidateSession?: (sessionId: string) => Promise<void> | void;\n warn?: WarningLogger;\n}\n\nexport interface RetireSessionInput {\n sourceControl: SourceControlStorageHandle;\n /** When provided, deleting the session row also strips its work-item refs. */\n workItems?: Pick<WorkItemsStorage, 'clearSessionReferences'>;\n orgId: string;\n sessionId: string;\n deleteSession: boolean;\n}\n\nfunction boundedError(error: unknown): string {\n const detail = error instanceof Error ? error.message : String(error);\n if (detail.length <= 2000) return detail;\n return `${detail.slice(0, 200)}...${detail.slice(-1797)}`;\n}\n\n/**\n * Owns terminal and destructive session cleanup. Each session is serialized so\n * duplicate role bindings and competing deletion/transition requests cannot\n * run teardown concurrently.\n *\n * Sandbox identity is the session id: retirement stops (or, for deleted\n * sessions, destroys) the sandbox this process holds in the session memo.\n * Sessions opened on another replica have no memo entry here — their VMs are\n * left to the provider's idle lifecycle (pause / idle GC), which is the\n * provider-owned half of the contract.\n */\nexport class SessionRetirementCoordinator {\n readonly #invalidateSession: NonNullable<SessionRetirementCoordinatorOptions['invalidateSession']>;\n readonly #warn: WarningLogger;\n readonly #locks = new Map<string, Promise<void>>();\n\n constructor(options: SessionRetirementCoordinatorOptions = {}) {\n this.#invalidateSession = options.invalidateSession ?? (() => {});\n this.#warn = options.warn ?? ((message, details) => console.warn(`[Mastra Factory] ${message}`, details));\n }\n\n async retireSession(input: RetireSessionInput): Promise<void> {\n const previous = this.#locks.get(input.sessionId) ?? Promise.resolve();\n const current = previous.catch(() => {}).then(() => this.#retireSession(input));\n this.#locks.set(input.sessionId, current);\n try {\n await current;\n } finally {\n if (this.#locks.get(input.sessionId) === current) this.#locks.delete(input.sessionId);\n }\n }\n\n async retireWorkItemSessions(options: {\n workItems: Pick<WorkItemsStorage, 'get'>;\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n workItemId: string;\n }): Promise<void> {\n const item = await options.workItems.get({ orgId: options.orgId, id: options.workItemId });\n if (!item) return;\n const sessionIds = [...new Set(Object.values(item.sessions).map(session => session.sessionId))];\n await Promise.all(\n sessionIds.map(sessionId =>\n this.retireSession({\n sourceControl: options.sourceControl,\n orgId: options.orgId,\n sessionId,\n deleteSession: false,\n }),\n ),\n );\n }\n\n async retireProjectRepositorySessions(options: {\n sourceControl: SourceControlStorageHandle;\n workItems?: Pick<WorkItemsStorage, 'clearSessionReferences'>;\n orgId: string;\n projectRepositoryId: string;\n }): Promise<void> {\n const sessions = await options.sourceControl.sessions.listByProjectRepository({\n projectRepositoryId: options.projectRepositoryId,\n });\n await Promise.all(\n sessions.map(session =>\n this.retireSession({\n sourceControl: options.sourceControl,\n ...(options.workItems ? { workItems: options.workItems } : {}),\n orgId: options.orgId,\n sessionId: session.sessionId,\n deleteSession: true,\n }),\n ),\n );\n }\n\n async #retireSession(input: RetireSessionInput): Promise<void> {\n const session = await input.sourceControl.sessions.getBySessionId(input.sessionId);\n if (!session || session.orgId !== input.orgId) return;\n\n try {\n await this.#teardownSessionSandbox(input, session);\n } finally {\n try {\n await this.#invalidateSession(session.sessionId);\n } catch (error) {\n this.#warn('Factory session workspace cache invalidation failed', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n error: boundedError(error),\n });\n }\n\n if (input.deleteSession) {\n // Refs first: clearing again is a no-op, but refs on a deleted row would dangle forever.\n await input.workItems?.clearSessionReferences({ orgId: input.orgId, sessionId: input.sessionId });\n await input.sourceControl.sessions.delete(session.id);\n }\n }\n }\n\n async #teardownSessionSandbox(input: RetireSessionInput, session: SourceControlSession): Promise<void> {\n const entry = peekSessionSandbox(session.id);\n if (!entry) return;\n\n // A session booted from its factory environment tears every repository\n // down in position order, each in its own directory; a failure is logged\n // and the next repository still runs.\n const environmentTeardown = peekSessionEnvironmentTeardown(session.sessionId);\n if (environmentTeardown.length > 0) {\n for (const repo of environmentTeardown) {\n try {\n await runTeardownCommand(requireExec(entry.sandbox), repo.dir, repo.command, {\n timeoutMs: DEFAULT_COMMAND_TIMEOUT_MS,\n });\n } catch (error) {\n this.#warn('Factory teardown command failed', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n repository: repo.slug,\n error: boundedError(error),\n });\n }\n }\n await this.#releaseSandbox(input, session);\n return;\n }\n\n let projectRepository: ProjectRepository | null | undefined;\n try {\n projectRepository = session.projectRepositoryId\n ? await input.sourceControl.projectRepositories.get({ orgId: input.orgId, id: session.projectRepositoryId })\n : null;\n } catch (error) {\n this.#warn('Factory repository settings could not be loaded for session retirement', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n error: boundedError(error),\n });\n }\n\n // An unresolved workdir means the sandbox never started in this process:\n // nothing was set up, so there is nothing for a teardown command to undo.\n if (projectRepository?.teardownCommand && entry.workdir) {\n try {\n await runTeardownCommand(requireExec(entry.sandbox), entry.workdir, projectRepository.teardownCommand, {\n timeoutMs: DEFAULT_COMMAND_TIMEOUT_MS,\n });\n } catch (error) {\n this.#warn('Factory teardown command failed', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n error: boundedError(error),\n });\n }\n }\n\n await this.#releaseSandbox(input, session);\n }\n\n async #releaseSandbox(input: RetireSessionInput, session: SourceControlSession): Promise<void> {\n try {\n await releaseSessionSandbox({ sessionId: session.id, destroy: input.deleteSession });\n } catch (error) {\n this.#warn('Factory session sandbox release failed', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n error: boundedError(error),\n });\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"session-retirement.js","names":["#invalidateSession","#sandbox","#warn","#destroyTimeoutMs","#locks","#withSessionLock","#retireSession","#recreateSessionSandbox","#teardownSessionSandbox","#invalidate","#destroyDetachedSandbox","#releaseSandbox","#destroyForRecreate","#settledWithin","#destroyDetachedForRecreate","#destroyDirectly"],"sources":["../../src/sandbox/session-retirement.ts"],"sourcesContent":["import type { FactorySandbox, WorkspaceSandbox } from '@mastra/core/workspace';\n\nimport { destroyDetachedSessionSandbox, releaseSessionSandbox } from '../integrations/github/sandbox-release.js';\nimport { DEFAULT_COMMAND_TIMEOUT_MS, runTeardownCommand } from '../integrations/github/sandbox.js';\nimport { peekSessionEnvironmentTeardown } from '../session/environment-state-processor.js';\nimport type {\n ProjectRepository,\n SourceControlSession,\n SourceControlStorageHandle,\n} from '../storage/domains/source-control/base.js';\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport { requireExec } from './materialization.js';\nimport { evictSessionSandbox, peekSessionSandbox } from './session-sandbox.js';\n\ntype WarningLogger = (message: string, details: Record<string, unknown>) => void;\n\nexport interface SessionRetirementCoordinatorOptions {\n invalidateSession?: (sessionId: string) => Promise<void> | void;\n /**\n * The deploy's sandbox configuration. Used to destroy a deleted session's\n * VM by identity when this process never attached to it (after a restart,\n * or on another replica). Recreate uses it the same way.\n */\n sandbox?: FactorySandbox;\n warn?: WarningLogger;\n /** How long recreate waits on a started sandbox's lifecycle destroy before falling back. */\n destroyTimeoutMs?: number;\n}\n\nexport interface RecreateSessionSandboxInput {\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n sessionId: string;\n}\n\nconst DEFAULT_DESTROY_TIMEOUT_MS = 30_000;\n\n/**\n * A provider error meaning the VM is already gone: 410, or a 404 the proxy\n * marks `not_found` (or leaves uncoded). A routing 404 with another code is\n * not proof the VM is gone.\n */\nfunction isSandboxGoneError(error: unknown): boolean {\n if (typeof error !== 'object' || error === null) return false;\n const { status, code } = error as { status?: unknown; code?: unknown };\n if (status === 410) return true;\n return status === 404 && (code === undefined || code === null || code === 'not_found');\n}\n\nexport interface RetireSessionInput {\n sourceControl: SourceControlStorageHandle;\n /** When provided, deleting the session row also strips its work-item refs. */\n workItems?: Pick<WorkItemsStorage, 'clearSessionReferences'>;\n orgId: string;\n sessionId: string;\n deleteSession: boolean;\n}\n\nfunction boundedError(error: unknown): string {\n const detail = error instanceof Error ? error.message : String(error);\n if (detail.length <= 2000) return detail;\n return `${detail.slice(0, 200)}...${detail.slice(-1797)}`;\n}\n\n/**\n * Owns terminal and destructive session cleanup. Each session is serialized so\n * duplicate role bindings and competing deletion/transition requests cannot\n * run teardown concurrently.\n *\n * Sandbox identity is the session id: retirement stops (or, for deleted\n * sessions, destroys) the sandbox this process holds in the session memo.\n * Sessions opened on another replica (or before a restart) have no memo entry\n * here: stopping them is left to the provider's idle lifecycle, but deleting a\n * materialized session destroys its VM by identity through the configured\n * sandbox. Providers such as E2B keep paused VMs forever, so the row's\n * deletion must never orphan one.\n */\nexport class SessionRetirementCoordinator {\n readonly #invalidateSession: NonNullable<SessionRetirementCoordinatorOptions['invalidateSession']>;\n readonly #sandbox: FactorySandbox | undefined;\n readonly #warn: WarningLogger;\n readonly #destroyTimeoutMs: number;\n readonly #locks = new Map<string, Promise<unknown>>();\n\n constructor(options: SessionRetirementCoordinatorOptions = {}) {\n this.#invalidateSession = options.invalidateSession ?? (() => {});\n this.#sandbox = options.sandbox;\n this.#warn = options.warn ?? ((message, details) => console.warn(`[Mastra Factory] ${message}`, details));\n this.#destroyTimeoutMs = options.destroyTimeoutMs ?? DEFAULT_DESTROY_TIMEOUT_MS;\n }\n\n async retireSession(input: RetireSessionInput): Promise<void> {\n await this.#withSessionLock(input.sessionId, () => this.#retireSession(input));\n }\n\n /**\n * Destroy a session's sandbox so its next message boots a fresh VM from the\n * template, keeping the session row, its conversation, and work-item refs.\n * Skips teardown commands, and works without a memo entry in this process\n * by destroying a detached instance built from the persisted `sandboxId`.\n * A destroy failure keeps the persisted id so a retry can reach the VM.\n */\n async recreateSessionSandbox(input: RecreateSessionSandboxInput): Promise<'recreated' | 'not-found'> {\n return this.#withSessionLock(input.sessionId, () => this.#recreateSessionSandbox(input));\n }\n\n async #withSessionLock<T>(sessionId: string, run: () => Promise<T>): Promise<T> {\n const previous = this.#locks.get(sessionId) ?? Promise.resolve();\n const current = previous.catch(() => {}).then(run);\n this.#locks.set(sessionId, current);\n try {\n return await current;\n } finally {\n if (this.#locks.get(sessionId) === current) this.#locks.delete(sessionId);\n }\n }\n\n async retireWorkItemSessions(options: {\n workItems: Pick<WorkItemsStorage, 'get'>;\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n workItemId: string;\n }): Promise<void> {\n const item = await options.workItems.get({ orgId: options.orgId, id: options.workItemId });\n if (!item) return;\n const sessionIds = [...new Set(Object.values(item.sessions).map(session => session.sessionId))];\n await Promise.all(\n sessionIds.map(sessionId =>\n this.retireSession({\n sourceControl: options.sourceControl,\n orgId: options.orgId,\n sessionId,\n deleteSession: false,\n }),\n ),\n );\n }\n\n async retireProjectRepositorySessions(options: {\n sourceControl: SourceControlStorageHandle;\n workItems?: Pick<WorkItemsStorage, 'clearSessionReferences'>;\n orgId: string;\n projectRepositoryId: string;\n }): Promise<void> {\n const sessions = await options.sourceControl.sessions.listByProjectRepository({\n projectRepositoryId: options.projectRepositoryId,\n });\n await Promise.all(\n sessions.map(session =>\n this.retireSession({\n sourceControl: options.sourceControl,\n ...(options.workItems ? { workItems: options.workItems } : {}),\n orgId: options.orgId,\n sessionId: session.sessionId,\n deleteSession: true,\n }),\n ),\n );\n }\n\n async #retireSession(input: RetireSessionInput): Promise<void> {\n const session = await input.sourceControl.sessions.getBySessionId(input.sessionId);\n if (!session || session.orgId !== input.orgId) return;\n\n try {\n await this.#teardownSessionSandbox(input, session);\n } finally {\n await this.#invalidate(session);\n\n if (input.deleteSession) {\n // Refs first: clearing again is a no-op, but refs on a deleted row would dangle forever.\n await input.workItems?.clearSessionReferences({ orgId: input.orgId, sessionId: input.sessionId });\n await input.sourceControl.sessions.delete(session.id);\n }\n }\n }\n\n async #teardownSessionSandbox(input: RetireSessionInput, session: SourceControlSession): Promise<void> {\n const entry = peekSessionSandbox(session.id);\n if (!entry) {\n await this.#destroyDetachedSandbox(input, session);\n return;\n }\n\n // A session booted from its factory environment tears every repository\n // down in position order, each in its own directory; a failure is logged\n // and the next repository still runs.\n const environmentTeardown = peekSessionEnvironmentTeardown(session.sessionId);\n if (environmentTeardown.length > 0) {\n for (const repo of environmentTeardown) {\n try {\n await runTeardownCommand(requireExec(entry.sandbox), repo.dir, repo.command, {\n timeoutMs: DEFAULT_COMMAND_TIMEOUT_MS,\n });\n } catch (error) {\n this.#warn('Factory teardown command failed', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n repository: repo.slug,\n error: boundedError(error),\n });\n }\n }\n await this.#releaseSandbox(input, session);\n return;\n }\n\n let projectRepository: ProjectRepository | null | undefined;\n try {\n projectRepository = session.projectRepositoryId\n ? await input.sourceControl.projectRepositories.get({ orgId: input.orgId, id: session.projectRepositoryId })\n : null;\n } catch (error) {\n this.#warn('Factory repository settings could not be loaded for session retirement', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n error: boundedError(error),\n });\n }\n\n // An unresolved workdir means the sandbox never started in this process:\n // nothing was set up, so there is nothing for a teardown command to undo.\n if (projectRepository?.teardownCommand && entry.workdir) {\n try {\n await runTeardownCommand(requireExec(entry.sandbox), entry.workdir, projectRepository.teardownCommand, {\n timeoutMs: DEFAULT_COMMAND_TIMEOUT_MS,\n });\n } catch (error) {\n this.#warn('Factory teardown command failed', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n error: boundedError(error),\n });\n }\n }\n\n await this.#releaseSandbox(input, session);\n }\n\n /**\n * Destroy a deleted session's VM that this process never attached to. Only\n * for sessions that materialized (a persisted `sandboxId`): nothing is\n * started, no teardown command runs — the provider kills by identity.\n */\n async #destroyDetachedSandbox(input: RetireSessionInput, session: SourceControlSession): Promise<void> {\n if (!input.deleteSession || !session.sandboxId || !this.#sandbox) return;\n try {\n await destroyDetachedSessionSandbox({ sandbox: this.#sandbox, session });\n } catch (error) {\n this.#warn('Factory detached session sandbox destroy failed', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n error: boundedError(error),\n });\n }\n }\n\n /** Bump the session's workspace generation; a failing unregister is logged, never fatal. */\n async #invalidate(session: SourceControlSession): Promise<void> {\n try {\n await this.#invalidateSession(session.sessionId);\n } catch (error) {\n this.#warn('Factory session workspace cache invalidation failed', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n error: boundedError(error),\n });\n }\n }\n\n async #recreateSessionSandbox(input: RecreateSessionSandboxInput): Promise<'recreated' | 'not-found'> {\n const session = await input.sourceControl.sessions.getBySessionId(input.sessionId);\n if (!session || session.orgId !== input.orgId) return 'not-found';\n\n // Bump the generation first so an in-flight start hook for the old\n // instance throws instead of republishing its sandbox id.\n await this.#invalidate(session);\n const entry = peekSessionSandbox(session.id);\n evictSessionSandbox(session.id);\n\n await this.#destroyForRecreate(session, entry?.sandbox);\n await input.sourceControl.sessions.setSandbox({ id: session.id, sandboxId: null, sandboxWorkdir: '' });\n\n // A resolve that ran between the eviction and clearing the id could have\n // built a fresh instance from the old `sandboxId`; drop it and fence it.\n evictSessionSandbox(session.id);\n await this.#invalidate(session);\n return 'recreated';\n }\n\n /**\n * Destroy the session's VM. `_destroy()` silently no-ops on a never-started\n * (`pending`) instance, so pending and detached instances call the\n * provider's `destroy()` directly; a started instance goes through the\n * lifecycle wrapper, bounded so a hung start can't hold the session lock.\n */\n async #destroyForRecreate(session: SourceControlSession, memo: WorkspaceSandbox | undefined): Promise<void> {\n const lifecycle = memo as { _destroy?: () => Promise<void> } | undefined;\n if (memo && memo.status !== 'pending' && lifecycle?._destroy) {\n const destroying = lifecycle._destroy.call(memo);\n if (await this.#settledWithin(destroying)) {\n memo.status = 'destroyed';\n return;\n }\n // Load-bearing: once the hung start settles, this destroy reaps any VM it\n // provisioned. Its rejection must never surface as unhandled.\n destroying.catch(error =>\n this.#warn('Factory sandbox destroy failed after recreate stopped waiting', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n error: boundedError(error),\n }),\n );\n if (!this.#sandbox) {\n throw new Error(\n 'Timed out destroying the session sandbox, and no sandbox provider is configured to retry by identity.',\n );\n }\n await this.#destroyDetachedForRecreate(this.#sandbox, session);\n memo.status = 'destroyed';\n return;\n }\n\n if (memo) {\n await this.#destroyDirectly(memo);\n memo.status = 'destroyed';\n return;\n }\n\n // No instance in this process (restart, or another replica opened it):\n // destroy through a never-started instance carrying the persisted id.\n // Unlike retirement, this runs without a `sandboxId` too: E2B and Docker\n // find the VM by session identity, and Platform's destroy no-ops safely.\n if (this.#sandbox) await this.#destroyDetachedForRecreate(this.#sandbox, session);\n }\n\n async #destroyDetachedForRecreate(sandbox: FactorySandbox, session: SourceControlSession): Promise<void> {\n try {\n await destroyDetachedSessionSandbox({ sandbox, session });\n } catch (error) {\n if (!isSandboxGoneError(error)) throw error;\n }\n }\n\n /** True when the destroy finished in time; false on timeout. Gone-VM errors count as finished. */\n async #settledWithin(destroying: Promise<void>): Promise<boolean> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timedOut = new Promise<false>(resolve => {\n timer = setTimeout(() => resolve(false), this.#destroyTimeoutMs);\n });\n try {\n return await Promise.race([destroying.then(() => true as const), timedOut]);\n } catch (error) {\n if (isSandboxGoneError(error)) return true;\n throw error;\n } finally {\n clearTimeout(timer);\n }\n }\n\n async #destroyDirectly(sandbox: WorkspaceSandbox): Promise<void> {\n try {\n await sandbox.destroy?.();\n } catch (error) {\n if (!isSandboxGoneError(error)) throw error;\n }\n }\n\n async #releaseSandbox(input: RetireSessionInput, session: SourceControlSession): Promise<void> {\n try {\n await releaseSessionSandbox({ sessionId: session.id, destroy: input.deleteSession });\n } catch (error) {\n this.#warn('Factory session sandbox release failed', {\n orgId: session.orgId,\n sessionId: session.sessionId,\n projectRepositoryId: session.projectRepositoryId,\n error: boundedError(error),\n });\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { CredentialTenant } from '@mastra/code-sdk/agents/credential-resolver';
|
|
2
2
|
import type { RequestContext } from '@mastra/core/request-context';
|
|
3
3
|
import type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';
|
|
4
4
|
import type { WorkItemsStorage } from '../storage/domains/work-items/base.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credential-policy.d.ts","sourceRoot":"","sources":["../../src/session/credential-policy.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"credential-policy.d.ts","sourceRoot":"","sources":["../../src/session/credential-policy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6CAA6C,CAAC;AAEpF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAInE,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAClF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAC9E,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAIvE,UAAU,8BAA8B;IACtC,QAAQ,EAAE,IAAI,CAAC,sBAAsB,EAAE,SAAS,CAAC,CAAC;IAClD,aAAa,EAAE,IAAI,CAAC,0BAA0B,EAAE,gBAAgB,CAAC,CAAC;IAClE,SAAS,EAAE,IAAI,CAAC,gBAAgB,EAAE,wBAAwB,CAAC,CAAC;CAC7D;AAED,uGAAuG;AACvG,wBAAsB,8BAA8B,CAClD,cAAc,EAAE,cAAc,EAC9B,IAAI,EAAE,8BAA8B,GACnC,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAEvC;AAED,+FAA+F;AAC/F,wBAAsB,yBAAyB,CAC7C,cAAc,EAAE,cAAc,EAC9B,IAAI,EAAE,8BAA8B,GACnC,OAAO,CAAC;IAAE,MAAM,EAAE,gBAAgB,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAAC,qBAAqB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,SAAS,CAAC,CAsDrH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credential-policy.js","names":[],"sources":["../../src/session/credential-policy.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"credential-policy.js","names":[],"sources":["../../src/session/credential-policy.ts"],"sourcesContent":["import { resolveAuthenticatedCredentialTenant } from '@mastra/code-sdk/agents/credential-resolver';\nimport type { CredentialTenant } from '@mastra/code-sdk/agents/credential-resolver';\nimport type { AgentControllerRequestContext } from '@mastra/core/agent-controller';\nimport type { RequestContext } from '@mastra/core/request-context';\n\nimport { UUID_PATTERN } from '../routes/contracts.js';\nimport { effectiveModelScope } from '../storage/domains/projects/base.js';\nimport type { FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport type { WorkItemsStorage } from '../storage/domains/work-items/base.js';\nimport type { SourceControlSessionLookup } from './factory-session.js';\n\nconst FACTORY_PROJECT_ID = new RegExp(UUID_PATTERN);\n\ninterface FactoryModelPolicyDependencies {\n projects: Pick<FactoryProjectsStorage, 'getById'>;\n sourceControl: Pick<SourceControlSessionLookup, 'getBySessionId'>;\n workItems: Pick<WorkItemsStorage, 'findRunBindingByThread'>;\n}\n\n/** Resolve billing authority from authenticated identity and durable bindings, never session state. */\nexport async function resolveFactoryCredentialTenant(\n requestContext: RequestContext,\n deps: FactoryModelPolicyDependencies,\n): Promise<CredentialTenant | undefined> {\n return (await resolveFactoryModelPolicy(requestContext, deps))?.tenant;\n}\n\n/** Shared invocation and model-picker policy; no client-supplied owner or scope is trusted. */\nexport async function resolveFactoryModelPolicy(\n requestContext: RequestContext,\n deps: FactoryModelPolicyDependencies,\n): Promise<{ tenant: CredentialTenant; factoryProjectId?: string; factoryDefaultModelId?: string | null } | undefined> {\n const principal = resolveAuthenticatedCredentialTenant(requestContext);\n if (!principal) return undefined;\n const controller = requestContext.get('controller') as AgentControllerRequestContext | undefined;\n if (!controller?.resourceId) return { tenant: principal };\n\n const sourceSession = await deps.sourceControl.getBySessionId(controller.resourceId);\n const binding =\n principal.orgId && controller.threadId\n ? await deps.workItems.findRunBindingByThread({\n orgId: principal.orgId,\n resourceId: controller.resourceId,\n sessionId: controller.resourceId,\n threadId: controller.threadId,\n rejectAmbiguous: true,\n })\n : null;\n if (!sourceSession) {\n if (!binding && (controller.resourceId === principal.userId || controller.resourceId.startsWith('channel:')))\n return { tenant: principal };\n // Session-less Factory pages address the Factory itself: the resourceId is the project id.\n const factoryProject =\n !binding && FACTORY_PROJECT_ID.test(controller.resourceId)\n ? await deps.projects.getById({ id: controller.resourceId })\n : null;\n if (factoryProject && factoryProject.orgId === principal.orgId) {\n return {\n factoryProjectId: factoryProject.id,\n factoryDefaultModelId: factoryProject.defaultModelId,\n tenant: { ...principal, credentialScope: effectiveModelScope(factoryProject.modelScope, 'session') },\n };\n }\n throw new Error('Factory session ownership is unavailable.');\n }\n if (\n sourceSession.orgId !== principal.orgId ||\n (sourceSession.visibility === 'private' && sourceSession.userId !== principal.userId)\n ) {\n throw new Error('Factory session is not available to the current user.');\n }\n if (!sourceSession.factoryProjectId) throw new Error('Factory session ownership is unavailable.');\n const projectId = binding?.factoryProjectId ?? sourceSession.factoryProjectId;\n const project = await deps.projects.getById({ id: projectId });\n if (!project || project.orgId !== sourceSession.orgId) throw new Error('Factory model policy is unavailable.');\n // Work-item threads keep their billing scope after revocation; history never restores run authority.\n return {\n factoryProjectId: project.id,\n factoryDefaultModelId: project.defaultModelId,\n tenant: {\n orgId: project.orgId,\n userId: sourceSession.userId,\n credentialScope: effectiveModelScope(project.modelScope, binding ? 'run' : 'session'),\n },\n };\n}\n"],"mappings":";;;;AAWA,MAAM,qBAAqB,IAAI,OAAO,YAAY;;AASlD,eAAsB,+BACpB,gBACA,MACuC;CACvC,QAAQ,MAAM,0BAA0B,gBAAgB,IAAI,EAAA,EAAI;AAClE;;AAGA,eAAsB,0BACpB,gBACA,MACqH;CACrH,MAAM,YAAY,qCAAqC,cAAc;CACrE,IAAI,CAAC,WAAW,OAAO,KAAA;CACvB,MAAM,aAAa,eAAe,IAAI,YAAY;CAClD,IAAI,CAAC,YAAY,YAAY,OAAO,EAAE,QAAQ,UAAU;CAExD,MAAM,gBAAgB,MAAM,KAAK,cAAc,eAAe,WAAW,UAAU;CACnF,MAAM,UACJ,UAAU,SAAS,WAAW,WAC1B,MAAM,KAAK,UAAU,uBAAuB;EAC1C,OAAO,UAAU;EACjB,YAAY,WAAW;EACvB,WAAW,WAAW;EACtB,UAAU,WAAW;EACrB,iBAAiB;CACnB,CAAC,IACD;CACN,IAAI,CAAC,eAAe;EAClB,IAAI,CAAC,YAAY,WAAW,eAAe,UAAU,UAAU,WAAW,WAAW,WAAW,UAAU,IACxG,OAAO,EAAE,QAAQ,UAAU;EAE7B,MAAM,iBACJ,CAAC,WAAW,mBAAmB,KAAK,WAAW,UAAU,IACrD,MAAM,KAAK,SAAS,QAAQ,EAAE,IAAI,WAAW,WAAW,CAAC,IACzD;EACN,IAAI,kBAAkB,eAAe,UAAU,UAAU,OACvD,OAAO;GACL,kBAAkB,eAAe;GACjC,uBAAuB,eAAe;GACtC,QAAQ;IAAE,GAAG;IAAW,iBAAiB,oBAAoB,eAAe,YAAY,SAAS;GAAE;EACrG;EAEF,MAAM,IAAI,MAAM,2CAA2C;CAC7D;CACA,IACE,cAAc,UAAU,UAAU,SACjC,cAAc,eAAe,aAAa,cAAc,WAAW,UAAU,QAE9E,MAAM,IAAI,MAAM,uDAAuD;CAEzE,IAAI,CAAC,cAAc,kBAAkB,MAAM,IAAI,MAAM,2CAA2C;CAChG,MAAM,YAAY,SAAS,oBAAoB,cAAc;CAC7D,MAAM,UAAU,MAAM,KAAK,SAAS,QAAQ,EAAE,IAAI,UAAU,CAAC;CAC7D,IAAI,CAAC,WAAW,QAAQ,UAAU,cAAc,OAAO,MAAM,IAAI,MAAM,sCAAsC;CAE7G,OAAO;EACL,kBAAkB,QAAQ;EAC1B,uBAAuB,QAAQ;EAC/B,QAAQ;GACN,OAAO,QAAQ;GACf,QAAQ,cAAc;GACtB,iBAAiB,oBAAoB,QAAQ,YAAY,UAAU,QAAQ,SAAS;EACtF;CACF;AACF"}
|
|
@@ -2,7 +2,7 @@ import type { MastraCodeState } from '@mastra/code-sdk/schema';
|
|
|
2
2
|
import type { AgentController } from '@mastra/core/agent-controller';
|
|
3
3
|
import type { ModelDefaultsStorage } from '../storage/domains/model-defaults/base.js';
|
|
4
4
|
import type { EffectiveModelScope, FactoryProjectsStorage } from '../storage/domains/projects/base.js';
|
|
5
|
-
import {
|
|
5
|
+
import type { SourceControlSession, SourceControlSessionVisibility, SourceControlStorageHandle } from '../storage/domains/source-control/base.js';
|
|
6
6
|
type FactorySession = Awaited<ReturnType<AgentController<MastraCodeState>['createSession']>>;
|
|
7
7
|
export declare class FactorySourceControlConflictError extends Error {
|
|
8
8
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory-session.d.ts","sourceRoot":"","sources":["../../src/session/factory-session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAGrE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEtF,OAAO,KAAK,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;
|
|
1
|
+
{"version":3,"file":"factory-session.d.ts","sourceRoot":"","sources":["../../src/session/factory-session.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"factory-session.js","names":[],"sources":["../../src/session/factory-session.ts"],"sourcesContent":["import type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController } from '@mastra/core/agent-controller';\n\nimport { resolveTenantProviderDefaultModel } from '../routes/tenant-credentials.js';\nimport type { ModelDefaultsStorage } from '../storage/domains/model-defaults/base.js';\nimport { effectiveModelScope } from '../storage/domains/projects/base.js';\nimport type { EffectiveModelScope, FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport {\n SourceControlConnectionNotFoundError,\n type SourceControlSession,\n type SourceControlSessionVisibility,\n type SourceControlStorageHandle,\n} from '../storage/domains/source-control/base.js';\nimport { resolveEnvironmentRepositories } from './environment-repositories.js';\nimport { seedSessionOrg } from './org-seed.js';\n\ntype FactorySession = Awaited<ReturnType<AgentController<MastraCodeState>['createSession']>>;\n\nexport class FactorySourceControlConflictError extends Error {}\n\n/** Resolve the model and exclusive credential scope for a Factory session. */\nexport async function resolveFactorySessionModel(args: {\n projects: FactoryProjectsStorage | undefined;\n modelDefaults: ModelDefaultsStorage | undefined;\n factoryProjectId: string | undefined;\n orgId: string;\n ownerUserId: string;\n /** A work-item run or an interactive session; a hybrid Factory resolves a different scope for each. */\n context: 'run' | 'session';\n}): Promise<{ modelScope: EffectiveModelScope; modelId?: string }> {\n if (!args.projects || !args.factoryProjectId) throw new Error('Factory model policy is unavailable.');\n\n const project = await args.projects.getById({ id: args.factoryProjectId });\n if (!project || project.orgId !== args.orgId) throw new Error('Factory model policy is unavailable.');\n if (effectiveModelScope(project.modelScope, args.context) === 'factory') {\n return { modelScope: 'factory', ...(project.defaultModelId ? { modelId: project.defaultModelId } : {}) };\n }\n\n try {\n const userDefault = await args.modelDefaults?.get({ orgId: args.orgId, userId: args.ownerUserId });\n return { modelScope: 'user', ...(userDefault?.modelId ? { modelId: userDefault.modelId } : {}) };\n } catch (error) {\n console.warn('[Factory] Owner default model lookup failed; using the mode default model', {\n orgId: args.orgId,\n ownerUserId: args.ownerUserId,\n error: error instanceof Error ? error.message : String(error),\n });\n return { modelScope: 'user' };\n }\n}\n\nexport interface SourceControlSessionLookup {\n getBySessionId(sessionId: string): Promise<SourceControlSession | null>;\n getSourceControlBySessionId(sessionId: string): Promise<SourceControlStorageHandle | null>;\n /** Side sessions started by `originSessionId`, across every provider. */\n listByOrigin(args: { orgId: string; originSessionId: string }): Promise<SourceControlSession[]>;\n rename(args: { sessionId: string; title: string }): Promise<void>;\n markFirstMessage(args: { sessionId: string }): Promise<void>;\n markFirstMeaningfulExec(args: { sessionId: string }): Promise<void>;\n}\n\n/**\n * Read or update a session across every registered source-control partition.\n * Session ids are globally generated, so more than one match is invalid and\n * fails closed instead of mutating an arbitrary provider.\n */\nexport function createSourceControlSessionLookup(\n sourceControls: readonly SourceControlStorageHandle[],\n): SourceControlSessionLookup {\n const resolve = async (\n sessionId: string,\n ): Promise<{ sourceControl: SourceControlStorageHandle; session: SourceControlSession } | null> => {\n const matches = (\n await Promise.all(\n sourceControls.map(async sourceControl => ({\n sourceControl,\n session: await sourceControl.sessions.getBySessionId(sessionId),\n })),\n )\n ).filter(\n (match): match is { sourceControl: SourceControlStorageHandle; session: SourceControlSession } =>\n match.session !== null,\n );\n if (matches.length > 1) throw new Error('Factory session exists in multiple source-control providers.');\n return matches[0] ?? null;\n };\n\n return {\n getBySessionId: async sessionId => (await resolve(sessionId))?.session ?? null,\n getSourceControlBySessionId: async sessionId => (await resolve(sessionId))?.sourceControl ?? null,\n listByOrigin: async args =>\n (await Promise.all(sourceControls.map(sourceControl => sourceControl.sessions.listByOrigin(args)))).flat(),\n rename: async args => (await resolve(args.sessionId))?.sourceControl.sessions.rename(args),\n markFirstMessage: async args => (await resolve(args.sessionId))?.sourceControl.sessions.markFirstMessage(args),\n markFirstMeaningfulExec: async args =>\n (await resolve(args.sessionId))?.sourceControl.sessions.markFirstMeaningfulExec(args),\n };\n}\n\n/**\n * Resolve the source-control provider from durable Factory repository links,\n * never from the issue tracker that happened to create the work item.\n */\nexport async function resolveFactorySourceControl(args: {\n sourceControls: readonly SourceControlStorageHandle[];\n orgId: string;\n factoryProjectId: string;\n sessionId?: string;\n}): Promise<SourceControlStorageHandle | undefined> {\n if (args.sessionId) {\n const sessionMatches = (\n await Promise.all(\n args.sourceControls.map(async sourceControl => ({\n sourceControl,\n session: await sourceControl.sessions.getBySessionId(args.sessionId!),\n })),\n )\n ).filter(match => match.session !== null);\n if (sessionMatches.length > 1) throw new Error('Factory session exists in multiple source-control providers.');\n if (sessionMatches[0]) return sessionMatches[0].sourceControl;\n }\n\n const linked = [];\n for (const sourceControl of args.sourceControls) {\n const connections = await sourceControl.connections.list({\n orgId: args.orgId,\n factoryProjectId: args.factoryProjectId,\n });\n let hasLinkedRepository = false;\n for (const connection of connections) {\n try {\n if (\n (await sourceControl.projectRepositories.list({ orgId: args.orgId, connectionId: connection.id })).length > 0\n ) {\n hasLinkedRepository = true;\n break;\n }\n } catch (error) {\n if (!(error instanceof SourceControlConnectionNotFoundError)) throw error;\n }\n }\n if (hasLinkedRepository) linked.push(sourceControl);\n }\n if (linked.length > 1)\n throw new FactorySourceControlConflictError(\n 'Factory project has repositories linked through multiple source-control providers.',\n );\n return linked[0];\n}\n\nexport interface EnsureFactorySourceSessionArgs {\n /**\n * Storage handle of the integration that owns source control. Nothing here is\n * provider-specific: the connection is matched by the handle's own\n * `integrationId`, so GitHub, Slack-on-behalf-of-GitHub, or any future owner\n * all resolve through the same traversal.\n */\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n factoryProjectId: string;\n branch: string;\n /**\n * The repository link the session is filed under: the one a work item\n * targets. Defaults to the factory's position-1 environment link (D1), for\n * callers where nothing names a repository (chat threads, user sessions).\n */\n projectRepositoryId?: string;\n /**\n * Attribute the run to this user instead of the repo connector. Set when the\n * run has an interactive user — e.g. the person who approved a proposed run.\n */\n attributeToUserId?: string;\n /** A pre-generated session id, so a caller can name the branch after it. Defaults to a fresh UUID. */\n sessionId?: string;\n /** Defaults to 'org'. A side session copies its parent's visibility. */\n visibility?: SourceControlSessionVisibility;\n /** The session and thread that started this one as a side session. */\n origin?: { sessionId: string; threadId: string };\n}\n\nexport interface EnsuredFactorySourceSession {\n sessionId: string;\n userId: string;\n projectRepositoryId: string;\n branch: string;\n baseBranch: string;\n}\n\nexport class FactorySourceSessionResolutionError extends Error {\n constructor(readonly reason: 'connection' | 'repository') {\n super(\n reason === 'connection'\n ? 'Factory source-control connection not found.'\n : 'Factory source-control repository not found.',\n );\n this.name = 'FactorySourceSessionResolutionError';\n }\n}\n\nexport interface ResolvedFactorySourceRepository {\n projectRepositoryId: string;\n /** The repository's pinned branch, else its default branch. */\n baseBranch: string;\n /** Who connected the repository. The attribution for runs with no interactive user. */\n connectedByUserId: string;\n}\n\n/**\n * Outcome of {@link resolvePrimaryEnvironmentRepository}. A miss carries which\n * step failed: callers differ on whether that is an error (an autonomous run\n * cannot proceed) or a routine fallback (a chat integration drops to a\n * chat-only session), and the two steps fail for different reasons worth\n * reporting apart.\n */\nexport type FactorySourceRepositoryResult =\n | ({ found: true } & ResolvedFactorySourceRepository)\n | { found: false; reason: 'connection' | 'repository' };\n\n/**\n * The repository link a new session is filed under. By default the position-1\n * `inEnvironment` link of the factory (decision D1); a work item that targets\n * a repository names its link through `projectRepositoryId`, which must be an\n * environment link of the same factory. Session start no longer picks a\n * repository to work in, the sandbox holds every environment repository. The\n * PR tools default to this link, and subscriptions, audit and the work item's\n * repository check still read it; moving them to the per-repository\n * `sessionRepositories` rows is a follow-up.\n *\n * The owner is whichever integration owns source control, matched by the\n * handle's own `integrationId` — nothing here is provider-specific.\n */\nexport async function resolvePrimaryEnvironmentRepository(args: {\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n factoryProjectId: string;\n projectRepositoryId?: string;\n}): Promise<FactorySourceRepositoryResult> {\n const { sourceControl, orgId, factoryProjectId, projectRepositoryId } = args;\n const connections = await sourceControl.connections.list({ orgId, factoryProjectId });\n if (!connections.some(candidate => candidate.integrationId === sourceControl.integrationId)) {\n return { found: false, reason: 'connection' };\n }\n const repositories = await resolveEnvironmentRepositories({ sourceControl, orgId, factoryProjectId });\n const primary = repositories.find(\n candidate => projectRepositoryId === undefined || candidate.link.id === projectRepositoryId,\n );\n if (!primary) return { found: false, reason: 'repository' };\n return {\n found: true,\n projectRepositoryId: primary.link.id,\n baseBranch: primary.link.branch ?? primary.repository.defaultBranch,\n connectedByUserId: primary.connection.createdByUserId,\n };\n}\n\n/**\n * Create the source-control session a repo-backed factory run needs.\n *\n * `FactoryStartCoordinator.prepare` requires this record to already exist —\n * `resolveSourceSession` throws `Factory session not found` otherwise — so every\n * autonomous entry point has to produce one before it can start a run. This is\n * that step, in one place: the owner's connection on the factory project, the\n * work item's repository link (else the position-1 environment link, D1) as\n * the session's link, and a session on the requested branch with that\n * repository's pinned or default branch as the base. The sandbox itself boots\n * every environment repository.\n *\n * The run is attributed to `attributeToUserId` when the caller has an\n * interactive user (e.g. the approver of a proposed run), and otherwise falls\n * back to whoever connected the repository (`connection.createdByUserId`),\n * because a genuinely autonomous run has no interactive user of its own.\n */\nexport async function ensureFactorySourceSession(\n args: EnsureFactorySourceSessionArgs,\n): Promise<EnsuredFactorySourceSession> {\n const { sourceControl, orgId, factoryProjectId, branch, projectRepositoryId } = args;\n\n const resolved = await resolvePrimaryEnvironmentRepository({\n sourceControl,\n orgId,\n factoryProjectId,\n projectRepositoryId,\n });\n if (!resolved.found) throw new FactorySourceSessionResolutionError(resolved.reason);\n\n const userId = args.attributeToUserId ?? resolved.connectedByUserId;\n const session = await sourceControl.sessions.create({\n sessionId: args.sessionId ?? globalThis.crypto.randomUUID(),\n projectRepositoryId: resolved.projectRepositoryId,\n factoryProjectId,\n orgId,\n userId,\n branch,\n baseBranch: resolved.baseBranch,\n visibility: args.visibility ?? 'org',\n ...(args.origin ? { origin: args.origin } : {}),\n });\n return {\n sessionId: session.sessionId,\n userId,\n projectRepositoryId: resolved.projectRepositoryId,\n branch: session.branch,\n baseBranch: resolved.baseBranch,\n };\n}\n\nexport interface HydrateFactorySessionArgs {\n orgId: string;\n modelScope: EffectiveModelScope;\n ownerUserId: string;\n /**\n * The model selected by the Factory scope. Without it the provider default of a connected\n * provider is applied when the SDK mode default has no credential, else the mode default.\n */\n defaultModelId?: string;\n}\n\n/**\n * Apply a factory project's main model to a session on every run.\n *\n * The model switch is best-effort. A retired model id must not sink a run that\n * is otherwise ready, and the reason is logged. Observational-memory settings are resolved from the\n * authoritative row per invocation before memory processors run.\n */\nexport async function hydrateFactorySession(\n session: FactorySession,\n args: HydrateFactorySessionArgs,\n): Promise<string | undefined> {\n // The org rung knowledge curation scopes on. Seeded first so it lands even if\n // a later best-effort step fails; an empty org marks the session unresolved.\n await seedSessionOrg(session, args.orgId);\n await session.state.set({\n factoryModelScope: args.modelScope,\n factoryOwnerUserId: args.ownerUserId,\n });\n\n let selectedModelId = args.defaultModelId;\n if (!selectedModelId) {\n const modeModelId = session.mode.resolve().defaultModelId;\n const providerModelId = await resolveTenantProviderDefaultModel(\n { orgId: args.orgId, userId: args.ownerUserId, credentialScope: args.modelScope },\n modeModelId,\n ).catch(error => {\n console.warn('[Factory Start] Failed to resolve a provider default model', {\n error: error instanceof Error ? error.message : String(error),\n });\n return undefined;\n });\n selectedModelId = providerModelId ?? modeModelId;\n }\n if (!selectedModelId) return undefined;\n\n let switched = true;\n try {\n await session.model.switch(selectedModelId);\n } catch (error) {\n switched = false;\n console.warn('[Factory Start] Failed to apply scoped default model', {\n modelId: selectedModelId,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n // Subagents otherwise keep the server-wide settings (or the SDK's built-in\n // default), which may name a provider this factory has no credentials for.\n // Each role is independent, so one failure doesn't strand the others.\n for (const agentType of ['explore', 'plan', 'execute']) {\n try {\n await session.subagents.model.set({ modelId: selectedModelId, agentType });\n } catch (error) {\n console.warn('[Factory Start] Failed to apply scoped default subagent model', {\n agentType,\n modelId: selectedModelId,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n // Callers pin this id to the thread, so only report a model the session actually runs on.\n return switched ? selectedModelId : undefined;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"factory-session.js","names":[],"sources":["../../src/session/factory-session.ts"],"sourcesContent":["import type { MastraCodeState } from '@mastra/code-sdk/schema';\nimport type { AgentController } from '@mastra/core/agent-controller';\n\nimport { resolveTenantProviderDefaultModel } from '../routes/tenant-credentials.js';\nimport type { ModelDefaultsStorage } from '../storage/domains/model-defaults/base.js';\nimport { effectiveModelScope } from '../storage/domains/projects/base.js';\nimport type { EffectiveModelScope, FactoryProjectsStorage } from '../storage/domains/projects/base.js';\nimport { SourceControlConnectionNotFoundError } from '../storage/domains/source-control/base.js';\nimport type {\n SourceControlSession,\n SourceControlSessionVisibility,\n SourceControlStorageHandle,\n} from '../storage/domains/source-control/base.js';\nimport { resolveEnvironmentRepositories } from './environment-repositories.js';\nimport { seedSessionOrg } from './org-seed.js';\n\ntype FactorySession = Awaited<ReturnType<AgentController<MastraCodeState>['createSession']>>;\n\nexport class FactorySourceControlConflictError extends Error {}\n\n/** Resolve the model and exclusive credential scope for a Factory session. */\nexport async function resolveFactorySessionModel(args: {\n projects: FactoryProjectsStorage | undefined;\n modelDefaults: ModelDefaultsStorage | undefined;\n factoryProjectId: string | undefined;\n orgId: string;\n ownerUserId: string;\n /** A work-item run or an interactive session; a hybrid Factory resolves a different scope for each. */\n context: 'run' | 'session';\n}): Promise<{ modelScope: EffectiveModelScope; modelId?: string }> {\n if (!args.projects || !args.factoryProjectId) throw new Error('Factory model policy is unavailable.');\n\n const project = await args.projects.getById({ id: args.factoryProjectId });\n if (!project || project.orgId !== args.orgId) throw new Error('Factory model policy is unavailable.');\n if (effectiveModelScope(project.modelScope, args.context) === 'factory') {\n return { modelScope: 'factory', ...(project.defaultModelId ? { modelId: project.defaultModelId } : {}) };\n }\n\n try {\n const userDefault = await args.modelDefaults?.get({ orgId: args.orgId, userId: args.ownerUserId });\n return { modelScope: 'user', ...(userDefault?.modelId ? { modelId: userDefault.modelId } : {}) };\n } catch (error) {\n console.warn('[Factory] Owner default model lookup failed; using the mode default model', {\n orgId: args.orgId,\n ownerUserId: args.ownerUserId,\n error: error instanceof Error ? error.message : String(error),\n });\n return { modelScope: 'user' };\n }\n}\n\nexport interface SourceControlSessionLookup {\n getBySessionId(sessionId: string): Promise<SourceControlSession | null>;\n getSourceControlBySessionId(sessionId: string): Promise<SourceControlStorageHandle | null>;\n /** Side sessions started by `originSessionId`, across every provider. */\n listByOrigin(args: { orgId: string; originSessionId: string }): Promise<SourceControlSession[]>;\n rename(args: { sessionId: string; title: string }): Promise<void>;\n markFirstMessage(args: { sessionId: string }): Promise<void>;\n markFirstMeaningfulExec(args: { sessionId: string }): Promise<void>;\n}\n\n/**\n * Read or update a session across every registered source-control partition.\n * Session ids are globally generated, so more than one match is invalid and\n * fails closed instead of mutating an arbitrary provider.\n */\nexport function createSourceControlSessionLookup(\n sourceControls: readonly SourceControlStorageHandle[],\n): SourceControlSessionLookup {\n const resolve = async (\n sessionId: string,\n ): Promise<{ sourceControl: SourceControlStorageHandle; session: SourceControlSession } | null> => {\n const matches = (\n await Promise.all(\n sourceControls.map(async sourceControl => ({\n sourceControl,\n session: await sourceControl.sessions.getBySessionId(sessionId),\n })),\n )\n ).filter(\n (match): match is { sourceControl: SourceControlStorageHandle; session: SourceControlSession } =>\n match.session !== null,\n );\n if (matches.length > 1) throw new Error('Factory session exists in multiple source-control providers.');\n return matches[0] ?? null;\n };\n\n return {\n getBySessionId: async sessionId => (await resolve(sessionId))?.session ?? null,\n getSourceControlBySessionId: async sessionId => (await resolve(sessionId))?.sourceControl ?? null,\n listByOrigin: async args =>\n (await Promise.all(sourceControls.map(sourceControl => sourceControl.sessions.listByOrigin(args)))).flat(),\n rename: async args => (await resolve(args.sessionId))?.sourceControl.sessions.rename(args),\n markFirstMessage: async args => (await resolve(args.sessionId))?.sourceControl.sessions.markFirstMessage(args),\n markFirstMeaningfulExec: async args =>\n (await resolve(args.sessionId))?.sourceControl.sessions.markFirstMeaningfulExec(args),\n };\n}\n\n/**\n * Resolve the source-control provider from durable Factory repository links,\n * never from the issue tracker that happened to create the work item.\n */\nexport async function resolveFactorySourceControl(args: {\n sourceControls: readonly SourceControlStorageHandle[];\n orgId: string;\n factoryProjectId: string;\n sessionId?: string;\n}): Promise<SourceControlStorageHandle | undefined> {\n if (args.sessionId) {\n const sessionMatches = (\n await Promise.all(\n args.sourceControls.map(async sourceControl => ({\n sourceControl,\n session: await sourceControl.sessions.getBySessionId(args.sessionId!),\n })),\n )\n ).filter(match => match.session !== null);\n if (sessionMatches.length > 1) throw new Error('Factory session exists in multiple source-control providers.');\n if (sessionMatches[0]) return sessionMatches[0].sourceControl;\n }\n\n const linked = [];\n for (const sourceControl of args.sourceControls) {\n const connections = await sourceControl.connections.list({\n orgId: args.orgId,\n factoryProjectId: args.factoryProjectId,\n });\n let hasLinkedRepository = false;\n for (const connection of connections) {\n try {\n if (\n (await sourceControl.projectRepositories.list({ orgId: args.orgId, connectionId: connection.id })).length > 0\n ) {\n hasLinkedRepository = true;\n break;\n }\n } catch (error) {\n if (!(error instanceof SourceControlConnectionNotFoundError)) throw error;\n }\n }\n if (hasLinkedRepository) linked.push(sourceControl);\n }\n if (linked.length > 1)\n throw new FactorySourceControlConflictError(\n 'Factory project has repositories linked through multiple source-control providers.',\n );\n return linked[0];\n}\n\nexport interface EnsureFactorySourceSessionArgs {\n /**\n * Storage handle of the integration that owns source control. Nothing here is\n * provider-specific: the connection is matched by the handle's own\n * `integrationId`, so GitHub, Slack-on-behalf-of-GitHub, or any future owner\n * all resolve through the same traversal.\n */\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n factoryProjectId: string;\n branch: string;\n /**\n * The repository link the session is filed under: the one a work item\n * targets. Defaults to the factory's position-1 environment link (D1), for\n * callers where nothing names a repository (chat threads, user sessions).\n */\n projectRepositoryId?: string;\n /**\n * Attribute the run to this user instead of the repo connector. Set when the\n * run has an interactive user — e.g. the person who approved a proposed run.\n */\n attributeToUserId?: string;\n /** A pre-generated session id, so a caller can name the branch after it. Defaults to a fresh UUID. */\n sessionId?: string;\n /** Defaults to 'org'. A side session copies its parent's visibility. */\n visibility?: SourceControlSessionVisibility;\n /** The session and thread that started this one as a side session. */\n origin?: { sessionId: string; threadId: string };\n}\n\nexport interface EnsuredFactorySourceSession {\n sessionId: string;\n userId: string;\n projectRepositoryId: string;\n branch: string;\n baseBranch: string;\n}\n\nexport class FactorySourceSessionResolutionError extends Error {\n constructor(readonly reason: 'connection' | 'repository') {\n super(\n reason === 'connection'\n ? 'Factory source-control connection not found.'\n : 'Factory source-control repository not found.',\n );\n this.name = 'FactorySourceSessionResolutionError';\n }\n}\n\nexport interface ResolvedFactorySourceRepository {\n projectRepositoryId: string;\n /** The repository's pinned branch, else its default branch. */\n baseBranch: string;\n /** Who connected the repository. The attribution for runs with no interactive user. */\n connectedByUserId: string;\n}\n\n/**\n * Outcome of {@link resolvePrimaryEnvironmentRepository}. A miss carries which\n * step failed: callers differ on whether that is an error (an autonomous run\n * cannot proceed) or a routine fallback (a chat integration drops to a\n * chat-only session), and the two steps fail for different reasons worth\n * reporting apart.\n */\nexport type FactorySourceRepositoryResult =\n | ({ found: true } & ResolvedFactorySourceRepository)\n | { found: false; reason: 'connection' | 'repository' };\n\n/**\n * The repository link a new session is filed under. By default the position-1\n * `inEnvironment` link of the factory (decision D1); a work item that targets\n * a repository names its link through `projectRepositoryId`, which must be an\n * environment link of the same factory. Session start no longer picks a\n * repository to work in, the sandbox holds every environment repository. The\n * PR tools default to this link, and subscriptions, audit and the work item's\n * repository check still read it; moving them to the per-repository\n * `sessionRepositories` rows is a follow-up.\n *\n * The owner is whichever integration owns source control, matched by the\n * handle's own `integrationId` — nothing here is provider-specific.\n */\nexport async function resolvePrimaryEnvironmentRepository(args: {\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n factoryProjectId: string;\n projectRepositoryId?: string;\n}): Promise<FactorySourceRepositoryResult> {\n const { sourceControl, orgId, factoryProjectId, projectRepositoryId } = args;\n const connections = await sourceControl.connections.list({ orgId, factoryProjectId });\n if (!connections.some(candidate => candidate.integrationId === sourceControl.integrationId)) {\n return { found: false, reason: 'connection' };\n }\n const repositories = await resolveEnvironmentRepositories({ sourceControl, orgId, factoryProjectId });\n const primary = repositories.find(\n candidate => projectRepositoryId === undefined || candidate.link.id === projectRepositoryId,\n );\n if (!primary) return { found: false, reason: 'repository' };\n return {\n found: true,\n projectRepositoryId: primary.link.id,\n baseBranch: primary.link.branch ?? primary.repository.defaultBranch,\n connectedByUserId: primary.connection.createdByUserId,\n };\n}\n\n/**\n * Create the source-control session a repo-backed factory run needs.\n *\n * `FactoryStartCoordinator.prepare` requires this record to already exist —\n * `resolveSourceSession` throws `Factory session not found` otherwise — so every\n * autonomous entry point has to produce one before it can start a run. This is\n * that step, in one place: the owner's connection on the factory project, the\n * work item's repository link (else the position-1 environment link, D1) as\n * the session's link, and a session on the requested branch with that\n * repository's pinned or default branch as the base. The sandbox itself boots\n * every environment repository.\n *\n * The run is attributed to `attributeToUserId` when the caller has an\n * interactive user (e.g. the approver of a proposed run), and otherwise falls\n * back to whoever connected the repository (`connection.createdByUserId`),\n * because a genuinely autonomous run has no interactive user of its own.\n */\nexport async function ensureFactorySourceSession(\n args: EnsureFactorySourceSessionArgs,\n): Promise<EnsuredFactorySourceSession> {\n const { sourceControl, orgId, factoryProjectId, branch, projectRepositoryId } = args;\n\n const resolved = await resolvePrimaryEnvironmentRepository({\n sourceControl,\n orgId,\n factoryProjectId,\n projectRepositoryId,\n });\n if (!resolved.found) throw new FactorySourceSessionResolutionError(resolved.reason);\n\n const userId = args.attributeToUserId ?? resolved.connectedByUserId;\n const session = await sourceControl.sessions.create({\n sessionId: args.sessionId ?? globalThis.crypto.randomUUID(),\n projectRepositoryId: resolved.projectRepositoryId,\n factoryProjectId,\n orgId,\n userId,\n branch,\n baseBranch: resolved.baseBranch,\n visibility: args.visibility ?? 'org',\n ...(args.origin ? { origin: args.origin } : {}),\n });\n return {\n sessionId: session.sessionId,\n userId,\n projectRepositoryId: resolved.projectRepositoryId,\n branch: session.branch,\n baseBranch: resolved.baseBranch,\n };\n}\n\nexport interface HydrateFactorySessionArgs {\n orgId: string;\n modelScope: EffectiveModelScope;\n ownerUserId: string;\n /**\n * The model selected by the Factory scope. Without it the provider default of a connected\n * provider is applied when the SDK mode default has no credential, else the mode default.\n */\n defaultModelId?: string;\n}\n\n/**\n * Apply a factory project's main model to a session on every run.\n *\n * The model switch is best-effort. A retired model id must not sink a run that\n * is otherwise ready, and the reason is logged. Observational-memory settings are resolved from the\n * authoritative row per invocation before memory processors run.\n */\nexport async function hydrateFactorySession(\n session: FactorySession,\n args: HydrateFactorySessionArgs,\n): Promise<string | undefined> {\n // The org rung knowledge curation scopes on. Seeded first so it lands even if\n // a later best-effort step fails; an empty org marks the session unresolved.\n await seedSessionOrg(session, args.orgId);\n await session.state.set({\n factoryModelScope: args.modelScope,\n factoryOwnerUserId: args.ownerUserId,\n });\n\n let selectedModelId = args.defaultModelId;\n if (!selectedModelId) {\n const modeModelId = session.mode.resolve().defaultModelId;\n const providerModelId = await resolveTenantProviderDefaultModel(\n { orgId: args.orgId, userId: args.ownerUserId, credentialScope: args.modelScope },\n modeModelId,\n ).catch(error => {\n console.warn('[Factory Start] Failed to resolve a provider default model', {\n error: error instanceof Error ? error.message : String(error),\n });\n return undefined;\n });\n selectedModelId = providerModelId ?? modeModelId;\n }\n if (!selectedModelId) return undefined;\n\n let switched = true;\n try {\n await session.model.switch(selectedModelId);\n } catch (error) {\n switched = false;\n console.warn('[Factory Start] Failed to apply scoped default model', {\n modelId: selectedModelId,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n // Subagents otherwise keep the server-wide settings (or the SDK's built-in\n // default), which may name a provider this factory has no credentials for.\n // Each role is independent, so one failure doesn't strand the others.\n for (const agentType of ['explore', 'plan', 'execute']) {\n try {\n await session.subagents.model.set({ modelId: selectedModelId, agentType });\n } catch (error) {\n console.warn('[Factory Start] Failed to apply scoped default subagent model', {\n agentType,\n modelId: selectedModelId,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n // Callers pin this id to the thread, so only report a model the session actually runs on.\n return switched ? selectedModelId : undefined;\n}\n"],"mappings":";;;;;;AAkBA,IAAa,oCAAb,cAAuD,MAAM,CAAC;;AAG9D,eAAsB,2BAA2B,MAQkB;CACjE,IAAI,CAAC,KAAK,YAAY,CAAC,KAAK,kBAAkB,MAAM,IAAI,MAAM,sCAAsC;CAEpG,MAAM,UAAU,MAAM,KAAK,SAAS,QAAQ,EAAE,IAAI,KAAK,iBAAiB,CAAC;CACzE,IAAI,CAAC,WAAW,QAAQ,UAAU,KAAK,OAAO,MAAM,IAAI,MAAM,sCAAsC;CACpG,IAAI,oBAAoB,QAAQ,YAAY,KAAK,OAAO,MAAM,WAC5D,OAAO;EAAE,YAAY;EAAW,GAAI,QAAQ,iBAAiB,EAAE,SAAS,QAAQ,eAAe,IAAI,CAAC;CAAG;CAGzG,IAAI;EACF,MAAM,cAAc,MAAM,KAAK,eAAe,IAAI;GAAE,OAAO,KAAK;GAAO,QAAQ,KAAK;EAAY,CAAC;EACjG,OAAO;GAAE,YAAY;GAAQ,GAAI,aAAa,UAAU,EAAE,SAAS,YAAY,QAAQ,IAAI,CAAC;EAAG;CACjG,SAAS,OAAO;EACd,QAAQ,KAAK,6EAA6E;GACxF,OAAO,KAAK;GACZ,aAAa,KAAK;GAClB,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;EAC9D,CAAC;EACD,OAAO,EAAE,YAAY,OAAO;CAC9B;AACF;;;;;;AAiBA,SAAgB,iCACd,gBAC4B;CAC5B,MAAM,UAAU,OACd,cACiG;EACjG,MAAM,WACJ,MAAM,QAAQ,IACZ,eAAe,IAAI,OAAM,mBAAkB;GACzC;GACA,SAAS,MAAM,cAAc,SAAS,eAAe,SAAS;EAChE,EAAE,CACJ,EAAA,CACA,QACC,UACC,MAAM,YAAY,IACtB;EACA,IAAI,QAAQ,SAAS,GAAG,MAAM,IAAI,MAAM,8DAA8D;EACtG,OAAO,QAAQ,MAAM;CACvB;CAEA,OAAO;EACL,gBAAgB,OAAM,eAAc,MAAM,QAAQ,SAAS,EAAA,EAAI,WAAW;EAC1E,6BAA6B,OAAM,eAAc,MAAM,QAAQ,SAAS,EAAA,EAAI,iBAAiB;EAC7F,cAAc,OAAM,UACjB,MAAM,QAAQ,IAAI,eAAe,KAAI,kBAAiB,cAAc,SAAS,aAAa,IAAI,CAAC,CAAC,EAAA,CAAG,KAAK;EAC3G,QAAQ,OAAM,UAAS,MAAM,QAAQ,KAAK,SAAS,EAAA,EAAI,cAAc,SAAS,OAAO,IAAI;EACzF,kBAAkB,OAAM,UAAS,MAAM,QAAQ,KAAK,SAAS,EAAA,EAAI,cAAc,SAAS,iBAAiB,IAAI;EAC7G,yBAAyB,OAAM,UAC5B,MAAM,QAAQ,KAAK,SAAS,EAAA,EAAI,cAAc,SAAS,wBAAwB,IAAI;CACxF;AACF;;;;;AAMA,eAAsB,4BAA4B,MAKE;CAClD,IAAI,KAAK,WAAW;EAClB,MAAM,kBACJ,MAAM,QAAQ,IACZ,KAAK,eAAe,IAAI,OAAM,mBAAkB;GAC9C;GACA,SAAS,MAAM,cAAc,SAAS,eAAe,KAAK,SAAU;EACtE,EAAE,CACJ,EAAA,CACA,QAAO,UAAS,MAAM,YAAY,IAAI;EACxC,IAAI,eAAe,SAAS,GAAG,MAAM,IAAI,MAAM,8DAA8D;EAC7G,IAAI,eAAe,IAAI,OAAO,eAAe,EAAE,CAAC;CAClD;CAEA,MAAM,SAAS,CAAC;CAChB,KAAK,MAAM,iBAAiB,KAAK,gBAAgB;EAC/C,MAAM,cAAc,MAAM,cAAc,YAAY,KAAK;GACvD,OAAO,KAAK;GACZ,kBAAkB,KAAK;EACzB,CAAC;EACD,IAAI,sBAAsB;EAC1B,KAAK,MAAM,cAAc,aACvB,IAAI;GACF,KACG,MAAM,cAAc,oBAAoB,KAAK;IAAE,OAAO,KAAK;IAAO,cAAc,WAAW;GAAG,CAAC,EAAA,CAAG,SAAS,GAC5G;IACA,sBAAsB;IACtB;GACF;EACF,SAAS,OAAO;GACd,IAAI,EAAE,iBAAiB,uCAAuC,MAAM;EACtE;EAEF,IAAI,qBAAqB,OAAO,KAAK,aAAa;CACpD;CACA,IAAI,OAAO,SAAS,GAClB,MAAM,IAAI,kCACR,oFACF;CACF,OAAO,OAAO;AAChB;AAwCA,IAAa,sCAAb,cAAyD,MAAM;CACxC;CAArB,YAAY,QAA8C;EACxD,MACE,WAAW,eACP,iDACA,8CACN;EALmB,KAAA,SAAA;EAMnB,KAAK,OAAO;CACd;AACF;;;;;;;;;;;;;;AAkCA,eAAsB,oCAAoC,MAKf;CACzC,MAAM,EAAE,eAAe,OAAO,kBAAkB,wBAAwB;CAExE,IAAI,EAAC,MADqB,cAAc,YAAY,KAAK;EAAE;EAAO;CAAiB,CAAC,EAAA,CACnE,MAAK,cAAa,UAAU,kBAAkB,cAAc,aAAa,GACxF,OAAO;EAAE,OAAO;EAAO,QAAQ;CAAa;CAG9C,MAAM,WAAU,MADW,+BAA+B;EAAE;EAAe;EAAO;CAAiB,CAAC,EAAA,CACvE,MAC3B,cAAa,wBAAwB,KAAA,KAAa,UAAU,KAAK,OAAO,mBAC1E;CACA,IAAI,CAAC,SAAS,OAAO;EAAE,OAAO;EAAO,QAAQ;CAAa;CAC1D,OAAO;EACL,OAAO;EACP,qBAAqB,QAAQ,KAAK;EAClC,YAAY,QAAQ,KAAK,UAAU,QAAQ,WAAW;EACtD,mBAAmB,QAAQ,WAAW;CACxC;AACF;;;;;;;;;;;;;;;;;;AAmBA,eAAsB,2BACpB,MACsC;CACtC,MAAM,EAAE,eAAe,OAAO,kBAAkB,QAAQ,wBAAwB;CAEhF,MAAM,WAAW,MAAM,oCAAoC;EACzD;EACA;EACA;EACA;CACF,CAAC;CACD,IAAI,CAAC,SAAS,OAAO,MAAM,IAAI,oCAAoC,SAAS,MAAM;CAElF,MAAM,SAAS,KAAK,qBAAqB,SAAS;CAClD,MAAM,UAAU,MAAM,cAAc,SAAS,OAAO;EAClD,WAAW,KAAK,aAAa,WAAW,OAAO,WAAW;EAC1D,qBAAqB,SAAS;EAC9B;EACA;EACA;EACA;EACA,YAAY,SAAS;EACrB,YAAY,KAAK,cAAc;EAC/B,GAAI,KAAK,SAAS,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;CAC/C,CAAC;CACD,OAAO;EACL,WAAW,QAAQ;EACnB;EACA,qBAAqB,SAAS;EAC9B,QAAQ,QAAQ;EAChB,YAAY,SAAS;CACvB;AACF;;;;;;;;AAoBA,eAAsB,sBACpB,SACA,MAC6B;CAG7B,MAAM,eAAe,SAAS,KAAK,KAAK;CACxC,MAAM,QAAQ,MAAM,IAAI;EACtB,mBAAmB,KAAK;EACxB,oBAAoB,KAAK;CAC3B,CAAC;CAED,IAAI,kBAAkB,KAAK;CAC3B,IAAI,CAAC,iBAAiB;EACpB,MAAM,cAAc,QAAQ,KAAK,QAAQ,CAAC,CAAC;EAU3C,kBAAkB,MATY,kCAC5B;GAAE,OAAO,KAAK;GAAO,QAAQ,KAAK;GAAa,iBAAiB,KAAK;EAAW,GAChF,WACF,CAAC,CAAC,OAAM,UAAS;GACf,QAAQ,KAAK,8DAA8D,EACzE,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,EAC9D,CAAC;EAEH,CAAC,KACoC;CACvC;CACA,IAAI,CAAC,iBAAiB,OAAO,KAAA;CAE7B,IAAI,WAAW;CACf,IAAI;EACF,MAAM,QAAQ,MAAM,OAAO,eAAe;CAC5C,SAAS,OAAO;EACd,WAAW;EACX,QAAQ,KAAK,wDAAwD;GACnE,SAAS;GACT,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;EAC9D,CAAC;CACH;CAIA,KAAK,MAAM,aAAa;EAAC;EAAW;EAAQ;CAAS,GACnD,IAAI;EACF,MAAM,QAAQ,UAAU,MAAM,IAAI;GAAE,SAAS;GAAiB;EAAU,CAAC;CAC3E,SAAS,OAAO;EACd,QAAQ,KAAK,iEAAiE;GAC5E;GACA,SAAS;GACT,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;EAC9D,CAAC;CACH;CAGF,OAAO,WAAW,kBAAkB,KAAA;AACtC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';
|
|
2
2
|
import type { WorkItemRow } from '../storage/domains/work-items/base.js';
|
|
3
3
|
export type WorkItemRepositoryResolution = {
|
|
4
4
|
status: 'resolved';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"work-item-repository.d.ts","sourceRoot":"","sources":["../../src/session/work-item-repository.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"work-item-repository.d.ts","sourceRoot":"","sources":["../../src/session/work-item-repository.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,2CAA2C,CAAC;AAC5F,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uCAAuC,CAAC;AAEzE,MAAM,MAAM,4BAA4B,GACpC;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,mBAAmB,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACjE;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAkFlD,wBAAsB,yBAAyB,CAAC,IAAI,EAAE;IACpD,aAAa,EAAE,0BAA0B,CAAC;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,EAAE,MAAM,CAAC;IACzB,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;IACpC,mBAAmB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9C,GAAG,OAAO,CAAC,4BAA4B,CAAC,CA4CxC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"work-item-repository.js","names":[],"sources":["../../src/session/work-item-repository.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"work-item-repository.js","names":[],"sources":["../../src/session/work-item-repository.ts"],"sourcesContent":["import { SourceControlConnectionNotFoundError } from '../storage/domains/source-control/base.js';\nimport type { SourceControlStorageHandle } from '../storage/domains/source-control/base.js';\nimport type { WorkItemRow } from '../storage/domains/work-items/base.js';\n\nexport type WorkItemRepositoryResolution =\n | { status: 'resolved'; projectRepositoryId: string; slug: string }\n | { status: 'unlinked'; hint: string }\n | { status: 'ambiguous'; candidates: string[] };\n\ninterface LinkedRepository {\n projectRepositoryId: string;\n externalId: string;\n slug: string;\n inEnvironment: boolean;\n}\n\nasync function listLinkedRepositories(args: {\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n factoryProjectId: string;\n}): Promise<LinkedRepository[]> {\n const { sourceControl, orgId, factoryProjectId } = args;\n const connections = await sourceControl.connections.list({ orgId, factoryProjectId });\n const linked: LinkedRepository[] = [];\n\n for (const connection of connections.filter(candidate => candidate.integrationId === sourceControl.integrationId)) {\n try {\n const projectRepositories = await sourceControl.projectRepositories.list({ orgId, connectionId: connection.id });\n const repositories = await Promise.all(\n projectRepositories.map(async projectRepository => ({\n projectRepository,\n repository: await sourceControl.repositories.get({ orgId, id: projectRepository.repositoryId }),\n })),\n );\n for (const { projectRepository, repository } of repositories) {\n if (repository) {\n linked.push({\n projectRepositoryId: projectRepository.id,\n externalId: repository.externalId,\n slug: repository.slug,\n inEnvironment: projectRepository.inEnvironment,\n });\n }\n }\n } catch (error) {\n if (!(error instanceof SourceControlConnectionNotFoundError)) throw error;\n // A stale provider connection must not hide healthy linked repositories.\n }\n }\n\n return linked;\n}\n\nfunction getExternalRepositoryId(integrationId: string, metadata: Record<string, unknown>): string | undefined {\n let id: unknown;\n switch (integrationId) {\n case 'github':\n id = metadata.githubRepositoryId;\n break;\n case 'gitlab':\n id = metadata.gitlabProjectId;\n break;\n default:\n return undefined;\n }\n return id == null ? undefined : String(id);\n}\n\n/**\n * The one link among `matches` a run can use. A repository can be linked more\n * than once (a provider-app reinstall adds a second link for the same slug),\n * but only one of those links is in the environment, and only an environment\n * link can start a session, so it wins over its out-of-environment duplicates.\n */\nfunction pickLink(matches: LinkedRepository[]): LinkedRepository | undefined {\n if (matches.length === 1) return matches[0];\n const inEnvironment = matches.filter(repository => repository.inEnvironment);\n return inEnvironment.length === 1 ? inEnvironment[0] : undefined;\n}\n\nfunction findRepositoryBySlug(repositories: LinkedRepository[], slug: string) {\n const matches = repositories.filter(repository => repository.slug === slug);\n return pickLink(matches) ?? matches[0];\n}\n\nfunction resolvedRepository(repository: LinkedRepository): WorkItemRepositoryResolution {\n return { status: 'resolved', projectRepositoryId: repository.projectRepositoryId, slug: repository.slug };\n}\n\nexport async function resolveWorkItemRepository(args: {\n sourceControl: SourceControlStorageHandle;\n orgId: string;\n factoryProjectId: string;\n item: Pick<WorkItemRow, 'metadata'>;\n linearRepositoryMap?: Record<string, string>;\n}): Promise<WorkItemRepositoryResolution> {\n const { sourceControl, item, linearRepositoryMap } = args;\n const metadata = item.metadata ?? {};\n const linked = await listLinkedRepositories(args);\n const repositorySignal = typeof metadata.repository === 'string' ? metadata.repository : undefined;\n const externalRepositorySignal = getExternalRepositoryId(sourceControl.integrationId, metadata);\n const linearProjectId = typeof metadata.linearProjectId === 'string' ? metadata.linearProjectId : undefined;\n const mappedRepository = linearProjectId ? linearRepositoryMap?.[linearProjectId] : undefined;\n\n // The provider id survives repository renames, so it outranks a possibly stale slug.\n if (externalRepositorySignal !== undefined) {\n const match = pickLink(linked.filter(repository => repository.externalId === externalRepositorySignal));\n if (match) return resolvedRepository(match);\n }\n\n if (repositorySignal) {\n const match = findRepositoryBySlug(linked, repositorySignal);\n return match\n ? resolvedRepository(match)\n : { status: 'unlinked', hint: `Repository ${repositorySignal} is not linked to this Factory.` };\n }\n\n if (externalRepositorySignal !== undefined) {\n return {\n status: 'unlinked',\n hint: `Source-control repository ${externalRepositorySignal} is not linked to this Factory.`,\n };\n }\n\n if (mappedRepository) {\n const match = findRepositoryBySlug(linked, mappedRepository);\n return match\n ? resolvedRepository(match)\n : { status: 'unlinked', hint: `Mapped repository ${mappedRepository} is not linked to this Factory.` };\n }\n\n const candidates = [...new Set(linked.map(repository => repository.slug))].sort();\n if (candidates.length === 0) {\n return { status: 'unlinked', hint: 'This Factory has no linked source-control repositories.' };\n }\n // Every link shares the one slug here, so prefer the environment link among them.\n const onlyRepository = candidates.length === 1 ? (pickLink(linked) ?? linked[0]) : undefined;\n if (onlyRepository) return resolvedRepository(onlyRepository);\n return { status: 'ambiguous', candidates };\n}\n"],"mappings":"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"}
|
|
@@ -22,5 +22,9 @@ export declare class FilesystemStorage extends FactoryStorageDomain {
|
|
|
22
22
|
resourceId: string;
|
|
23
23
|
threadId: string;
|
|
24
24
|
}): Promise<number>;
|
|
25
|
+
/** Drop every thread's captured listing for a resource (e.g. its sandbox was recreated). */
|
|
26
|
+
deleteResourceFiles(args: {
|
|
27
|
+
resourceId: string;
|
|
28
|
+
}): Promise<number>;
|
|
25
29
|
}
|
|
26
30
|
//# sourceMappingURL=base.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/filesystem/base.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAI7D,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,2BAA2B;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,cAAc,EAAE,CAAC;CACzB;AAED,eAAO,MAAM,kBAAkB,EAAE,gBAAgB,EAiBhD,CAAC;AAuCF,qBAAa,iBAAkB,SAAQ,oBAAoB;;IAKnD,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAIrB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;IAIpC,YAAY,CAAC,KAAK,EAAE,2BAA2B,GAAG,OAAO,CAAC,IAAI,CAAC;IAY/D,SAAS,CAAC,IAAI,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IASpF,WAAW,CAAC,IAAI,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../../../src/storage/domains/filesystem/base.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAI7D,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,2BAA2B;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,cAAc,EAAE,CAAC;CACzB;AAED,eAAO,MAAM,kBAAkB,EAAE,gBAAgB,EAiBhD,CAAC;AAuCF,qBAAa,iBAAkB,SAAQ,oBAAoB;;IAKnD,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAIrB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;IAIpC,YAAY,CAAC,KAAK,EAAE,2BAA2B,GAAG,OAAO,CAAC,IAAI,CAAC;IAY/D,SAAS,CAAC,IAAI,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;IASpF,WAAW,CAAC,IAAI,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAKlF,4FAA4F;IACtF,mBAAmB,CAAC,IAAI,EAAE;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;CAIzE"}
|
|
@@ -67,6 +67,11 @@ var FilesystemStorage = class extends FactoryStorageDomain {
|
|
|
67
67
|
thread_id: args.threadId
|
|
68
68
|
});
|
|
69
69
|
}
|
|
70
|
+
/** Drop every thread's captured listing for a resource (e.g. its sandbox was recreated). */
|
|
71
|
+
async deleteResourceFiles(args) {
|
|
72
|
+
assertIdentifier(args.resourceId, "resourceId");
|
|
73
|
+
return this.ops.deleteMany(SNAPSHOTS, { resource_id: args.resourceId });
|
|
74
|
+
}
|
|
70
75
|
};
|
|
71
76
|
//#endregion
|
|
72
77
|
export { FILESYSTEM_SCHEMAS, FilesystemStorage };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base.js","names":[],"sources":["../../../../src/storage/domains/filesystem/base.ts"],"sourcesContent":["import { FactoryStorageDomain } from '@mastra/core/storage';\nimport type { CollectionSchema } from '@mastra/core/storage';\n\nconst SNAPSHOTS = 'filesystem_snapshots';\n\nexport interface FilesystemFile {\n path: string;\n}\n\nexport interface ReplaceFilesystemFilesInput {\n resourceId: string;\n threadId: string;\n files: FilesystemFile[];\n}\n\nexport const FILESYSTEM_SCHEMAS: CollectionSchema[] = [\n {\n name: SNAPSHOTS,\n columns: {\n id: { type: 'uuid-pk' },\n resource_id: { type: 'text' },\n thread_id: { type: 'text' },\n files: { type: 'json' },\n captured_at: { type: 'timestamp' },\n },\n uniqueIndexes: [\n {\n name: 'filesystem_snapshots_resource_thread_unique',\n columns: ['resource_id', 'thread_id'],\n },\n ],\n },\n];\n\ninterface FilesystemSnapshotDbRow extends Record<string, unknown> {\n id: string;\n resource_id: string;\n thread_id: string;\n files: FilesystemFile[];\n captured_at: Date;\n}\n\nfunction assertIdentifier(value: string, label: string): void {\n if (!value.trim()) throw new Error(`[FilesystemStorage] ${label} must not be empty.`);\n}\n\nfunction assertRelativePath(value: string): void {\n if (\n !value ||\n value.startsWith('/') ||\n value.split('/').some(segment => !segment || segment === '.' || segment === '..')\n ) {\n throw new Error('[FilesystemStorage] path must be a non-empty relative path.');\n }\n}\n\nfunction assertScope(args: { resourceId: string; threadId: string }): void {\n assertIdentifier(args.resourceId, 'resourceId');\n assertIdentifier(args.threadId, 'threadId');\n}\n\nfunction validateFiles(files: FilesystemFile[]): void {\n const paths = new Set<string>();\n\n for (const file of files) {\n assertRelativePath(file.path);\n if (paths.has(file.path)) throw new Error(`[FilesystemStorage] duplicate file path: ${file.path}`);\n paths.add(file.path);\n }\n}\n\nexport class FilesystemStorage extends FactoryStorageDomain {\n constructor() {\n super('filesystem');\n }\n\n async init(): Promise<void> {\n await this.ensureCollections(FILESYSTEM_SCHEMAS);\n }\n\n async dangerouslyClearAll(): Promise<void> {\n await this.ops.deleteMany(SNAPSHOTS, {});\n }\n\n async replaceFiles(input: ReplaceFilesystemFilesInput): Promise<void> {\n assertScope(input);\n validateFiles(input.files);\n\n await this.ops.upsertOne<FilesystemSnapshotDbRow>(SNAPSHOTS, ['resource_id', 'thread_id'], {\n resource_id: input.resourceId,\n thread_id: input.threadId,\n files: input.files.toSorted((a, b) => a.path.localeCompare(b.path)),\n captured_at: new Date(),\n });\n }\n\n async listFiles(args: { resourceId: string; threadId: string }): Promise<FilesystemFile[]> {\n assertScope(args);\n const snapshot = await this.ops.findOne<FilesystemSnapshotDbRow>(SNAPSHOTS, {\n resource_id: args.resourceId,\n thread_id: args.threadId,\n });\n return snapshot?.files ?? [];\n }\n\n async deleteFiles(args: { resourceId: string; threadId: string }): Promise<number> {\n assertScope(args);\n return this.ops.deleteMany(SNAPSHOTS, { resource_id: args.resourceId, thread_id: args.threadId });\n }\n}\n"],"mappings":";;AAGA,MAAM,YAAY;AAYlB,MAAa,qBAAyC,CACpD;CACE,MAAM;CACN,SAAS;EACP,IAAI,EAAE,MAAM,UAAU;EACtB,aAAa,EAAE,MAAM,OAAO;EAC5B,WAAW,EAAE,MAAM,OAAO;EAC1B,OAAO,EAAE,MAAM,OAAO;EACtB,aAAa,EAAE,MAAM,YAAY;CACnC;CACA,eAAe,CACb;EACE,MAAM;EACN,SAAS,CAAC,eAAe,WAAW;CACtC,CACF;AACF,CACF;AAUA,SAAS,iBAAiB,OAAe,OAAqB;CAC5D,IAAI,CAAC,MAAM,KAAK,GAAG,MAAM,IAAI,MAAM,uBAAuB,MAAM,oBAAoB;AACtF;AAEA,SAAS,mBAAmB,OAAqB;CAC/C,IACE,CAAC,SACD,MAAM,WAAW,GAAG,KACpB,MAAM,MAAM,GAAG,CAAC,CAAC,MAAK,YAAW,CAAC,WAAW,YAAY,OAAO,YAAY,IAAI,GAEhF,MAAM,IAAI,MAAM,6DAA6D;AAEjF;AAEA,SAAS,YAAY,MAAsD;CACzE,iBAAiB,KAAK,YAAY,YAAY;CAC9C,iBAAiB,KAAK,UAAU,UAAU;AAC5C;AAEA,SAAS,cAAc,OAA+B;CACpD,MAAM,wBAAQ,IAAI,IAAY;CAE9B,KAAK,MAAM,QAAQ,OAAO;EACxB,mBAAmB,KAAK,IAAI;EAC5B,IAAI,MAAM,IAAI,KAAK,IAAI,GAAG,MAAM,IAAI,MAAM,4CAA4C,KAAK,MAAM;EACjG,MAAM,IAAI,KAAK,IAAI;CACrB;AACF;AAEA,IAAa,oBAAb,cAAuC,qBAAqB;CAC1D,cAAc;EACZ,MAAM,YAAY;CACpB;CAEA,MAAM,OAAsB;EAC1B,MAAM,KAAK,kBAAkB,kBAAkB;CACjD;CAEA,MAAM,sBAAqC;EACzC,MAAM,KAAK,IAAI,WAAW,WAAW,CAAC,CAAC;CACzC;CAEA,MAAM,aAAa,OAAmD;EACpE,YAAY,KAAK;EACjB,cAAc,MAAM,KAAK;EAEzB,MAAM,KAAK,IAAI,UAAmC,WAAW,CAAC,eAAe,WAAW,GAAG;GACzF,aAAa,MAAM;GACnB,WAAW,MAAM;GACjB,OAAO,MAAM,MAAM,UAAU,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;GAClE,6BAAa,IAAI,KAAK;EACxB,CAAC;CACH;CAEA,MAAM,UAAU,MAA2E;EACzF,YAAY,IAAI;EAKhB,QAAO,MAJgB,KAAK,IAAI,QAAiC,WAAW;GAC1E,aAAa,KAAK;GAClB,WAAW,KAAK;EAClB,CAAC,EAAA,EACgB,SAAS,CAAC;CAC7B;CAEA,MAAM,YAAY,MAAiE;EACjF,YAAY,IAAI;EAChB,OAAO,KAAK,IAAI,WAAW,WAAW;GAAE,aAAa,KAAK;GAAY,WAAW,KAAK;EAAS,CAAC;CAClG;AACF"}
|
|
1
|
+
{"version":3,"file":"base.js","names":[],"sources":["../../../../src/storage/domains/filesystem/base.ts"],"sourcesContent":["import { FactoryStorageDomain } from '@mastra/core/storage';\nimport type { CollectionSchema } from '@mastra/core/storage';\n\nconst SNAPSHOTS = 'filesystem_snapshots';\n\nexport interface FilesystemFile {\n path: string;\n}\n\nexport interface ReplaceFilesystemFilesInput {\n resourceId: string;\n threadId: string;\n files: FilesystemFile[];\n}\n\nexport const FILESYSTEM_SCHEMAS: CollectionSchema[] = [\n {\n name: SNAPSHOTS,\n columns: {\n id: { type: 'uuid-pk' },\n resource_id: { type: 'text' },\n thread_id: { type: 'text' },\n files: { type: 'json' },\n captured_at: { type: 'timestamp' },\n },\n uniqueIndexes: [\n {\n name: 'filesystem_snapshots_resource_thread_unique',\n columns: ['resource_id', 'thread_id'],\n },\n ],\n },\n];\n\ninterface FilesystemSnapshotDbRow extends Record<string, unknown> {\n id: string;\n resource_id: string;\n thread_id: string;\n files: FilesystemFile[];\n captured_at: Date;\n}\n\nfunction assertIdentifier(value: string, label: string): void {\n if (!value.trim()) throw new Error(`[FilesystemStorage] ${label} must not be empty.`);\n}\n\nfunction assertRelativePath(value: string): void {\n if (\n !value ||\n value.startsWith('/') ||\n value.split('/').some(segment => !segment || segment === '.' || segment === '..')\n ) {\n throw new Error('[FilesystemStorage] path must be a non-empty relative path.');\n }\n}\n\nfunction assertScope(args: { resourceId: string; threadId: string }): void {\n assertIdentifier(args.resourceId, 'resourceId');\n assertIdentifier(args.threadId, 'threadId');\n}\n\nfunction validateFiles(files: FilesystemFile[]): void {\n const paths = new Set<string>();\n\n for (const file of files) {\n assertRelativePath(file.path);\n if (paths.has(file.path)) throw new Error(`[FilesystemStorage] duplicate file path: ${file.path}`);\n paths.add(file.path);\n }\n}\n\nexport class FilesystemStorage extends FactoryStorageDomain {\n constructor() {\n super('filesystem');\n }\n\n async init(): Promise<void> {\n await this.ensureCollections(FILESYSTEM_SCHEMAS);\n }\n\n async dangerouslyClearAll(): Promise<void> {\n await this.ops.deleteMany(SNAPSHOTS, {});\n }\n\n async replaceFiles(input: ReplaceFilesystemFilesInput): Promise<void> {\n assertScope(input);\n validateFiles(input.files);\n\n await this.ops.upsertOne<FilesystemSnapshotDbRow>(SNAPSHOTS, ['resource_id', 'thread_id'], {\n resource_id: input.resourceId,\n thread_id: input.threadId,\n files: input.files.toSorted((a, b) => a.path.localeCompare(b.path)),\n captured_at: new Date(),\n });\n }\n\n async listFiles(args: { resourceId: string; threadId: string }): Promise<FilesystemFile[]> {\n assertScope(args);\n const snapshot = await this.ops.findOne<FilesystemSnapshotDbRow>(SNAPSHOTS, {\n resource_id: args.resourceId,\n thread_id: args.threadId,\n });\n return snapshot?.files ?? [];\n }\n\n async deleteFiles(args: { resourceId: string; threadId: string }): Promise<number> {\n assertScope(args);\n return this.ops.deleteMany(SNAPSHOTS, { resource_id: args.resourceId, thread_id: args.threadId });\n }\n\n /** Drop every thread's captured listing for a resource (e.g. its sandbox was recreated). */\n async deleteResourceFiles(args: { resourceId: string }): Promise<number> {\n assertIdentifier(args.resourceId, 'resourceId');\n return this.ops.deleteMany(SNAPSHOTS, { resource_id: args.resourceId });\n }\n}\n"],"mappings":";;AAGA,MAAM,YAAY;AAYlB,MAAa,qBAAyC,CACpD;CACE,MAAM;CACN,SAAS;EACP,IAAI,EAAE,MAAM,UAAU;EACtB,aAAa,EAAE,MAAM,OAAO;EAC5B,WAAW,EAAE,MAAM,OAAO;EAC1B,OAAO,EAAE,MAAM,OAAO;EACtB,aAAa,EAAE,MAAM,YAAY;CACnC;CACA,eAAe,CACb;EACE,MAAM;EACN,SAAS,CAAC,eAAe,WAAW;CACtC,CACF;AACF,CACF;AAUA,SAAS,iBAAiB,OAAe,OAAqB;CAC5D,IAAI,CAAC,MAAM,KAAK,GAAG,MAAM,IAAI,MAAM,uBAAuB,MAAM,oBAAoB;AACtF;AAEA,SAAS,mBAAmB,OAAqB;CAC/C,IACE,CAAC,SACD,MAAM,WAAW,GAAG,KACpB,MAAM,MAAM,GAAG,CAAC,CAAC,MAAK,YAAW,CAAC,WAAW,YAAY,OAAO,YAAY,IAAI,GAEhF,MAAM,IAAI,MAAM,6DAA6D;AAEjF;AAEA,SAAS,YAAY,MAAsD;CACzE,iBAAiB,KAAK,YAAY,YAAY;CAC9C,iBAAiB,KAAK,UAAU,UAAU;AAC5C;AAEA,SAAS,cAAc,OAA+B;CACpD,MAAM,wBAAQ,IAAI,IAAY;CAE9B,KAAK,MAAM,QAAQ,OAAO;EACxB,mBAAmB,KAAK,IAAI;EAC5B,IAAI,MAAM,IAAI,KAAK,IAAI,GAAG,MAAM,IAAI,MAAM,4CAA4C,KAAK,MAAM;EACjG,MAAM,IAAI,KAAK,IAAI;CACrB;AACF;AAEA,IAAa,oBAAb,cAAuC,qBAAqB;CAC1D,cAAc;EACZ,MAAM,YAAY;CACpB;CAEA,MAAM,OAAsB;EAC1B,MAAM,KAAK,kBAAkB,kBAAkB;CACjD;CAEA,MAAM,sBAAqC;EACzC,MAAM,KAAK,IAAI,WAAW,WAAW,CAAC,CAAC;CACzC;CAEA,MAAM,aAAa,OAAmD;EACpE,YAAY,KAAK;EACjB,cAAc,MAAM,KAAK;EAEzB,MAAM,KAAK,IAAI,UAAmC,WAAW,CAAC,eAAe,WAAW,GAAG;GACzF,aAAa,MAAM;GACnB,WAAW,MAAM;GACjB,OAAO,MAAM,MAAM,UAAU,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;GAClE,6BAAa,IAAI,KAAK;EACxB,CAAC;CACH;CAEA,MAAM,UAAU,MAA2E;EACzF,YAAY,IAAI;EAKhB,QAAO,MAJgB,KAAK,IAAI,QAAiC,WAAW;GAC1E,aAAa,KAAK;GAClB,WAAW,KAAK;EAClB,CAAC,EAAA,EACgB,SAAS,CAAC;CAC7B;CAEA,MAAM,YAAY,MAAiE;EACjF,YAAY,IAAI;EAChB,OAAO,KAAK,IAAI,WAAW,WAAW;GAAE,aAAa,KAAK;GAAY,WAAW,KAAK;EAAS,CAAC;CAClG;;CAGA,MAAM,oBAAoB,MAA+C;EACvE,iBAAiB,KAAK,YAAY,YAAY;EAC9C,OAAO,KAAK,IAAI,WAAW,WAAW,EAAE,aAAa,KAAK,WAAW,CAAC;CACxE;AACF"}
|