webmux 0.23.0 → 0.24.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/bin/webmux.js CHANGED
@@ -257,6 +257,9 @@ class BunGitGateway {
257
257
  readWorktreeStatus(cwd) {
258
258
  return readGitWorktreeStatus(cwd);
259
259
  }
260
+ readStatus(cwd) {
261
+ return runGit(["status", "--short", "--untracked-files=all"], cwd);
262
+ }
260
263
  createWorktree(opts) {
261
264
  const args = ["worktree", "add"];
262
265
  if (opts.mode === "new") {
@@ -10762,11 +10765,12 @@ function buildClaudeArgs(model, systemPrompt, prompt) {
10762
10765
  systemPrompt,
10763
10766
  "--output-format",
10764
10767
  "text",
10765
- "--no-session-persistence"
10768
+ "--no-session-persistence",
10769
+ "--model",
10770
+ model || DEFAULT_AUTO_NAME_MODEL,
10771
+ "--effort",
10772
+ "low"
10766
10773
  ];
10767
- if (model) {
10768
- args.push("--model", model);
10769
- }
10770
10774
  args.push(prompt);
10771
10775
  return args;
10772
10776
  }
@@ -10812,8 +10816,11 @@ class AutoNameService {
10812
10816
  throw new Error(`'${cli}' CLI not found. Install it or check your PATH.`);
10813
10817
  }
10814
10818
  if (result.exitCode !== 0) {
10815
- const detail = result.stderr.trim() || `exit ${result.exitCode}`;
10816
- throw new Error(`${cli} failed: ${detail}`);
10819
+ const stderr = result.stderr.trim();
10820
+ const stdout = result.stdout.trim();
10821
+ const output2 = stderr || stdout || `exit ${result.exitCode}`;
10822
+ const command = args.join(" ");
10823
+ throw new Error(`${cli} failed (command: ${command}): ${output2}`);
10817
10824
  }
10818
10825
  const output = result.stdout.trim();
10819
10826
  if (!output) {
@@ -10822,7 +10829,7 @@ class AutoNameService {
10822
10829
  return normalizeGeneratedBranchName(output);
10823
10830
  }
10824
10831
  }
10825
- var MAX_BRANCH_LENGTH = 40, DEFAULT_SYSTEM_PROMPT;
10832
+ var MAX_BRANCH_LENGTH = 40, DEFAULT_AUTO_NAME_MODEL = "claude-haiku-4-5-20251001", DEFAULT_SYSTEM_PROMPT;
10826
10833
  var init_auto_name_service = __esm(() => {
10827
10834
  init_policies();
10828
10835
  DEFAULT_SYSTEM_PROMPT = [
@@ -11425,120 +11432,65 @@ function toErrorMessage2(error) {
11425
11432
  function stringifyStartupEnvValue(value) {
11426
11433
  return typeof value === "boolean" ? String(value) : value;
11427
11434
  }
11435
+ function prefixAgentBranch(agent, branch) {
11436
+ return `${agent}-${branch}`;
11437
+ }
11438
+ function buildCreateWorktreeTargets(branch, agentSelection) {
11439
+ if (agentSelection === "both") {
11440
+ return [
11441
+ { branch: prefixAgentBranch("claude", branch), agent: "claude" },
11442
+ { branch: prefixAgentBranch("codex", branch), agent: "codex" }
11443
+ ];
11444
+ }
11445
+ return [{ branch, agent: agentSelection }];
11446
+ }
11428
11447
 
11429
11448
  class LifecycleService {
11430
11449
  deps;
11431
11450
  constructor(deps2) {
11432
11451
  this.deps = deps2;
11433
11452
  }
11434
- async createWorktree(input) {
11453
+ async createWorktrees(input) {
11435
11454
  const mode = input.mode ?? "new";
11436
- const requestedBaseBranch = input.baseBranch?.trim();
11437
- if (requestedBaseBranch && !isValidBranchName(requestedBaseBranch)) {
11438
- throw new LifecycleError("Invalid base branch name", 400);
11439
- }
11440
- if (requestedBaseBranch && mode === "existing") {
11441
- throw new LifecycleError("Base branch is only supported for new worktrees", 400);
11455
+ const agentSelection = input.agent ?? this.deps.config.workspace.defaultAgent;
11456
+ if (agentSelection === "both" && mode === "existing") {
11457
+ throw new LifecycleError("Creating both agents is only supported for new worktrees", 400);
11442
11458
  }
11443
11459
  const branch = await this.resolveBranch(input.branch, input.prompt, mode);
11444
- if (requestedBaseBranch && requestedBaseBranch === branch) {
11445
- throw new LifecycleError("Base branch must differ from branch name", 400);
11446
- }
11447
- const baseBranch = mode === "new" ? requestedBaseBranch || this.deps.config.workspace.mainBranch : undefined;
11448
- const branchAvailability = this.resolveBranchAvailability(branch, mode);
11449
- const { profileName, profile } = this.resolveProfile(input.profile);
11450
- const agent = this.resolveAgent(input.agent);
11451
- const worktreePath = this.resolveWorktreePath(branch);
11452
- const createProgressBase = {
11453
- branch,
11454
- ...baseBranch ? { baseBranch } : {},
11455
- path: worktreePath,
11456
- profile: profileName,
11457
- agent
11458
- };
11459
- const deleteBranchOnRollback = mode === "new" || branchAvailability.deleteBranchOnRollback;
11460
- let initialized = null;
11460
+ const targets = buildCreateWorktreeTargets(branch, agentSelection);
11461
+ const createdBranches = [];
11461
11462
  try {
11462
- await this.reportCreateProgress({
11463
- ...createProgressBase,
11464
- phase: "creating_worktree"
11465
- });
11466
- await mkdir4(dirname5(worktreePath), { recursive: true });
11467
- initialized = await createManagedWorktree({
11468
- repoRoot: this.deps.projectRoot,
11469
- worktreePath,
11470
- branch,
11471
- mode,
11472
- ...baseBranch ? { baseBranch } : {},
11473
- ...branchAvailability.startPoint ? { startPoint: branchAvailability.startPoint } : {},
11474
- profile: profileName,
11475
- agent,
11476
- runtime: profile.runtime,
11477
- startupEnvValues: await this.buildStartupEnvValues(input.envOverrides),
11478
- allocatedPorts: await this.allocatePorts(),
11479
- runtimeEnvExtras: { WEBMUX_WORKTREE_PATH: worktreePath },
11480
- controlUrl: this.controlUrl(),
11481
- controlToken: await this.deps.getControlToken(),
11482
- deleteBranchOnRollback
11483
- }, {
11484
- git: this.deps.git
11485
- });
11486
- await this.reportCreateProgress({
11487
- ...createProgressBase,
11488
- phase: "running_post_create_hook"
11489
- });
11490
- await this.runLifecycleHook({
11491
- name: "postCreate",
11492
- command: this.deps.config.lifecycleHooks.postCreate,
11493
- meta: initialized.meta,
11494
- worktreePath
11495
- });
11496
- initialized = await this.refreshManagedArtifactsFromMeta({
11497
- gitDir: initialized.paths.gitDir,
11498
- meta: initialized.meta,
11499
- worktreePath
11500
- });
11501
- await this.reportCreateProgress({
11502
- ...createProgressBase,
11503
- phase: "preparing_runtime"
11504
- });
11505
- await ensureAgentRuntimeArtifacts({
11506
- gitDir: initialized.paths.gitDir,
11507
- worktreePath
11508
- });
11509
- await this.reportCreateProgress({
11510
- ...createProgressBase,
11511
- phase: "starting_session"
11512
- });
11513
- await this.materializeRuntimeSession({
11514
- branch,
11515
- profile,
11516
- agent,
11517
- initialized,
11518
- worktreePath,
11519
- prompt: input.prompt,
11520
- launchMode: "fresh"
11521
- });
11522
- await this.reportCreateProgress({
11523
- ...createProgressBase,
11524
- phase: "reconciling"
11525
- });
11526
- await this.deps.reconciliation.reconcile(this.deps.projectRoot, { force: true });
11527
- return {
11528
- branch,
11529
- worktreeId: initialized.meta.worktreeId
11530
- };
11463
+ for (const target of targets) {
11464
+ const created = await this.createResolvedWorktree({
11465
+ ...input,
11466
+ mode,
11467
+ branch: target.branch,
11468
+ agent: target.agent
11469
+ });
11470
+ createdBranches.push(created.branch);
11471
+ }
11531
11472
  } catch (error) {
11532
- if (initialized) {
11533
- const cleanupError = await this.cleanupFailedCreate(branch, worktreePath, profile.runtime, deleteBranchOnRollback);
11534
- if (cleanupError) {
11535
- throw this.wrapOperationError(new Error(`${toErrorMessage2(error)}; ${cleanupError}`));
11536
- }
11473
+ const rollbackError = await this.rollbackCreatedWorktrees(createdBranches);
11474
+ if (rollbackError) {
11475
+ throw this.wrapOperationError(new Error(`${toErrorMessage2(error)}; ${rollbackError}`));
11537
11476
  }
11538
11477
  throw this.wrapOperationError(error);
11539
- } finally {
11540
- await this.finishCreateProgress(branch);
11541
11478
  }
11479
+ return {
11480
+ primaryBranch: createdBranches[0],
11481
+ branches: createdBranches
11482
+ };
11483
+ }
11484
+ async createWorktree(input) {
11485
+ const mode = input.mode ?? "new";
11486
+ const branch = await this.resolveBranch(input.branch, input.prompt, mode);
11487
+ const agent = this.resolveAgent(input.agent);
11488
+ return await this.createResolvedWorktree({
11489
+ ...input,
11490
+ mode,
11491
+ branch,
11492
+ agent
11493
+ });
11542
11494
  }
11543
11495
  async openWorktree(branch) {
11544
11496
  try {
@@ -11939,6 +11891,123 @@ class LifecycleService {
11939
11891
  async finishCreateProgress(branch) {
11940
11892
  await this.deps.onCreateFinished?.(branch);
11941
11893
  }
11894
+ async rollbackCreatedWorktrees(branches) {
11895
+ const cleanupErrors = [];
11896
+ for (const branch of [...branches].reverse()) {
11897
+ try {
11898
+ await this.removeWorktree(branch);
11899
+ } catch (error) {
11900
+ cleanupErrors.push(`rollback failed for ${branch}: ${toErrorMessage2(error)}`);
11901
+ }
11902
+ }
11903
+ return cleanupErrors.length > 0 ? cleanupErrors.join("; ") : null;
11904
+ }
11905
+ async createResolvedWorktree(input) {
11906
+ const requestedBaseBranch = input.baseBranch?.trim();
11907
+ if (requestedBaseBranch && !isValidBranchName(requestedBaseBranch)) {
11908
+ throw new LifecycleError("Invalid base branch name", 400);
11909
+ }
11910
+ if (requestedBaseBranch && input.mode === "existing") {
11911
+ throw new LifecycleError("Base branch is only supported for new worktrees", 400);
11912
+ }
11913
+ if (requestedBaseBranch && requestedBaseBranch === input.branch) {
11914
+ throw new LifecycleError("Base branch must differ from branch name", 400);
11915
+ }
11916
+ const baseBranch = input.mode === "new" ? requestedBaseBranch || this.deps.config.workspace.mainBranch : undefined;
11917
+ const branchAvailability = this.resolveBranchAvailability(input.branch, input.mode);
11918
+ const { profileName, profile } = this.resolveProfile(input.profile);
11919
+ const worktreePath = this.resolveWorktreePath(input.branch);
11920
+ const createProgressBase = {
11921
+ branch: input.branch,
11922
+ ...baseBranch ? { baseBranch } : {},
11923
+ path: worktreePath,
11924
+ profile: profileName,
11925
+ agent: input.agent
11926
+ };
11927
+ const deleteBranchOnRollback = input.mode === "new" || branchAvailability.deleteBranchOnRollback;
11928
+ let initialized = null;
11929
+ try {
11930
+ await this.reportCreateProgress({
11931
+ ...createProgressBase,
11932
+ phase: "creating_worktree"
11933
+ });
11934
+ await mkdir4(dirname5(worktreePath), { recursive: true });
11935
+ initialized = await createManagedWorktree({
11936
+ repoRoot: this.deps.projectRoot,
11937
+ worktreePath,
11938
+ branch: input.branch,
11939
+ mode: input.mode,
11940
+ ...baseBranch ? { baseBranch } : {},
11941
+ ...branchAvailability.startPoint ? { startPoint: branchAvailability.startPoint } : {},
11942
+ profile: profileName,
11943
+ agent: input.agent,
11944
+ runtime: profile.runtime,
11945
+ startupEnvValues: await this.buildStartupEnvValues(input.envOverrides),
11946
+ allocatedPorts: await this.allocatePorts(),
11947
+ runtimeEnvExtras: { WEBMUX_WORKTREE_PATH: worktreePath },
11948
+ controlUrl: this.controlUrl(),
11949
+ controlToken: await this.deps.getControlToken(),
11950
+ deleteBranchOnRollback
11951
+ }, {
11952
+ git: this.deps.git
11953
+ });
11954
+ await this.reportCreateProgress({
11955
+ ...createProgressBase,
11956
+ phase: "running_post_create_hook"
11957
+ });
11958
+ await this.runLifecycleHook({
11959
+ name: "postCreate",
11960
+ command: this.deps.config.lifecycleHooks.postCreate,
11961
+ meta: initialized.meta,
11962
+ worktreePath
11963
+ });
11964
+ initialized = await this.refreshManagedArtifactsFromMeta({
11965
+ gitDir: initialized.paths.gitDir,
11966
+ meta: initialized.meta,
11967
+ worktreePath
11968
+ });
11969
+ await this.reportCreateProgress({
11970
+ ...createProgressBase,
11971
+ phase: "preparing_runtime"
11972
+ });
11973
+ await ensureAgentRuntimeArtifacts({
11974
+ gitDir: initialized.paths.gitDir,
11975
+ worktreePath
11976
+ });
11977
+ await this.reportCreateProgress({
11978
+ ...createProgressBase,
11979
+ phase: "starting_session"
11980
+ });
11981
+ await this.materializeRuntimeSession({
11982
+ branch: input.branch,
11983
+ profile,
11984
+ agent: input.agent,
11985
+ initialized,
11986
+ worktreePath,
11987
+ prompt: input.prompt,
11988
+ launchMode: "fresh"
11989
+ });
11990
+ await this.reportCreateProgress({
11991
+ ...createProgressBase,
11992
+ phase: "reconciling"
11993
+ });
11994
+ await this.deps.reconciliation.reconcile(this.deps.projectRoot, { force: true });
11995
+ return {
11996
+ branch: input.branch,
11997
+ worktreeId: initialized.meta.worktreeId
11998
+ };
11999
+ } catch (error) {
12000
+ if (initialized) {
12001
+ const cleanupError = await this.cleanupFailedCreate(input.branch, worktreePath, profile.runtime, deleteBranchOnRollback);
12002
+ if (cleanupError) {
12003
+ throw this.wrapOperationError(new Error(`${toErrorMessage2(error)}; ${cleanupError}`));
12004
+ }
12005
+ }
12006
+ throw this.wrapOperationError(error);
12007
+ } finally {
12008
+ await this.finishCreateProgress(input.branch);
12009
+ }
12010
+ }
11942
12011
  wrapOperationError(error) {
11943
12012
  if (error instanceof LifecycleError) {
11944
12013
  return error;
@@ -12817,6 +12886,9 @@ async function runWorktreeCommand(context, deps2 = {}) {
12817
12886
  stdout(PHASE_LABELS[progress.phase] ?? progress.phase);
12818
12887
  }
12819
12888
  });
12889
+ if (!parsed.input.branch && parsed.input.prompt && runtime2.config.autoName) {
12890
+ stdout("Generating branch name...");
12891
+ }
12820
12892
  const result = await runtime2.lifecycleService.createWorktree(parsed.input);
12821
12893
  stdout(`Created worktree ${result.branch}`);
12822
12894
  if (!parsed.detach) {
@@ -12954,7 +13026,7 @@ import { fileURLToPath } from "url";
12954
13026
  // package.json
12955
13027
  var package_default = {
12956
13028
  name: "webmux",
12957
- version: "0.23.0",
13029
+ version: "0.24.1",
12958
13030
  description: "Web dashboard for workmux \u2014 browser UI with embedded terminals, PR monitoring, and CI integration",
12959
13031
  type: "module",
12960
13032
  repository: {