webmux 0.20.0 → 0.22.0
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/backend/dist/server.js +46 -18
- package/bin/webmux.js +187 -35
- package/frontend/dist/assets/index-CIH_Vt6u.js +150 -0
- package/frontend/dist/assets/index-Dxe_PLr0.css +32 -0
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
- package/frontend/dist/assets/index-DGNuO17V.css +0 -32
- package/frontend/dist/assets/index-DrHGpNfR.js +0 -149
package/backend/dist/server.js
CHANGED
|
@@ -8886,7 +8886,7 @@ function listRemoteGitBranches(cwd) {
|
|
|
8886
8886
|
} catch {}
|
|
8887
8887
|
const output = runGit(["for-each-ref", "--format=%(refname:short)", "refs/remotes/origin"], cwd);
|
|
8888
8888
|
return output.split(`
|
|
8889
|
-
`).map((line) => line.trim()).filter((line) => line.length > 0).map((line) => line.replace(/^origin\//, "")).filter((name) => name !== "HEAD");
|
|
8889
|
+
`).map((line) => line.trim()).filter((line) => line.length > 0).map((line) => line.replace(/^origin\//, "")).filter((name) => name !== "HEAD" && name !== "origin");
|
|
8890
8890
|
}
|
|
8891
8891
|
function readGitWorktreeStatus(cwd) {
|
|
8892
8892
|
const dirtyOutput = runGit(["status", "--porcelain"], cwd);
|
|
@@ -8948,7 +8948,11 @@ class BunGitGateway {
|
|
|
8948
8948
|
if (opts.baseBranch)
|
|
8949
8949
|
args.push(opts.baseBranch);
|
|
8950
8950
|
} else {
|
|
8951
|
-
|
|
8951
|
+
if (opts.startPoint) {
|
|
8952
|
+
args.push("-b", opts.branch, opts.worktreePath, opts.startPoint);
|
|
8953
|
+
} else {
|
|
8954
|
+
args.push(opts.worktreePath, opts.branch);
|
|
8955
|
+
}
|
|
8952
8956
|
}
|
|
8953
8957
|
runGit(args, opts.repoRoot);
|
|
8954
8958
|
}
|
|
@@ -9114,7 +9118,8 @@ async function createManagedWorktree(opts, deps = {}) {
|
|
|
9114
9118
|
worktreePath: opts.worktreePath,
|
|
9115
9119
|
branch: opts.branch,
|
|
9116
9120
|
mode: opts.mode,
|
|
9117
|
-
baseBranch: opts.baseBranch
|
|
9121
|
+
baseBranch: opts.baseBranch,
|
|
9122
|
+
startPoint: opts.startPoint
|
|
9118
9123
|
});
|
|
9119
9124
|
worktreeCreated = true;
|
|
9120
9125
|
const gitDir = git.resolveWorktreeGitDir(opts.worktreePath);
|
|
@@ -9207,7 +9212,7 @@ class LifecycleService {
|
|
|
9207
9212
|
throw new LifecycleError("Base branch must differ from branch name", 400);
|
|
9208
9213
|
}
|
|
9209
9214
|
const baseBranch = mode === "new" ? requestedBaseBranch || this.deps.config.workspace.mainBranch : undefined;
|
|
9210
|
-
this.
|
|
9215
|
+
const branchAvailability = this.resolveBranchAvailability(branch, mode);
|
|
9211
9216
|
const { profileName, profile } = this.resolveProfile(input.profile);
|
|
9212
9217
|
const agent = this.resolveAgent(input.agent);
|
|
9213
9218
|
const worktreePath = this.resolveWorktreePath(branch);
|
|
@@ -9218,7 +9223,7 @@ class LifecycleService {
|
|
|
9218
9223
|
profile: profileName,
|
|
9219
9224
|
agent
|
|
9220
9225
|
};
|
|
9221
|
-
const deleteBranchOnRollback = mode === "new";
|
|
9226
|
+
const deleteBranchOnRollback = mode === "new" || branchAvailability.deleteBranchOnRollback;
|
|
9222
9227
|
let initialized = null;
|
|
9223
9228
|
try {
|
|
9224
9229
|
await this.reportCreateProgress({
|
|
@@ -9232,6 +9237,7 @@ class LifecycleService {
|
|
|
9232
9237
|
branch,
|
|
9233
9238
|
mode,
|
|
9234
9239
|
...baseBranch ? { baseBranch } : {},
|
|
9240
|
+
...branchAvailability.startPoint ? { startPoint: branchAvailability.startPoint } : {},
|
|
9235
9241
|
profile: profileName,
|
|
9236
9242
|
agent,
|
|
9237
9243
|
runtime: profile.runtime,
|
|
@@ -9377,9 +9383,9 @@ class LifecycleService {
|
|
|
9377
9383
|
throw this.wrapOperationError(error);
|
|
9378
9384
|
}
|
|
9379
9385
|
}
|
|
9380
|
-
listAvailableBranches() {
|
|
9386
|
+
listAvailableBranches(options = {}) {
|
|
9381
9387
|
const localBranches = this.listLocalBranches().filter((branch) => isValidBranchName(branch));
|
|
9382
|
-
const remoteBranches = this.listRemoteBranches().filter((branch) => isValidBranchName(branch));
|
|
9388
|
+
const remoteBranches = options.includeRemote ? this.listRemoteBranches().filter((branch) => isValidBranchName(branch)) : [];
|
|
9383
9389
|
const checkedOutBranches = this.listCheckedOutBranches();
|
|
9384
9390
|
const allBranches = [...new Set([...localBranches, ...remoteBranches])];
|
|
9385
9391
|
return allBranches.filter((branch) => !checkedOutBranches.has(branch)).sort((left, right) => left.localeCompare(right)).map((name) => ({ name }));
|
|
@@ -9404,20 +9410,28 @@ class LifecycleService {
|
|
|
9404
9410
|
}
|
|
9405
9411
|
return await this.deps.autoName.generateBranchName(this.deps.config.autoName, prompt);
|
|
9406
9412
|
}
|
|
9407
|
-
|
|
9413
|
+
resolveBranchAvailability(branch, mode) {
|
|
9408
9414
|
const localBranches = new Set(this.listLocalBranches());
|
|
9409
9415
|
if (mode === "new") {
|
|
9410
9416
|
if (localBranches.has(branch)) {
|
|
9411
9417
|
throw new LifecycleError(`Branch already exists: ${branch}`, 409);
|
|
9412
9418
|
}
|
|
9413
|
-
return;
|
|
9419
|
+
return { deleteBranchOnRollback: false };
|
|
9414
9420
|
}
|
|
9415
|
-
if (
|
|
9416
|
-
|
|
9421
|
+
if (localBranches.has(branch)) {
|
|
9422
|
+
if (this.listCheckedOutBranches().has(branch)) {
|
|
9423
|
+
throw new LifecycleError(`Branch already has a worktree: ${branch}`, 409);
|
|
9424
|
+
}
|
|
9425
|
+
return { deleteBranchOnRollback: false };
|
|
9417
9426
|
}
|
|
9418
|
-
|
|
9419
|
-
|
|
9427
|
+
const remoteBranches = new Set(this.listRemoteBranches());
|
|
9428
|
+
if (!remoteBranches.has(branch)) {
|
|
9429
|
+
throw new LifecycleError(`Branch not found: ${branch}`, 404);
|
|
9420
9430
|
}
|
|
9431
|
+
return {
|
|
9432
|
+
startPoint: `origin/${branch}`,
|
|
9433
|
+
deleteBranchOnRollback: true
|
|
9434
|
+
};
|
|
9421
9435
|
}
|
|
9422
9436
|
resolveProfile(profileName) {
|
|
9423
9437
|
const name = profileName ?? getDefaultProfileName(this.deps.config);
|
|
@@ -11696,9 +11710,10 @@ async function apiRuntimeEvent(req) {
|
|
|
11696
11710
|
...notification ? { notification } : {}
|
|
11697
11711
|
});
|
|
11698
11712
|
}
|
|
11699
|
-
async function apiListBranches() {
|
|
11713
|
+
async function apiListBranches(req) {
|
|
11714
|
+
const includeRemote = new URL(req.url).searchParams.get("includeRemote") === "true";
|
|
11700
11715
|
return jsonResponse({
|
|
11701
|
-
branches: lifecycleService.listAvailableBranches()
|
|
11716
|
+
branches: lifecycleService.listAvailableBranches({ includeRemote })
|
|
11702
11717
|
});
|
|
11703
11718
|
}
|
|
11704
11719
|
async function apiListBaseBranches() {
|
|
@@ -11878,9 +11893,22 @@ async function apiPullMain(req) {
|
|
|
11878
11893
|
const raw = await req.json().catch(() => ({}));
|
|
11879
11894
|
const body = raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
11880
11895
|
const force = body.force === true;
|
|
11881
|
-
const
|
|
11896
|
+
const repo = typeof body.repo === "string" ? body.repo : "";
|
|
11897
|
+
let projectRoot2 = PROJECT_DIR;
|
|
11898
|
+
if (repo) {
|
|
11899
|
+
const linkedRepo = config.integrations.github.linkedRepos.find((lr) => lr.alias === repo);
|
|
11900
|
+
if (!linkedRepo)
|
|
11901
|
+
return errorResponse(`Unknown linked repo: ${repo}`, 404);
|
|
11902
|
+
if (!linkedRepo.dir)
|
|
11903
|
+
return errorResponse(`Linked repo "${repo}" has no dir configured`, 400);
|
|
11904
|
+
projectRoot2 = resolve7(PROJECT_DIR, linkedRepo.dir);
|
|
11905
|
+
if (!projectRoot2.startsWith(PROJECT_DIR)) {
|
|
11906
|
+
return errorResponse("Invalid linked repo directory", 400);
|
|
11907
|
+
}
|
|
11908
|
+
}
|
|
11909
|
+
const deps = { git, projectRoot: projectRoot2, mainBranch: config.workspace.mainBranch };
|
|
11882
11910
|
const result = force ? forcePullMainBranch(deps) : pullMainBranch(deps);
|
|
11883
|
-
log.info(`[pull-main] ${force ? "force " : ""}pull: ${result.status}`);
|
|
11911
|
+
log.info(`[pull-main] ${repo || "main"} ${force ? "force " : ""}pull: ${result.status}`);
|
|
11884
11912
|
return jsonResponse(result);
|
|
11885
11913
|
}
|
|
11886
11914
|
async function apiGetLinearIssues() {
|
|
@@ -11984,7 +12012,7 @@ Bun.serve({
|
|
|
11984
12012
|
GET: () => jsonResponse(getFrontendConfig())
|
|
11985
12013
|
},
|
|
11986
12014
|
"/api/branches": {
|
|
11987
|
-
GET: () => catching("GET /api/branches", () => apiListBranches())
|
|
12015
|
+
GET: (req) => catching("GET /api/branches", () => apiListBranches(req))
|
|
11988
12016
|
},
|
|
11989
12017
|
"/api/base-branches": {
|
|
11990
12018
|
GET: () => catching("GET /api/base-branches", () => apiListBaseBranches())
|
package/bin/webmux.js
CHANGED
|
@@ -175,7 +175,7 @@ function listRemoteGitBranches(cwd) {
|
|
|
175
175
|
} catch {}
|
|
176
176
|
const output = runGit(["for-each-ref", "--format=%(refname:short)", "refs/remotes/origin"], cwd);
|
|
177
177
|
return output.split(`
|
|
178
|
-
`).map((line) => line.trim()).filter((line) => line.length > 0).map((line) => line.replace(/^origin\//, "")).filter((name) => name !== "HEAD");
|
|
178
|
+
`).map((line) => line.trim()).filter((line) => line.length > 0).map((line) => line.replace(/^origin\//, "")).filter((name) => name !== "HEAD" && name !== "origin");
|
|
179
179
|
}
|
|
180
180
|
function readGitWorktreeStatus(cwd) {
|
|
181
181
|
const dirtyOutput = runGit(["status", "--porcelain"], cwd);
|
|
@@ -237,7 +237,11 @@ class BunGitGateway {
|
|
|
237
237
|
if (opts.baseBranch)
|
|
238
238
|
args.push(opts.baseBranch);
|
|
239
239
|
} else {
|
|
240
|
-
|
|
240
|
+
if (opts.startPoint) {
|
|
241
|
+
args.push("-b", opts.branch, opts.worktreePath, opts.startPoint);
|
|
242
|
+
} else {
|
|
243
|
+
args.push(opts.worktreePath, opts.branch);
|
|
244
|
+
}
|
|
241
245
|
}
|
|
242
246
|
runGit(args, opts.repoRoot);
|
|
243
247
|
}
|
|
@@ -1529,7 +1533,9 @@ function buildInitPromptSpec(context) {
|
|
|
1529
1533
|
"Do not modify any other file.",
|
|
1530
1534
|
"Do not ask the user questions. Infer the config from the repository contents.",
|
|
1531
1535
|
"Be efficient: inspect only the files needed to determine the project name, main branch, service layout, dev commands, and ports.",
|
|
1532
|
-
"The YAML must be valid and minimal.",
|
|
1536
|
+
"The active, uncommented YAML must be valid and minimal.",
|
|
1537
|
+
"Do not remove other starter sections or their explanatory comments just because they are unused.",
|
|
1538
|
+
"Keep optional examples and comments in place so the user can uncomment and use them later.",
|
|
1533
1539
|
`Set workspace.defaultAgent to ${context.defaultAgent}.`,
|
|
1534
1540
|
"Use this config shape:",
|
|
1535
1541
|
"name: infer from the repository",
|
|
@@ -1544,6 +1550,7 @@ function buildInitPromptSpec(context) {
|
|
|
1544
1550
|
"Include integrations.github.linkedRepos as an empty list, integrations.linear.enabled as true, and startupEnvs as an empty object.",
|
|
1545
1551
|
"Only include optional sections like auto_name, lifecycleHooks, sandbox/docker config, mounts, or systemPrompt if the repository gives clear evidence they are needed.",
|
|
1546
1552
|
"Prefer editing the existing keys over replacing the file with a completely different shape.",
|
|
1553
|
+
"Preserve the existing template structure and comments unless a specific change requires updating them.",
|
|
1547
1554
|
"Before finishing, verify that `.webmux.yaml` exists and contains the final YAML."
|
|
1548
1555
|
].join(`
|
|
1549
1556
|
`);
|
|
@@ -1875,44 +1882,179 @@ function buildStarterTemplate(input) {
|
|
|
1875
1882
|
const defaultAgent = input.defaultAgent ?? "claude";
|
|
1876
1883
|
const packageManager = input.packageManager ?? "npm";
|
|
1877
1884
|
const devCommand = buildRunScriptCommand(packageManager, "dev");
|
|
1878
|
-
return `#
|
|
1885
|
+
return `# Starter config for webmux.
|
|
1886
|
+
# Keep the active keys below as a minimal working setup, then uncomment
|
|
1887
|
+
# the examples to enable more services, profiles, integrations, or hooks.
|
|
1888
|
+
|
|
1889
|
+
# Project display name shown in the dashboard and browser title.
|
|
1879
1890
|
name: ${input.projectName}
|
|
1880
1891
|
|
|
1881
1892
|
workspace:
|
|
1893
|
+
# Git branch new worktrees start from.
|
|
1882
1894
|
mainBranch: ${input.mainBranch}
|
|
1895
|
+
# Relative or absolute directory where managed worktrees are created.
|
|
1883
1896
|
worktreeRoot: ../worktrees
|
|
1897
|
+
# Agent new worktrees use by default.
|
|
1884
1898
|
defaultAgent: ${defaultAgent}
|
|
1885
|
-
|
|
1886
|
-
#
|
|
1887
|
-
#
|
|
1888
|
-
#
|
|
1899
|
+
# Example background pull settings for keeping the main branch fresh.
|
|
1900
|
+
# autoPull:
|
|
1901
|
+
# # Turn automatic pulls on or off.
|
|
1902
|
+
# enabled: false
|
|
1903
|
+
# # Seconds between pull attempts.
|
|
1904
|
+
# intervalSeconds: 300
|
|
1905
|
+
|
|
1906
|
+
# Services define the ports webmux allocates and tracks per worktree.
|
|
1889
1907
|
services:
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1908
|
+
# Example app service with a predictable per-worktree port.
|
|
1909
|
+
# - name: app
|
|
1910
|
+
# # Env var name injected into panes and hooks.
|
|
1911
|
+
# portEnv: PORT
|
|
1912
|
+
# # Starting port for the first worktree slot.
|
|
1913
|
+
# portStart: 3000
|
|
1914
|
+
# # Port increment between worktree slots.
|
|
1915
|
+
# portStep: 10
|
|
1916
|
+
# # Link shown in the dashboard when the service is running.
|
|
1917
|
+
# urlTemplate: http://localhost:\${PORT}
|
|
1918
|
+
|
|
1919
|
+
# Profiles define runtime, permissions, and tmux pane layout.
|
|
1895
1920
|
profiles:
|
|
1896
1921
|
default:
|
|
1922
|
+
# Run panes directly on the host machine.
|
|
1897
1923
|
runtime: host
|
|
1898
|
-
|
|
1899
|
-
envPassthrough:
|
|
1924
|
+
# Forward selected host env vars into the agent process.
|
|
1925
|
+
envPassthrough:
|
|
1926
|
+
# - ANTHROPIC_API_KEY
|
|
1927
|
+
# - OPENAI_API_KEY
|
|
1928
|
+
# Extra system instructions for the agent in this profile.
|
|
1929
|
+
# systemPrompt: >
|
|
1930
|
+
# You are working in \${WEBMUX_WORKTREE_PATH}
|
|
1931
|
+
# Skip agent permission prompts in this profile.
|
|
1932
|
+
# yolo: true
|
|
1933
|
+
# Panes define the tmux layout created for each worktree session.
|
|
1900
1934
|
panes:
|
|
1935
|
+
# Main AI coding pane.
|
|
1901
1936
|
- id: agent
|
|
1937
|
+
# Pane type: agent, command, or shell.
|
|
1902
1938
|
kind: agent
|
|
1939
|
+
# Focus this pane when the session opens.
|
|
1903
1940
|
focus: true
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1941
|
+
# Place this pane to the right of the existing layout.
|
|
1942
|
+
# split: right
|
|
1943
|
+
# Percent of the available space this pane should take.
|
|
1944
|
+
# sizePct: 50
|
|
1945
|
+
# Start this pane in the repo root or managed worktree.
|
|
1946
|
+
# cwd: worktree
|
|
1947
|
+
# Example dev server pane.
|
|
1948
|
+
# - id: app
|
|
1949
|
+
# # Pane type: agent, command, or shell.
|
|
1950
|
+
# kind: command
|
|
1951
|
+
# # Place this pane to the right of the existing layout.
|
|
1952
|
+
# split: right
|
|
1953
|
+
# # Percent of the available space this pane should take.
|
|
1954
|
+
# sizePct: 50
|
|
1955
|
+
# # Start this pane in the repo root or managed worktree.
|
|
1956
|
+
# cwd: worktree
|
|
1957
|
+
# # Change into a subdirectory before running the command.
|
|
1958
|
+
# workingDir: frontend
|
|
1959
|
+
# # Command run when the pane starts. webmux injects $PORT.
|
|
1960
|
+
# command: PORT=$PORT ${devCommand}
|
|
1961
|
+
# Example shell pane for manual commands.
|
|
1962
|
+
# - id: shell
|
|
1963
|
+
# # Pane type: agent, command, or shell.
|
|
1964
|
+
# kind: shell
|
|
1965
|
+
# # Place this pane below the existing layout.
|
|
1966
|
+
# split: bottom
|
|
1967
|
+
# # Percent of the available space this pane should take.
|
|
1968
|
+
# sizePct: 30
|
|
1969
|
+
# # Start this pane in the repo root or managed worktree.
|
|
1970
|
+
# cwd: repo
|
|
1971
|
+
|
|
1972
|
+
# Example sandbox profile that runs panes inside Docker.
|
|
1973
|
+
# sandbox:
|
|
1974
|
+
# # Run panes inside a container instead of on the host.
|
|
1975
|
+
# runtime: docker
|
|
1976
|
+
# # Docker image used for the sandbox container.
|
|
1977
|
+
# image: ghcr.io/your-org/your-image:latest
|
|
1978
|
+
# # Forward selected host env vars into the container.
|
|
1979
|
+
# envPassthrough:
|
|
1980
|
+
# - ANTHROPIC_API_KEY
|
|
1981
|
+
# - OPENAI_API_KEY
|
|
1982
|
+
# # Extra system instructions for the agent in this profile.
|
|
1983
|
+
# systemPrompt: >
|
|
1984
|
+
# Extra instructions for the sandbox profile.
|
|
1985
|
+
# # Skip agent permission prompts in this profile.
|
|
1986
|
+
# yolo: true
|
|
1987
|
+
# # Extra host paths to mount into the container.
|
|
1988
|
+
# mounts:
|
|
1989
|
+
# # Host path mounted into the sandbox.
|
|
1990
|
+
# - hostPath: ~/.codex
|
|
1991
|
+
# # Path inside the container.
|
|
1992
|
+
# guestPath: /root/.codex
|
|
1993
|
+
# # Allow writes through this mount.
|
|
1994
|
+
# writable: true
|
|
1995
|
+
# # Panes define the tmux layout created for sandbox sessions.
|
|
1996
|
+
# panes:
|
|
1997
|
+
# # Main AI coding pane.
|
|
1998
|
+
# - id: agent
|
|
1999
|
+
# # Pane type: agent, command, or shell.
|
|
2000
|
+
# kind: agent
|
|
2001
|
+
# # Focus this pane when the session opens.
|
|
2002
|
+
# focus: true
|
|
2003
|
+
# # Example shell pane for manual commands.
|
|
2004
|
+
# - id: shell
|
|
2005
|
+
# # Pane type: agent, command, or shell.
|
|
2006
|
+
# kind: shell
|
|
2007
|
+
# # Place this pane to the right of the existing layout.
|
|
2008
|
+
# split: right
|
|
2009
|
+
# # Start this pane in the repo root or managed worktree.
|
|
2010
|
+
# cwd: repo
|
|
2011
|
+
|
|
2012
|
+
# Integrations connect webmux to external systems.
|
|
1909
2013
|
integrations:
|
|
1910
2014
|
github:
|
|
1911
|
-
|
|
2015
|
+
# Additional local repos webmux should consider alongside the main repo.
|
|
2016
|
+
linkedRepos:
|
|
2017
|
+
# GitHub slug for a related repo.
|
|
2018
|
+
# - repo: your-org/your-repo
|
|
2019
|
+
# # Short label shown in the UI.
|
|
2020
|
+
# alias: repo
|
|
2021
|
+
# # Relative or absolute path to that local checkout.
|
|
2022
|
+
# dir: ../your-repo
|
|
2023
|
+
# Remove managed worktrees automatically when their PR merges.
|
|
2024
|
+
# autoRemoveOnMerge: true
|
|
1912
2025
|
linear:
|
|
2026
|
+
# Enable Linear issue lookup and linking in the UI.
|
|
1913
2027
|
enabled: true
|
|
1914
|
-
|
|
1915
|
-
|
|
2028
|
+
# Auto-create worktrees for assigned issues.
|
|
2029
|
+
# autoCreateWorktrees: true
|
|
2030
|
+
# Show a create-ticket action in the dashboard.
|
|
2031
|
+
# createTicketOption: true
|
|
2032
|
+
# Restrict issue sync to a specific Linear team id.
|
|
2033
|
+
# teamId: team-123
|
|
2034
|
+
|
|
2035
|
+
# startupEnvs become runtime env vars for panes, agents, and hooks.
|
|
2036
|
+
startupEnvs:
|
|
2037
|
+
# Example feature flag available in every worktree session.
|
|
2038
|
+
# FEATURE_FLAG: true
|
|
2039
|
+
# Example service URL built from allocated ports.
|
|
2040
|
+
# API_BASE_URL: http://localhost:\${PORT}
|
|
2041
|
+
|
|
2042
|
+
# lifecycleHooks run custom shell commands during worktree lifecycle events.
|
|
2043
|
+
# lifecycleHooks:
|
|
2044
|
+
# # Runs after env setup and before panes start.
|
|
2045
|
+
# postCreate: bun install
|
|
2046
|
+
# # Runs before the worktree directory is removed.
|
|
2047
|
+
# preRemove: tmux kill-session -t "$WEBMUX_WORKTREE_ID" || true
|
|
2048
|
+
|
|
2049
|
+
# auto_name lets webmux generate a branch name when one is not provided.
|
|
2050
|
+
# auto_name:
|
|
2051
|
+
# # Provider used for automatic branch naming.
|
|
2052
|
+
# provider: ${defaultAgent}
|
|
2053
|
+
# # Model used for automatic branch naming.
|
|
2054
|
+
# model: ${defaultAgent === "codex" ? "gpt-5.1-codex" : "claude-3-5-haiku-latest"}
|
|
2055
|
+
# # Prompt that tells the model how to name branches.
|
|
2056
|
+
# system_prompt: >
|
|
2057
|
+
# Generate a short kebab-case git branch name.
|
|
1916
2058
|
`;
|
|
1917
2059
|
}
|
|
1918
2060
|
var FAST_CLAUDE_MODEL = "haiku", FAST_CLAUDE_EFFORT = "low", FAST_CODEX_MODEL = "gpt-5.1-codex", FAST_CODEX_REASONING = "low";
|
|
@@ -11182,7 +11324,8 @@ async function createManagedWorktree(opts, deps2 = {}) {
|
|
|
11182
11324
|
worktreePath: opts.worktreePath,
|
|
11183
11325
|
branch: opts.branch,
|
|
11184
11326
|
mode: opts.mode,
|
|
11185
|
-
baseBranch: opts.baseBranch
|
|
11327
|
+
baseBranch: opts.baseBranch,
|
|
11328
|
+
startPoint: opts.startPoint
|
|
11186
11329
|
});
|
|
11187
11330
|
worktreeCreated = true;
|
|
11188
11331
|
const gitDir = git.resolveWorktreeGitDir(opts.worktreePath);
|
|
@@ -11275,7 +11418,7 @@ class LifecycleService {
|
|
|
11275
11418
|
throw new LifecycleError("Base branch must differ from branch name", 400);
|
|
11276
11419
|
}
|
|
11277
11420
|
const baseBranch = mode === "new" ? requestedBaseBranch || this.deps.config.workspace.mainBranch : undefined;
|
|
11278
|
-
this.
|
|
11421
|
+
const branchAvailability = this.resolveBranchAvailability(branch, mode);
|
|
11279
11422
|
const { profileName, profile } = this.resolveProfile(input.profile);
|
|
11280
11423
|
const agent = this.resolveAgent(input.agent);
|
|
11281
11424
|
const worktreePath = this.resolveWorktreePath(branch);
|
|
@@ -11286,7 +11429,7 @@ class LifecycleService {
|
|
|
11286
11429
|
profile: profileName,
|
|
11287
11430
|
agent
|
|
11288
11431
|
};
|
|
11289
|
-
const deleteBranchOnRollback = mode === "new";
|
|
11432
|
+
const deleteBranchOnRollback = mode === "new" || branchAvailability.deleteBranchOnRollback;
|
|
11290
11433
|
let initialized = null;
|
|
11291
11434
|
try {
|
|
11292
11435
|
await this.reportCreateProgress({
|
|
@@ -11300,6 +11443,7 @@ class LifecycleService {
|
|
|
11300
11443
|
branch,
|
|
11301
11444
|
mode,
|
|
11302
11445
|
...baseBranch ? { baseBranch } : {},
|
|
11446
|
+
...branchAvailability.startPoint ? { startPoint: branchAvailability.startPoint } : {},
|
|
11303
11447
|
profile: profileName,
|
|
11304
11448
|
agent,
|
|
11305
11449
|
runtime: profile.runtime,
|
|
@@ -11445,9 +11589,9 @@ class LifecycleService {
|
|
|
11445
11589
|
throw this.wrapOperationError(error);
|
|
11446
11590
|
}
|
|
11447
11591
|
}
|
|
11448
|
-
listAvailableBranches() {
|
|
11592
|
+
listAvailableBranches(options = {}) {
|
|
11449
11593
|
const localBranches = this.listLocalBranches().filter((branch) => isValidBranchName(branch));
|
|
11450
|
-
const remoteBranches = this.listRemoteBranches().filter((branch) => isValidBranchName(branch));
|
|
11594
|
+
const remoteBranches = options.includeRemote ? this.listRemoteBranches().filter((branch) => isValidBranchName(branch)) : [];
|
|
11451
11595
|
const checkedOutBranches = this.listCheckedOutBranches();
|
|
11452
11596
|
const allBranches = [...new Set([...localBranches, ...remoteBranches])];
|
|
11453
11597
|
return allBranches.filter((branch) => !checkedOutBranches.has(branch)).sort((left, right) => left.localeCompare(right)).map((name) => ({ name }));
|
|
@@ -11472,20 +11616,28 @@ class LifecycleService {
|
|
|
11472
11616
|
}
|
|
11473
11617
|
return await this.deps.autoName.generateBranchName(this.deps.config.autoName, prompt);
|
|
11474
11618
|
}
|
|
11475
|
-
|
|
11619
|
+
resolveBranchAvailability(branch, mode) {
|
|
11476
11620
|
const localBranches = new Set(this.listLocalBranches());
|
|
11477
11621
|
if (mode === "new") {
|
|
11478
11622
|
if (localBranches.has(branch)) {
|
|
11479
11623
|
throw new LifecycleError(`Branch already exists: ${branch}`, 409);
|
|
11480
11624
|
}
|
|
11481
|
-
return;
|
|
11625
|
+
return { deleteBranchOnRollback: false };
|
|
11482
11626
|
}
|
|
11483
|
-
if (
|
|
11484
|
-
|
|
11627
|
+
if (localBranches.has(branch)) {
|
|
11628
|
+
if (this.listCheckedOutBranches().has(branch)) {
|
|
11629
|
+
throw new LifecycleError(`Branch already has a worktree: ${branch}`, 409);
|
|
11630
|
+
}
|
|
11631
|
+
return { deleteBranchOnRollback: false };
|
|
11485
11632
|
}
|
|
11486
|
-
|
|
11487
|
-
|
|
11633
|
+
const remoteBranches = new Set(this.listRemoteBranches());
|
|
11634
|
+
if (!remoteBranches.has(branch)) {
|
|
11635
|
+
throw new LifecycleError(`Branch not found: ${branch}`, 404);
|
|
11488
11636
|
}
|
|
11637
|
+
return {
|
|
11638
|
+
startPoint: `origin/${branch}`,
|
|
11639
|
+
deleteBranchOnRollback: true
|
|
11640
|
+
};
|
|
11489
11641
|
}
|
|
11490
11642
|
resolveProfile(profileName) {
|
|
11491
11643
|
const name = profileName ?? getDefaultProfileName(this.deps.config);
|
|
@@ -12764,7 +12916,7 @@ import { fileURLToPath } from "url";
|
|
|
12764
12916
|
// package.json
|
|
12765
12917
|
var package_default = {
|
|
12766
12918
|
name: "webmux",
|
|
12767
|
-
version: "0.
|
|
12919
|
+
version: "0.22.0",
|
|
12768
12920
|
description: "Web dashboard for workmux \u2014 browser UI with embedded terminals, PR monitoring, and CI integration",
|
|
12769
12921
|
type: "module",
|
|
12770
12922
|
repository: {
|