@mastra/factory 0.11.1 → 0.12.0-alpha.1
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/CHANGELOG.md +59 -0
- package/dist/factory.d.ts +2 -22
- package/dist/factory.d.ts.map +1 -1
- package/dist/factory.js +21 -59
- package/dist/factory.js.map +1 -1
- package/dist/feed-events.d.ts +13 -0
- package/dist/feed-events.d.ts.map +1 -0
- package/dist/feed-events.js +25 -0
- package/dist/feed-events.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/integrations/base.d.ts +5 -13
- package/dist/integrations/base.d.ts.map +1 -1
- package/dist/integrations/github/config.d.ts +4 -4
- package/dist/integrations/github/config.d.ts.map +1 -1
- package/dist/integrations/github/config.js +3 -3
- package/dist/integrations/github/config.js.map +1 -1
- package/dist/integrations/github/integration.d.ts.map +1 -1
- package/dist/integrations/github/integration.js +2 -4
- package/dist/integrations/github/integration.js.map +1 -1
- package/dist/integrations/github/reconcile-worker.d.ts +0 -5
- package/dist/integrations/github/reconcile-worker.d.ts.map +1 -1
- package/dist/integrations/github/reconcile-worker.js +1 -8
- package/dist/integrations/github/reconcile-worker.js.map +1 -1
- package/dist/integrations/github/routes.d.ts +6 -5
- package/dist/integrations/github/routes.d.ts.map +1 -1
- package/dist/integrations/github/routes.js +36 -263
- package/dist/integrations/github/routes.js.map +1 -1
- package/dist/integrations/github/sandbox-release.d.ts +17 -47
- package/dist/integrations/github/sandbox-release.d.ts.map +1 -1
- package/dist/integrations/github/sandbox-release.js +24 -103
- package/dist/integrations/github/sandbox-release.js.map +1 -1
- package/dist/integrations/github/sandbox.d.ts +25 -139
- package/dist/integrations/github/sandbox.d.ts.map +1 -1
- package/dist/integrations/github/sandbox.js +24 -212
- package/dist/integrations/github/sandbox.js.map +1 -1
- package/dist/integrations/github/webhook.d.ts.map +1 -1
- package/dist/integrations/github/webhook.js +1 -2
- package/dist/integrations/github/webhook.js.map +1 -1
- package/dist/integrations/issue-reconcile-worker.js +1 -1
- package/dist/integrations/platform/github/event-worker.d.ts +0 -2
- package/dist/integrations/platform/github/event-worker.d.ts.map +1 -1
- package/dist/integrations/platform/github/event-worker.js +1 -8
- package/dist/integrations/platform/github/event-worker.js.map +1 -1
- package/dist/integrations/platform/github/integration.d.ts.map +1 -1
- package/dist/integrations/platform/github/integration.js +5 -7
- package/dist/integrations/platform/github/integration.js.map +1 -1
- package/dist/integrations/platform/linear/event-worker.js +1 -1
- package/dist/routes/attention-activity.d.ts +24 -0
- package/dist/routes/attention-activity.d.ts.map +1 -0
- package/dist/routes/attention-activity.js +143 -0
- package/dist/routes/attention-activity.js.map +1 -0
- package/dist/routes/attention-providers.d.ts +31 -1
- package/dist/routes/attention-providers.d.ts.map +1 -1
- package/dist/routes/attention-providers.js +4 -4
- package/dist/routes/attention-providers.js.map +1 -1
- package/dist/routes/attention.d.ts.map +1 -1
- package/dist/routes/attention.js +39 -11
- package/dist/routes/attention.js.map +1 -1
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/config.js +3 -1
- package/dist/routes/config.js.map +1 -1
- package/dist/routes/fs.d.ts +4 -6
- package/dist/routes/fs.d.ts.map +1 -1
- package/dist/routes/fs.js +28 -34
- package/dist/routes/fs.js.map +1 -1
- package/dist/routes/skills.d.ts.map +1 -1
- package/dist/routes/skills.js +6 -3
- package/dist/routes/skills.js.map +1 -1
- package/dist/routes/surface.d.ts +4 -9
- package/dist/routes/surface.d.ts.map +1 -1
- package/dist/routes/surface.js +3 -5
- package/dist/routes/surface.js.map +1 -1
- package/dist/sandbox/materialization.d.ts +36 -0
- package/dist/sandbox/materialization.d.ts.map +1 -0
- package/dist/sandbox/materialization.js +18 -0
- package/dist/sandbox/materialization.js.map +1 -0
- package/dist/sandbox/session-retirement.d.ts +8 -6
- package/dist/sandbox/session-retirement.d.ts.map +1 -1
- package/dist/sandbox/session-retirement.js +28 -123
- package/dist/sandbox/session-retirement.js.map +1 -1
- package/dist/sandbox/session-sandbox.d.ts +117 -0
- package/dist/sandbox/session-sandbox.d.ts.map +1 -0
- package/dist/sandbox/session-sandbox.js +132 -0
- package/dist/sandbox/session-sandbox.js.map +1 -0
- package/dist/sandbox/workdir.d.ts +30 -0
- package/dist/sandbox/workdir.d.ts.map +1 -0
- package/dist/sandbox/workdir.js +48 -0
- package/dist/sandbox/workdir.js.map +1 -0
- package/dist/session/filesystem-capture.d.ts +2 -1
- package/dist/session/filesystem-capture.d.ts.map +1 -1
- package/dist/session/filesystem-capture.js +45 -4
- package/dist/session/filesystem-capture.js.map +1 -1
- package/dist/session/first-exec-capture.d.ts +1 -0
- package/dist/session/first-exec-capture.d.ts.map +1 -1
- package/dist/session/first-exec-capture.js +1 -1
- package/dist/session/first-exec-capture.js.map +1 -1
- package/dist/storage/domains/comments/activity.d.ts +40 -0
- package/dist/storage/domains/comments/activity.d.ts.map +1 -0
- package/dist/storage/domains/comments/activity.js +94 -0
- package/dist/storage/domains/comments/activity.js.map +1 -0
- package/dist/storage/domains/comments/base.d.ts +13 -1
- package/dist/storage/domains/comments/base.d.ts.map +1 -1
- package/dist/storage/domains/comments/base.js +41 -3
- package/dist/storage/domains/comments/base.js.map +1 -1
- package/dist/storage/domains/comments/domain.d.ts +4 -1
- package/dist/storage/domains/comments/domain.d.ts.map +1 -1
- package/dist/storage/domains/comments/domain.js +17 -5
- package/dist/storage/domains/comments/domain.js.map +1 -1
- package/dist/storage/domains/comments/parse.d.ts +1 -0
- package/dist/storage/domains/comments/parse.d.ts.map +1 -1
- package/dist/storage/domains/comments/parse.js +2 -2
- package/dist/storage/domains/comments/parse.js.map +1 -1
- package/dist/storage/domains/comments/routes.d.ts +2 -0
- package/dist/storage/domains/comments/routes.d.ts.map +1 -1
- package/dist/storage/domains/comments/routes.js +49 -3
- package/dist/storage/domains/comments/routes.js.map +1 -1
- package/dist/storage/domains/comments/schema.d.ts +1 -0
- package/dist/storage/domains/comments/schema.d.ts.map +1 -1
- package/dist/storage/domains/comments/schema.js +45 -1
- package/dist/storage/domains/comments/schema.js.map +1 -1
- package/dist/storage/domains/integrations/base.d.ts +1 -1
- package/dist/storage/domains/integrations/base.js +1 -1
- package/dist/storage/domains/integrations/base.js.map +1 -1
- package/dist/storage/domains/source-control/base.d.ts +0 -144
- package/dist/storage/domains/source-control/base.d.ts.map +1 -1
- package/dist/storage/domains/source-control/base.js +3 -290
- package/dist/storage/domains/source-control/base.js.map +1 -1
- package/dist/storage/domains/source-control/inmemory.d.ts +1 -63
- package/dist/storage/domains/source-control/inmemory.d.ts.map +1 -1
- package/dist/storage/domains/source-control/inmemory.js +0 -89
- package/dist/storage/domains/source-control/inmemory.js.map +1 -1
- package/dist/storage/domains/work-items/base.d.ts +14 -1
- package/dist/storage/domains/work-items/base.d.ts.map +1 -1
- package/dist/storage/domains/work-items/base.js +60 -11
- package/dist/storage/domains/work-items/base.js.map +1 -1
- package/dist/workspace.d.ts +5 -37
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +107 -245
- package/dist/workspace.js.map +1 -1
- package/package.json +6 -6
- package/dist/sandbox/base-checkpoint-triggers.d.ts +0 -77
- package/dist/sandbox/base-checkpoint-triggers.d.ts.map +0 -1
- package/dist/sandbox/base-checkpoint-triggers.js +0 -137
- package/dist/sandbox/base-checkpoint-triggers.js.map +0 -1
- package/dist/sandbox/base-checkpoint.d.ts +0 -59
- package/dist/sandbox/base-checkpoint.d.ts.map +0 -1
- package/dist/sandbox/base-checkpoint.js +0 -145
- package/dist/sandbox/base-checkpoint.js.map +0 -1
- package/dist/sandbox/fleet.d.ts +0 -258
- package/dist/sandbox/fleet.d.ts.map +0 -1
- package/dist/sandbox/fleet.js +0 -356
- package/dist/sandbox/fleet.js.map +0 -1
- package/dist/sandbox/reattach.d.ts +0 -3
- package/dist/sandbox/reattach.d.ts.map +0 -1
- package/dist/sandbox/reattach.js +0 -16
- package/dist/sandbox/reattach.js.map +0 -1
|
@@ -1,58 +1,6 @@
|
|
|
1
1
|
import { timedPhase } from "../../timing.js";
|
|
2
|
-
import { reportProgress } from "../../sandbox/fleet.js";
|
|
3
|
-
import { createHash } from "crypto";
|
|
4
2
|
//#region src/integrations/github/sandbox.ts
|
|
5
3
|
/**
|
|
6
|
-
* Repo materialization for GitHub-backed repositories.
|
|
7
|
-
*
|
|
8
|
-
* A GitHub repo is never cloned onto the server host. Instead each project gets
|
|
9
|
-
* its own isolated sandbox (provisioned by the fleet in `../sandbox/fleet`) and
|
|
10
|
-
* the repo is cloned *inside* that sandbox. The agent's file tools and command
|
|
11
|
-
* tools then operate entirely against the remote checkout.
|
|
12
|
-
*
|
|
13
|
-
* - `ensureProjectSandbox(row)` / `teardownProjectSandbox(row)` bind the fleet's
|
|
14
|
-
* provision/reattach/teardown lifecycle to the per-(project,user) sandbox row.
|
|
15
|
-
* - `materializeRepo(row, token)` runs `git clone` (first open) or `git pull`
|
|
16
|
-
* (re-open) inside the sandbox, using a short-lived installation token that is
|
|
17
|
-
* scrubbed from the git remote afterwards so it never persists in the VM.
|
|
18
|
-
*
|
|
19
|
-
* This module owns everything git/GitHub: clone/pull, commit/push, worktrees,
|
|
20
|
-
* and `gh pr create`. Sandbox provisioning, budgets, and workdir layout live in
|
|
21
|
-
* the fleet module.
|
|
22
|
-
*/
|
|
23
|
-
/** Adapt a per-(project,user) sandbox binding row to the fleet's persistence seam. */
|
|
24
|
-
function bindingStore(row, storage, seedCheckpointName) {
|
|
25
|
-
return {
|
|
26
|
-
sandboxId: row.sandboxId,
|
|
27
|
-
...seedCheckpointName ? { seedCheckpointName } : {},
|
|
28
|
-
setSandboxId: (id) => id === null ? storage.clearBinding({ id: row.id }) : storage.setSandboxId({
|
|
29
|
-
id: row.id,
|
|
30
|
-
sandboxId: id
|
|
31
|
-
}),
|
|
32
|
-
clear: () => storage.clearBinding({ id: row.id })
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
/**
|
|
36
|
-
* Provision a new sandbox (persisting its provider id on first open) or
|
|
37
|
-
* reattach to the stored one. Returns a started, live sandbox.
|
|
38
|
-
*/
|
|
39
|
-
async function ensureProjectSandbox(options) {
|
|
40
|
-
const { fleet, row, storage, token, onProgress, seedCheckpointName } = options;
|
|
41
|
-
return fleet.ensureSandbox(bindingStore(row, storage, seedCheckpointName), { GH_TOKEN: token }, onProgress, { actingUserId: row.userId });
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* Tear down a user's sandbox for a project: stop the live VM (best-effort) and
|
|
45
|
-
* clear the persisted `sandboxId`/`materializedAt` on the per-(project,user)
|
|
46
|
-
* binding row so the next open re-provisions cleanly.
|
|
47
|
-
*
|
|
48
|
-
* @param row the per-(project,user) sandbox binding to tear down
|
|
49
|
-
* @param sandbox an already-reattached live sandbox to stop, when available
|
|
50
|
-
*/
|
|
51
|
-
async function teardownProjectSandbox(options) {
|
|
52
|
-
const { fleet, row, storage, sandbox } = options;
|
|
53
|
-
return fleet.teardownSandbox(bindingStore(row, storage), sandbox);
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
4
|
* Single-quote a string for safe POSIX shell interpolation. Wraps the value in
|
|
57
5
|
* single quotes and escapes any embedded single quote using the canonical
|
|
58
6
|
* close-quote / escaped-quote / reopen-quote sequence (`'\''`). This is the
|
|
@@ -190,13 +138,12 @@ function cleanUrl(repoFullName) {
|
|
|
190
138
|
* Materialize the repo inside the user's sandbox. Clones on first open, pulls on
|
|
191
139
|
* re-open. Always scrubs the install token from the remote afterwards and sets
|
|
192
140
|
* `materialized_at` on the per-user sandbox binding row.
|
|
193
|
-
*
|
|
194
141
|
*/
|
|
195
142
|
async function materializeRepo(options) {
|
|
196
143
|
return timedPhase("workspace.materialize", () => materializeRepoImpl(options));
|
|
197
144
|
}
|
|
198
145
|
async function materializeRepoImpl(options) {
|
|
199
|
-
const { row: sandboxRow, repoInfo, sandbox, token, storage
|
|
146
|
+
const { row: sandboxRow, repoInfo, sandbox, token, storage } = options;
|
|
200
147
|
const workdir = sandboxRow.sandboxWorkdir;
|
|
201
148
|
const repo = repoInfo.repoFullName;
|
|
202
149
|
if (!/^[\w.-]+\/[\w.-]+$/.test(repo)) throw new MaterializeError(`Refusing to materialize: invalid repo full name '${repo}'.`, "clone-failed");
|
|
@@ -207,18 +154,10 @@ async function materializeRepoImpl(options) {
|
|
|
207
154
|
let tokenInRemote = false;
|
|
208
155
|
try {
|
|
209
156
|
if (!alreadyMaterialized) {
|
|
210
|
-
reportProgress(onProgress, {
|
|
211
|
-
phase: "cloning",
|
|
212
|
-
message: `Cloning ${repo} (first open can take a minute)…`
|
|
213
|
-
});
|
|
214
157
|
await sh(sandbox, `mkdir -p ${shellQuote(workdir)} && find ${shellQuote(workdir)} -mindepth 1 -maxdepth 1 -exec rm -rf -- {} +`);
|
|
215
158
|
const clone = await gitTransfer(sandbox, `git clone --depth=1 --single-branch --branch ${shellQuote(repoInfo.defaultBranch)} ${shellQuote(authUrl)} ${shellQuote(workdir)}`, {
|
|
216
159
|
phase: "repository clone",
|
|
217
|
-
beforeRetry: async (
|
|
218
|
-
reportProgress(onProgress, {
|
|
219
|
-
phase: "cloning",
|
|
220
|
-
message: `Lost the connection to github.com — retrying the clone (attempt ${attempt + 1})…`
|
|
221
|
-
});
|
|
160
|
+
beforeRetry: async () => {
|
|
222
161
|
await sh(sandbox, `mkdir -p ${shellQuote(workdir)} && find ${shellQuote(workdir)} -mindepth 1 -maxdepth 1 -exec rm -rf -- {} +`);
|
|
223
162
|
}
|
|
224
163
|
});
|
|
@@ -227,68 +166,21 @@ async function materializeRepoImpl(options) {
|
|
|
227
166
|
throw classifyGitFailure(clone, "clone-failed");
|
|
228
167
|
}
|
|
229
168
|
tokenInRemote = true;
|
|
230
|
-
} else if (skipPullOnExistingCheckout) {
|
|
231
|
-
const setUrl = await sh(sandbox, `git -C ${shellQuote(workdir)} remote set-url origin ${shellQuote(authUrl)}`);
|
|
232
|
-
if (setUrl.exitCode !== 0) throw new MaterializeError(`Failed to set git remote: ${setUrl.stderr}`, "pull-failed");
|
|
233
|
-
tokenInRemote = true;
|
|
234
169
|
} else {
|
|
235
|
-
reportProgress(onProgress, {
|
|
236
|
-
phase: "pulling",
|
|
237
|
-
message: `Updating ${repo} to the latest changes…`
|
|
238
|
-
});
|
|
239
170
|
const setUrl = await sh(sandbox, `git -C ${shellQuote(workdir)} remote set-url origin ${shellQuote(authUrl)}`);
|
|
240
171
|
if (setUrl.exitCode !== 0) throw new MaterializeError(`Failed to set git remote: ${setUrl.stderr}`, "pull-failed");
|
|
241
172
|
tokenInRemote = true;
|
|
242
|
-
const pull = await gitTransfer(sandbox, `git -C ${shellQuote(workdir)} pull --ff-only`, {
|
|
243
|
-
phase: "repository pull",
|
|
244
|
-
beforeRetry: async (attempt) => reportProgress(onProgress, {
|
|
245
|
-
phase: "pulling",
|
|
246
|
-
message: `Lost the connection to github.com — retrying the update (attempt ${attempt + 1})…`
|
|
247
|
-
})
|
|
248
|
-
});
|
|
173
|
+
const pull = await gitTransfer(sandbox, `git -C ${shellQuote(workdir)} pull --ff-only`, { phase: "repository pull" });
|
|
249
174
|
if (pull.exitCode !== 0) {
|
|
250
175
|
if (!isBenignNonFastForward(pull)) throw classifyGitFailure(pull, "pull-failed");
|
|
251
|
-
reportProgress(onProgress, {
|
|
252
|
-
phase: "pulling",
|
|
253
|
-
message: isDeletedUpstreamRef(pull) ? "Workspace could not be updated from its remote — keeping the existing checkout as-is." : "Workspace has local changes that diverge from the remote — keeping them as-is."
|
|
254
|
-
});
|
|
255
176
|
}
|
|
256
177
|
}
|
|
257
178
|
} catch (primary) {
|
|
258
179
|
throw await scrubbedFailure(sandbox, workdir, repo, tokenInRemote, primary, "pull-failed");
|
|
259
180
|
}
|
|
260
181
|
await scrubRemote(sandbox, workdir, repo, tokenInRemote);
|
|
261
|
-
reportProgress(onProgress, {
|
|
262
|
-
phase: "finalizing",
|
|
263
|
-
message: "Finalizing workspace…"
|
|
264
|
-
});
|
|
265
182
|
await storage.markMaterialized({ id: sandboxRow.id });
|
|
266
183
|
}
|
|
267
|
-
/**
|
|
268
|
-
* Reset a pooled workdir that a new session just claimed: the previous
|
|
269
|
-
* session's branch and dirty state must not leak into the new session, so
|
|
270
|
-
* force-checkout the default branch, drop all local modifications, and delete
|
|
271
|
-
* every other local branch (a surviving branch would otherwise hand the next
|
|
272
|
-
* session for the same branch the previous session's stale tip instead of a
|
|
273
|
-
* fresh fork from the base branch). When the claimed VM was reaped and
|
|
274
|
-
* re-provisioned there is no checkout yet and this is a no-op (the clone path
|
|
275
|
-
* handles it). A wedged checkout falls back to wiping the workdir so
|
|
276
|
-
* `materializeRepo` re-clones inside the same VM instead of permanently
|
|
277
|
-
* failing the session.
|
|
278
|
-
*/
|
|
279
|
-
async function recycleClaimedWorkdir(sandbox, workdir, defaultBranch) {
|
|
280
|
-
if (!/^[A-Za-z0-9_./-]+$/.test(defaultBranch)) throw new MaterializeError(`Refusing to recycle: invalid default branch '${defaultBranch}'.`, "clone-failed");
|
|
281
|
-
const w = shellQuote(workdir);
|
|
282
|
-
if ((await sh(sandbox, `git -C ${w} rev-parse --is-inside-work-tree`)).exitCode !== 0) return;
|
|
283
|
-
const recycle = await sh(sandbox, `git -C ${w} checkout -f ${shellQuote(defaultBranch)} && git -C ${w} reset --hard && git -C ${w} clean -fdx`, { phase: "claimed workdir recycle" });
|
|
284
|
-
let failure = recycle;
|
|
285
|
-
if (recycle.exitCode === 0) {
|
|
286
|
-
const sweep = await sh(sandbox, `set -e; for ref in $(git -C ${w} for-each-ref --format='%(refname)' refs/heads); do [ "$ref" = ${shellQuote(`refs/heads/${defaultBranch}`)} ] || git -C ${w} update-ref --no-deref -d "$ref"; done`, { phase: "claimed workdir branch sweep" });
|
|
287
|
-
if (sweep.exitCode === 0) return;
|
|
288
|
-
failure = sweep;
|
|
289
|
-
}
|
|
290
|
-
if ((await sh(sandbox, `rm -rf ${w}`)).exitCode !== 0) throw new MaterializeError(`Failed to recycle claimed sandbox workdir: ${failure.stderr}`, "clone-failed");
|
|
291
|
-
}
|
|
292
184
|
/** Check out a session's branch inside its isolated repository clone. */
|
|
293
185
|
async function checkoutSessionBranch(sandbox, workdir, options) {
|
|
294
186
|
return timedPhase("workspace.checkout", () => checkoutSessionBranchImpl(sandbox, workdir, options));
|
|
@@ -521,7 +413,7 @@ async function pushBranch(sandbox, workdir, branch, token, repoFullName) {
|
|
|
521
413
|
* error, so callers can safely commit-then-push without first diffing.
|
|
522
414
|
*
|
|
523
415
|
* @param sandbox the live sandbox containing the checkout
|
|
524
|
-
* @param workdir the
|
|
416
|
+
* @param workdir the session workdir to commit in
|
|
525
417
|
* @param message the commit message (quoted; arbitrary text is safe)
|
|
526
418
|
* @param identity authorship identity for the commit
|
|
527
419
|
*/
|
|
@@ -534,81 +426,21 @@ async function commitAll(sandbox, workdir, message, identity) {
|
|
|
534
426
|
if (commit.exitCode !== 0) throw new MaterializeError(`git commit failed: ${commit.stderr.trim() || commit.stdout.trim()}`, "commit-failed");
|
|
535
427
|
return { committed: true };
|
|
536
428
|
}
|
|
537
|
-
/** Error raised when
|
|
538
|
-
var
|
|
429
|
+
/** Error raised when the org's setup or teardown command fails in the sandbox. */
|
|
430
|
+
var SetupCommandError = class extends Error {
|
|
539
431
|
code;
|
|
540
432
|
constructor(message, code) {
|
|
541
433
|
super(message);
|
|
542
434
|
this.code = code;
|
|
543
|
-
this.name = "
|
|
435
|
+
this.name = "SetupCommandError";
|
|
544
436
|
}
|
|
545
437
|
};
|
|
546
438
|
/**
|
|
547
|
-
*
|
|
548
|
-
*
|
|
549
|
-
*
|
|
550
|
-
*
|
|
551
|
-
*
|
|
552
|
-
* so an 8-char hash of the original branch is appended whenever the sanitized
|
|
553
|
-
* form differs from the input. That keeps clean names (`main`) readable while
|
|
554
|
-
* guaranteeing distinct branches never share a worktree directory.
|
|
555
|
-
*/
|
|
556
|
-
function safeBranchDir(branch) {
|
|
557
|
-
const sanitized = branch.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/\/+/g, "-").replace(/^[-.]+|[-.]+$/g, "").slice(0, 100) || "work";
|
|
558
|
-
if (sanitized === branch) return sanitized;
|
|
559
|
-
return `${sanitized}-${createHash("sha256").update(branch).digest("hex").slice(0, 8)}`;
|
|
560
|
-
}
|
|
561
|
-
/**
|
|
562
|
-
* Compute the absolute worktree path for a branch, server-side only. Worktrees
|
|
563
|
-
* live alongside the repo checkout under a sibling `worktrees/` directory so the
|
|
564
|
-
* repo's `.git` is shared. Never derived from client-supplied paths.
|
|
565
|
-
*/
|
|
566
|
-
function computeWorktreePath(repoWorkdir, branch) {
|
|
567
|
-
return `${repoWorkdir.replace(/\/+$/, "").split("/").slice(0, -1).join("/") || ""}/worktrees/${safeBranchDir(branch)}`;
|
|
568
|
-
}
|
|
569
|
-
/**
|
|
570
|
-
* Create (or reuse) a git worktree + branch inside the sandbox for a unit of
|
|
571
|
-
* work. Idempotent: if a worktree already exists at the computed path it is
|
|
572
|
-
* reused. The branch is created from the freshly fetched `origin/<baseBranch>`
|
|
573
|
-
* — never the sandbox's possibly stale local ref — so new worktrees always
|
|
574
|
-
* start from the latest remote state.
|
|
575
|
-
*
|
|
576
|
-
* @param sandbox live sandbox containing the base checkout
|
|
577
|
-
* @param repoWorkdir the base repo checkout path inside the sandbox
|
|
578
|
-
* @param branch the feature branch (ref-validated server-side)
|
|
579
|
-
* @param baseBranch the branch to fork from (ref-validated; defaults to the repo's default branch)
|
|
580
|
-
* @param token short-lived installation token used only for the base-branch fetch
|
|
581
|
-
* @param repoFullName `owner/repo` used to build the tokenized remote URL
|
|
582
|
-
*/
|
|
583
|
-
async function ensureWorktree(sandbox, repoWorkdir, { branch, baseBranch, token, repoFullName }) {
|
|
584
|
-
if (!isValidGitRef(branch)) throw new WorktreeError(`Invalid branch name '${branch}'.`, "invalid-branch");
|
|
585
|
-
if (!isValidGitRef(baseBranch)) throw new WorktreeError(`Invalid base branch name '${baseBranch}'.`, "invalid-branch");
|
|
586
|
-
const worktreePath = computeWorktreePath(repoWorkdir, branch);
|
|
587
|
-
if ((await sh(sandbox, `test -e ${shellQuote(`${worktreePath}/.git`)}`)).exitCode === 0) return {
|
|
588
|
-
worktreePath,
|
|
589
|
-
branch,
|
|
590
|
-
baseBranch,
|
|
591
|
-
reused: true
|
|
592
|
-
};
|
|
593
|
-
const baseRef = `origin/${baseBranch}`;
|
|
594
|
-
await withInstallToken(sandbox, repoWorkdir, repoFullName, token, async () => {
|
|
595
|
-
const fetch = await sh(sandbox, `git -C ${shellQuote(repoWorkdir)} fetch origin ${shellQuote(`+refs/heads/${baseBranch}:refs/remotes/${baseRef}`)}`);
|
|
596
|
-
if (fetch.exitCode !== 0) throw classifyGitFailure(fetch, "pull-failed");
|
|
597
|
-
});
|
|
598
|
-
const add = await sh(sandbox, `git -C ${shellQuote(repoWorkdir)} worktree add --no-track -B ${shellQuote(branch)} ${shellQuote(worktreePath)} ${shellQuote(baseRef)}`);
|
|
599
|
-
if (add.exitCode !== 0) throw new WorktreeError(`git worktree add failed: ${add.stderr.trim() || add.stdout.trim()}`, "worktree-failed");
|
|
600
|
-
return {
|
|
601
|
-
worktreePath,
|
|
602
|
-
branch,
|
|
603
|
-
baseBranch,
|
|
604
|
-
reused: false
|
|
605
|
-
};
|
|
606
|
-
}
|
|
607
|
-
/**
|
|
608
|
-
* Run the project's setup command (e.g. `pnpm i && pnpm build`) inside a
|
|
609
|
-
* freshly created worktree. Called before the worktree is handed to any agent
|
|
610
|
-
* run so the checkout is ready to build/test. A non-zero exit is a hard error —
|
|
611
|
-
* starting agent work in a half-set-up tree is worse than failing the request.
|
|
439
|
+
* Run the project's setup command (e.g. `pnpm i && pnpm build`) inside the
|
|
440
|
+
* freshly materialized session workdir. Called before the checkout is handed
|
|
441
|
+
* to any agent run so it is ready to build/test. A non-zero exit is a hard
|
|
442
|
+
* error — starting agent work in a half-set-up tree is worse than failing the
|
|
443
|
+
* request.
|
|
612
444
|
*
|
|
613
445
|
* Security model: the command is intentionally arbitrary shell — that is the
|
|
614
446
|
* feature (install deps, build, seed fixtures). It is only configurable by
|
|
@@ -620,22 +452,22 @@ async function ensureWorktree(sandbox, repoWorkdir, { branch, baseBranch, token,
|
|
|
620
452
|
* server host, so it grants no privilege beyond what sandbox access already
|
|
621
453
|
* provides.
|
|
622
454
|
*
|
|
623
|
-
* @param sandbox
|
|
624
|
-
* @param
|
|
625
|
-
* @param command
|
|
455
|
+
* @param sandbox live sandbox containing the checkout
|
|
456
|
+
* @param workdir the server-resolved session workdir the command runs in
|
|
457
|
+
* @param command the org-configured setup shell command
|
|
626
458
|
*/
|
|
627
|
-
async function
|
|
628
|
-
const result = await sh(sandbox, `cd ${shellQuote(
|
|
629
|
-
phase:
|
|
459
|
+
async function runLifecycleCommand(sandbox, workdir, command, options) {
|
|
460
|
+
const result = await sh(sandbox, `cd ${shellQuote(workdir)} && { ${command}\n}`, {
|
|
461
|
+
phase: `${options.phase} command`,
|
|
630
462
|
...options.timeoutMs !== void 0 ? { timeoutMs: options.timeoutMs } : {}
|
|
631
463
|
});
|
|
632
464
|
if (result.exitCode !== 0) {
|
|
633
465
|
const detail = (result.stderr.trim() || result.stdout.trim()).slice(-1800);
|
|
634
|
-
throw new
|
|
466
|
+
throw new SetupCommandError(`${options.phase === "setup" ? "Setup" : "Teardown"} command failed (exit ${result.exitCode}): ${detail}`, options.phase === "setup" ? "setup-failed" : "teardown-failed");
|
|
635
467
|
}
|
|
636
468
|
}
|
|
637
|
-
async function
|
|
638
|
-
return
|
|
469
|
+
async function runSetupCommand(sandbox, workdir, command) {
|
|
470
|
+
return runLifecycleCommand(sandbox, workdir, command, { phase: "setup" });
|
|
639
471
|
}
|
|
640
472
|
/**
|
|
641
473
|
* Run the repository's best-effort teardown command from the materialized
|
|
@@ -643,33 +475,13 @@ async function runWorktreeSetup(sandbox, worktreePath, command) {
|
|
|
643
475
|
* so the retirement coordinator can log them while still continuing with
|
|
644
476
|
* scrub, pooling/destruction, cache invalidation, and row deletion.
|
|
645
477
|
*/
|
|
646
|
-
async function
|
|
647
|
-
return
|
|
478
|
+
async function runTeardownCommand(sandbox, workdir, command, options = {}) {
|
|
479
|
+
return runLifecycleCommand(sandbox, workdir, command, {
|
|
648
480
|
phase: "teardown",
|
|
649
481
|
...options.timeoutMs !== void 0 ? { timeoutMs: options.timeoutMs } : {}
|
|
650
482
|
});
|
|
651
483
|
}
|
|
652
484
|
/**
|
|
653
|
-
* Remove a worktree (and its local feature branch) from the sandbox. The
|
|
654
|
-
* checkout is removed with `--force` — the caller owns confirming that any
|
|
655
|
-
* uncommitted work in it can be discarded. Idempotent: a worktree whose
|
|
656
|
-
* directory is already gone only has its metadata pruned.
|
|
657
|
-
*
|
|
658
|
-
* @param sandbox live sandbox containing the base checkout
|
|
659
|
-
* @param repoWorkdir the base repo checkout path inside the sandbox
|
|
660
|
-
* @param branch the worktree's feature branch (ref-validated)
|
|
661
|
-
* @param worktreePath the persisted, server-computed worktree path
|
|
662
|
-
*/
|
|
663
|
-
async function removeWorktree(sandbox, repoWorkdir, { branch, worktreePath }) {
|
|
664
|
-
if (!isValidGitRef(branch)) throw new WorktreeError(`Invalid branch name '${branch}'.`, "invalid-branch");
|
|
665
|
-
const remove = await sh(sandbox, `git -C ${shellQuote(repoWorkdir)} worktree remove --force ${shellQuote(worktreePath)}`);
|
|
666
|
-
if (remove.exitCode !== 0) {
|
|
667
|
-
await sh(sandbox, `git -C ${shellQuote(repoWorkdir)} worktree prune`);
|
|
668
|
-
if ((await sh(sandbox, `test -e ${shellQuote(worktreePath)}`)).exitCode === 0) throw new WorktreeError(`git worktree remove failed: ${remove.stderr.trim() || remove.stdout.trim()}`, "worktree-failed");
|
|
669
|
-
}
|
|
670
|
-
await sh(sandbox, `git -C ${shellQuote(repoWorkdir)} branch -D ${shellQuote(branch)}`);
|
|
671
|
-
}
|
|
672
|
-
/**
|
|
673
485
|
* Preflight that `gh` is installed in the sandbox. Only called on the PR path so
|
|
674
486
|
* a missing `gh` never blocks clone/open. Surfaces an actionable error naming
|
|
675
487
|
* the sandbox template requirement.
|
|
@@ -712,6 +524,6 @@ async function createPullRequest(sandbox, workdir, { token, base, head, title, b
|
|
|
712
524
|
return { url };
|
|
713
525
|
}
|
|
714
526
|
//#endregion
|
|
715
|
-
export { CHECKOUT_COMMAND_TIMEOUT_MS, DEFAULT_COMMAND_TIMEOUT_MS, MaterializeError,
|
|
527
|
+
export { CHECKOUT_COMMAND_TIMEOUT_MS, DEFAULT_COMMAND_TIMEOUT_MS, MaterializeError, SetupCommandError, checkoutSessionBranch, commitAll, configureGitIdentity, createPullRequest, hasExistingCheckout, isValidGitRef, materializeRepo, pushBranch, resolveGitIdentity, runSetupCommand, runTeardownCommand, sh, shellQuote, withInstallToken };
|
|
716
528
|
|
|
717
529
|
//# sourceMappingURL=sandbox.js.map
|