webmux 0.22.1 → 0.24.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/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
+ "--max-tokens",
10772
+ String(AUTO_NAME_MAX_TOKENS)
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", AUTO_NAME_MAX_TOKENS = 50, DEFAULT_SYSTEM_PROMPT;
10826
10833
  var init_auto_name_service = __esm(() => {
10827
10834
  init_policies();
10828
10835
  DEFAULT_SYSTEM_PROMPT = [
@@ -11131,7 +11138,7 @@ function buildAgentInvocation(input) {
11131
11138
  if (input.launchMode === "resume") {
11132
11139
  return `codex${yoloFlag2} resume --last`;
11133
11140
  }
11134
- const promptSuffix2 = input.prompt ? ` ${quoteShell(input.prompt)}` : "";
11141
+ const promptSuffix2 = input.prompt ? ` -- ${quoteShell(input.prompt)}` : "";
11135
11142
  if (input.systemPrompt) {
11136
11143
  return `codex${yoloFlag2} -c ${quoteShell(`developer_instructions=${input.systemPrompt}`)}${promptSuffix2}`;
11137
11144
  }
@@ -11141,7 +11148,7 @@ function buildAgentInvocation(input) {
11141
11148
  if (input.launchMode === "resume") {
11142
11149
  return `claude${yoloFlag} --continue`;
11143
11150
  }
11144
- const promptSuffix = input.prompt ? ` ${quoteShell(input.prompt)}` : "";
11151
+ const promptSuffix = input.prompt ? ` -- ${quoteShell(input.prompt)}` : "";
11145
11152
  if (input.systemPrompt) {
11146
11153
  return `claude${yoloFlag} --append-system-prompt ${quoteShell(input.systemPrompt)}${promptSuffix}`;
11147
11154
  }
@@ -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;
@@ -12461,6 +12530,7 @@ function createWebmuxRuntime(options = {}) {
12461
12530
  autoName,
12462
12531
  onCreateProgress: (progress) => {
12463
12532
  worktreeCreationTracker.set(progress);
12533
+ options.onCreateProgress?.(progress);
12464
12534
  },
12465
12535
  onCreateFinished: (branch) => {
12466
12536
  worktreeCreationTracker.clear(branch);
@@ -12811,8 +12881,14 @@ async function runWorktreeCommand(context, deps2 = {}) {
12811
12881
  }
12812
12882
  const runtime2 = createRuntime({
12813
12883
  projectDir: context.projectDir,
12814
- port: context.port
12884
+ port: context.port,
12885
+ onCreateProgress: (progress) => {
12886
+ stdout(PHASE_LABELS[progress.phase] ?? progress.phase);
12887
+ }
12815
12888
  });
12889
+ if (!parsed.input.branch && parsed.input.prompt && runtime2.config.autoName) {
12890
+ stdout("Generating branch name...");
12891
+ }
12816
12892
  const result = await runtime2.lifecycleService.createWorktree(parsed.input);
12817
12893
  stdout(`Created worktree ${result.branch}`);
12818
12894
  if (!parsed.detach) {
@@ -12925,13 +13001,20 @@ async function runWorktreeCommand(context, deps2 = {}) {
12925
13001
  return 1;
12926
13002
  }
12927
13003
  }
12928
- var CommandUsageError;
13004
+ var PHASE_LABELS, CommandUsageError;
12929
13005
  var init_worktree_commands = __esm(() => {
12930
13006
  init_dist2();
12931
13007
  init_fs();
12932
13008
  init_tmux();
12933
13009
  init_policies();
12934
13010
  init_runtime();
13011
+ PHASE_LABELS = {
13012
+ creating_worktree: "Creating worktree",
13013
+ running_post_create_hook: "Running post-create hook",
13014
+ preparing_runtime: "Preparing runtime",
13015
+ starting_session: "Starting session",
13016
+ reconciling: "Reconciling"
13017
+ };
12935
13018
  CommandUsageError = class CommandUsageError extends Error {
12936
13019
  };
12937
13020
  });
@@ -12943,7 +13026,7 @@ import { fileURLToPath } from "url";
12943
13026
  // package.json
12944
13027
  var package_default = {
12945
13028
  name: "webmux",
12946
- version: "0.22.1",
13029
+ version: "0.24.0",
12947
13030
  description: "Web dashboard for workmux \u2014 browser UI with embedded terminals, PR monitoring, and CI integration",
12948
13031
  type: "module",
12949
13032
  repository: {