webmux 0.11.0 → 0.13.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/README.md +2 -2
- package/backend/dist/server.js +303 -64
- package/bin/webmux.js +873 -393
- package/frontend/dist/assets/index-Bi9DHlpD.js +34 -0
- package/frontend/dist/assets/index-FwEUWC9Q.css +32 -0
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
- package/frontend/dist/assets/index-BQ3MC-qW.css +0 -32
- package/frontend/dist/assets/index-B_xw0AzA.js +0 -32
package/backend/dist/server.js
CHANGED
|
@@ -6932,7 +6932,7 @@ var log = {
|
|
|
6932
6932
|
// backend/src/adapters/terminal.ts
|
|
6933
6933
|
var textDecoder = new TextDecoder;
|
|
6934
6934
|
var textEncoder = new TextEncoder;
|
|
6935
|
-
var DASH_PORT = Bun.env.
|
|
6935
|
+
var DASH_PORT = Bun.env.PORT || "5111";
|
|
6936
6936
|
var SESSION_PREFIX = `wm-dash-${DASH_PORT}-`;
|
|
6937
6937
|
var MAX_SCROLLBACK_BYTES = 1 * 1024 * 1024;
|
|
6938
6938
|
var TMUX_TIMEOUT_MS = 5000;
|
|
@@ -7413,7 +7413,8 @@ function parseLinkedRepos(raw) {
|
|
|
7413
7413
|
return [];
|
|
7414
7414
|
return raw.filter(isRecord).filter((entry) => typeof entry.repo === "string").map((entry) => ({
|
|
7415
7415
|
repo: entry.repo,
|
|
7416
|
-
alias: typeof entry.alias === "string" ? entry.alias : entry.repo.split("/").pop() ?? "repo"
|
|
7416
|
+
alias: typeof entry.alias === "string" ? entry.alias : entry.repo.split("/").pop() ?? "repo",
|
|
7417
|
+
...typeof entry.dir === "string" && entry.dir.trim() ? { dir: entry.dir.trim() } : {}
|
|
7417
7418
|
}));
|
|
7418
7419
|
}
|
|
7419
7420
|
function isDockerProfile(profile) {
|
|
@@ -7453,7 +7454,7 @@ function loadConfig(dir) {
|
|
|
7453
7454
|
startupEnvs: parseStartupEnvs(parsed.startupEnvs),
|
|
7454
7455
|
integrations: {
|
|
7455
7456
|
github: {
|
|
7456
|
-
linkedRepos: isRecord(parsed.integrations) && isRecord(parsed.integrations.github) ? parseLinkedRepos(parsed.integrations.github.linkedRepos) : []
|
|
7457
|
+
linkedRepos: isRecord(parsed.integrations) && isRecord(parsed.integrations.github) ? parseLinkedRepos(parsed.integrations.github.linkedRepos) : isRecord(parsed.integrations) && Array.isArray(parsed.integrations.github) ? parseLinkedRepos(parsed.integrations.github) : []
|
|
7457
7458
|
},
|
|
7458
7459
|
linear: {
|
|
7459
7460
|
enabled: isRecord(parsed.integrations) && isRecord(parsed.integrations.linear) && typeof parsed.integrations.linear.enabled === "boolean" ? parsed.integrations.linear.enabled : DEFAULT_CONFIG.integrations.linear.enabled
|
|
@@ -8088,9 +8089,9 @@ class BunTmuxGateway {
|
|
|
8088
8089
|
}
|
|
8089
8090
|
ensureSession(sessionName, cwd) {
|
|
8090
8091
|
const check = runTmux(["has-session", "-t", sessionName]);
|
|
8091
|
-
if (check.exitCode
|
|
8092
|
-
|
|
8093
|
-
|
|
8092
|
+
if (check.exitCode !== 0) {
|
|
8093
|
+
assertTmuxOk(["new-session", "-d", "-s", sessionName, "-c", cwd], `create tmux session ${sessionName}`);
|
|
8094
|
+
}
|
|
8094
8095
|
}
|
|
8095
8096
|
hasWindow(sessionName, windowName) {
|
|
8096
8097
|
const result = runTmux(["list-windows", "-t", sessionName, "-F", "#{window_name}"]);
|
|
@@ -8188,15 +8189,22 @@ function buildRuntimeBootstrap(runtimeEnvPath) {
|
|
|
8188
8189
|
return `set -a; . ${quoteShell(runtimeEnvPath)}; set +a`;
|
|
8189
8190
|
}
|
|
8190
8191
|
function buildAgentInvocation(input) {
|
|
8191
|
-
const promptSuffix = input.prompt ? ` ${quoteShell(input.prompt)}` : "";
|
|
8192
8192
|
if (input.agent === "codex") {
|
|
8193
8193
|
const yoloFlag2 = input.yolo ? " --yolo" : "";
|
|
8194
|
+
if (input.launchMode === "resume") {
|
|
8195
|
+
return `codex${yoloFlag2} resume --last`;
|
|
8196
|
+
}
|
|
8197
|
+
const promptSuffix2 = input.prompt ? ` ${quoteShell(input.prompt)}` : "";
|
|
8194
8198
|
if (input.systemPrompt) {
|
|
8195
|
-
return `codex${yoloFlag2} -c ${quoteShell(`developer_instructions=${input.systemPrompt}`)}${
|
|
8199
|
+
return `codex${yoloFlag2} -c ${quoteShell(`developer_instructions=${input.systemPrompt}`)}${promptSuffix2}`;
|
|
8196
8200
|
}
|
|
8197
|
-
return `codex${yoloFlag2}${
|
|
8201
|
+
return `codex${yoloFlag2}${promptSuffix2}`;
|
|
8198
8202
|
}
|
|
8199
8203
|
const yoloFlag = input.yolo ? " --dangerously-skip-permissions" : "";
|
|
8204
|
+
if (input.launchMode === "resume") {
|
|
8205
|
+
return `claude${yoloFlag} --continue`;
|
|
8206
|
+
}
|
|
8207
|
+
const promptSuffix = input.prompt ? ` ${quoteShell(input.prompt)}` : "";
|
|
8200
8208
|
if (input.systemPrompt) {
|
|
8201
8209
|
return `claude${yoloFlag} --append-system-prompt ${quoteShell(input.systemPrompt)}${promptSuffix}`;
|
|
8202
8210
|
}
|
|
@@ -8279,6 +8287,7 @@ function ensureSessionLayout(tmux, plan) {
|
|
|
8279
8287
|
cwd: rootPane.cwd,
|
|
8280
8288
|
command: plan.shellCommand
|
|
8281
8289
|
});
|
|
8290
|
+
tmux.setWindowOption(plan.sessionName, plan.windowName, "pane-base-index", "0");
|
|
8282
8291
|
tmux.setWindowOption(plan.sessionName, plan.windowName, "automatic-rename", "off");
|
|
8283
8292
|
tmux.setWindowOption(plan.sessionName, plan.windowName, "allow-rename", "off");
|
|
8284
8293
|
for (const pane of plan.panes.slice(1)) {
|
|
@@ -8419,6 +8428,11 @@ function listGitWorktrees(cwd) {
|
|
|
8419
8428
|
const output = runGit(["worktree", "list", "--porcelain"], cwd);
|
|
8420
8429
|
return parseGitWorktreePorcelain(output);
|
|
8421
8430
|
}
|
|
8431
|
+
function listLocalGitBranches(cwd) {
|
|
8432
|
+
const output = runGit(["for-each-ref", "--format=%(refname:short)", "refs/heads"], cwd);
|
|
8433
|
+
return output.split(`
|
|
8434
|
+
`).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
8435
|
+
}
|
|
8422
8436
|
function readGitWorktreeStatus(cwd) {
|
|
8423
8437
|
const dirtyOutput = runGit(["status", "--porcelain"], cwd);
|
|
8424
8438
|
const commit = tryRunGit(["rev-parse", "HEAD"], cwd);
|
|
@@ -8460,13 +8474,21 @@ class BunGitGateway {
|
|
|
8460
8474
|
listWorktrees(cwd) {
|
|
8461
8475
|
return listGitWorktrees(cwd);
|
|
8462
8476
|
}
|
|
8477
|
+
listLocalBranches(cwd) {
|
|
8478
|
+
return listLocalGitBranches(cwd);
|
|
8479
|
+
}
|
|
8463
8480
|
readWorktreeStatus(cwd) {
|
|
8464
8481
|
return readGitWorktreeStatus(cwd);
|
|
8465
8482
|
}
|
|
8466
8483
|
createWorktree(opts) {
|
|
8467
|
-
const args = ["worktree", "add"
|
|
8468
|
-
if (opts.
|
|
8469
|
-
args.push(opts.
|
|
8484
|
+
const args = ["worktree", "add"];
|
|
8485
|
+
if (opts.mode === "new") {
|
|
8486
|
+
args.push("-b", opts.branch, opts.worktreePath);
|
|
8487
|
+
if (opts.baseBranch)
|
|
8488
|
+
args.push(opts.baseBranch);
|
|
8489
|
+
} else {
|
|
8490
|
+
args.push(opts.worktreePath, opts.branch);
|
|
8491
|
+
}
|
|
8470
8492
|
runGit(args, opts.repoRoot);
|
|
8471
8493
|
}
|
|
8472
8494
|
removeWorktree(opts) {
|
|
@@ -8545,10 +8567,12 @@ function rollbackManagedWorktreeCreation(opts, sessionLayoutPlan, git, deps) {
|
|
|
8545
8567
|
} catch (error) {
|
|
8546
8568
|
cleanupErrors.push(`worktree rollback failed: ${toErrorMessage(error)}`);
|
|
8547
8569
|
}
|
|
8548
|
-
|
|
8549
|
-
|
|
8550
|
-
|
|
8551
|
-
|
|
8570
|
+
if (opts.deleteBranchOnRollback ?? true) {
|
|
8571
|
+
try {
|
|
8572
|
+
git.deleteBranch(opts.repoRoot, opts.branch, true);
|
|
8573
|
+
} catch (error) {
|
|
8574
|
+
cleanupErrors.push(`branch rollback failed: ${toErrorMessage(error)}`);
|
|
8575
|
+
}
|
|
8552
8576
|
}
|
|
8553
8577
|
return cleanupErrors.length > 0 ? joinErrorMessages(cleanupErrors) : null;
|
|
8554
8578
|
}
|
|
@@ -8598,6 +8622,7 @@ async function createManagedWorktree(opts, deps = {}) {
|
|
|
8598
8622
|
repoRoot: opts.repoRoot,
|
|
8599
8623
|
worktreePath: opts.worktreePath,
|
|
8600
8624
|
branch: opts.branch,
|
|
8625
|
+
mode: opts.mode,
|
|
8601
8626
|
baseBranch: opts.baseBranch
|
|
8602
8627
|
});
|
|
8603
8628
|
worktreeCreated = true;
|
|
@@ -8677,19 +8702,29 @@ class LifecycleService {
|
|
|
8677
8702
|
this.deps = deps;
|
|
8678
8703
|
}
|
|
8679
8704
|
async createWorktree(input) {
|
|
8680
|
-
const
|
|
8681
|
-
this.
|
|
8705
|
+
const mode = input.mode ?? "new";
|
|
8706
|
+
const branch = await this.resolveBranch(input.branch, input.prompt, mode);
|
|
8707
|
+
this.ensureBranchAvailable(branch, mode);
|
|
8682
8708
|
const { profileName, profile } = this.resolveProfile(input.profile);
|
|
8683
8709
|
const agent = this.resolveAgent(input.agent);
|
|
8684
8710
|
const worktreePath = this.resolveWorktreePath(branch);
|
|
8711
|
+
const deleteBranchOnRollback = mode === "new";
|
|
8685
8712
|
let initialized = null;
|
|
8686
8713
|
try {
|
|
8714
|
+
await this.reportCreateProgress({
|
|
8715
|
+
branch,
|
|
8716
|
+
path: worktreePath,
|
|
8717
|
+
profile: profileName,
|
|
8718
|
+
agent,
|
|
8719
|
+
phase: "creating_worktree"
|
|
8720
|
+
});
|
|
8687
8721
|
await mkdir4(dirname3(worktreePath), { recursive: true });
|
|
8688
8722
|
initialized = await createManagedWorktree({
|
|
8689
8723
|
repoRoot: this.deps.projectRoot,
|
|
8690
8724
|
worktreePath,
|
|
8691
8725
|
branch,
|
|
8692
|
-
|
|
8726
|
+
mode,
|
|
8727
|
+
...mode === "new" ? { baseBranch: this.deps.config.workspace.mainBranch } : {},
|
|
8693
8728
|
profile: profileName,
|
|
8694
8729
|
agent,
|
|
8695
8730
|
runtime: profile.runtime,
|
|
@@ -8697,13 +8732,17 @@ class LifecycleService {
|
|
|
8697
8732
|
allocatedPorts: await this.allocatePorts(),
|
|
8698
8733
|
runtimeEnvExtras: { WEBMUX_WORKTREE_PATH: worktreePath },
|
|
8699
8734
|
controlUrl: this.controlUrl(),
|
|
8700
|
-
controlToken: await this.deps.getControlToken()
|
|
8735
|
+
controlToken: await this.deps.getControlToken(),
|
|
8736
|
+
deleteBranchOnRollback
|
|
8701
8737
|
}, {
|
|
8702
8738
|
git: this.deps.git
|
|
8703
8739
|
});
|
|
8704
|
-
await
|
|
8705
|
-
|
|
8706
|
-
worktreePath
|
|
8740
|
+
await this.reportCreateProgress({
|
|
8741
|
+
branch,
|
|
8742
|
+
path: worktreePath,
|
|
8743
|
+
profile: profileName,
|
|
8744
|
+
agent,
|
|
8745
|
+
phase: "running_post_create_hook"
|
|
8707
8746
|
});
|
|
8708
8747
|
await this.runLifecycleHook({
|
|
8709
8748
|
name: "postCreate",
|
|
@@ -8711,13 +8750,44 @@ class LifecycleService {
|
|
|
8711
8750
|
meta: initialized.meta,
|
|
8712
8751
|
worktreePath
|
|
8713
8752
|
});
|
|
8753
|
+
initialized = await this.refreshManagedArtifactsFromMeta({
|
|
8754
|
+
gitDir: initialized.paths.gitDir,
|
|
8755
|
+
meta: initialized.meta,
|
|
8756
|
+
worktreePath
|
|
8757
|
+
});
|
|
8758
|
+
await this.reportCreateProgress({
|
|
8759
|
+
branch,
|
|
8760
|
+
path: worktreePath,
|
|
8761
|
+
profile: profileName,
|
|
8762
|
+
agent,
|
|
8763
|
+
phase: "preparing_runtime"
|
|
8764
|
+
});
|
|
8765
|
+
await ensureAgentRuntimeArtifacts({
|
|
8766
|
+
gitDir: initialized.paths.gitDir,
|
|
8767
|
+
worktreePath
|
|
8768
|
+
});
|
|
8769
|
+
await this.reportCreateProgress({
|
|
8770
|
+
branch,
|
|
8771
|
+
path: worktreePath,
|
|
8772
|
+
profile: profileName,
|
|
8773
|
+
agent,
|
|
8774
|
+
phase: "starting_session"
|
|
8775
|
+
});
|
|
8714
8776
|
await this.materializeRuntimeSession({
|
|
8715
8777
|
branch,
|
|
8716
8778
|
profile,
|
|
8717
8779
|
agent,
|
|
8718
8780
|
initialized,
|
|
8719
8781
|
worktreePath,
|
|
8720
|
-
prompt: input.prompt
|
|
8782
|
+
prompt: input.prompt,
|
|
8783
|
+
launchMode: "fresh"
|
|
8784
|
+
});
|
|
8785
|
+
await this.reportCreateProgress({
|
|
8786
|
+
branch,
|
|
8787
|
+
path: worktreePath,
|
|
8788
|
+
profile: profileName,
|
|
8789
|
+
agent,
|
|
8790
|
+
phase: "reconciling"
|
|
8721
8791
|
});
|
|
8722
8792
|
await this.deps.reconciliation.reconcile(this.deps.projectRoot);
|
|
8723
8793
|
return {
|
|
@@ -8726,17 +8796,20 @@ class LifecycleService {
|
|
|
8726
8796
|
};
|
|
8727
8797
|
} catch (error) {
|
|
8728
8798
|
if (initialized) {
|
|
8729
|
-
const cleanupError = await this.cleanupFailedCreate(branch, worktreePath, profile.runtime);
|
|
8799
|
+
const cleanupError = await this.cleanupFailedCreate(branch, worktreePath, profile.runtime, deleteBranchOnRollback);
|
|
8730
8800
|
if (cleanupError) {
|
|
8731
8801
|
throw this.wrapOperationError(new Error(`${toErrorMessage2(error)}; ${cleanupError}`));
|
|
8732
8802
|
}
|
|
8733
8803
|
}
|
|
8734
8804
|
throw this.wrapOperationError(error);
|
|
8805
|
+
} finally {
|
|
8806
|
+
await this.finishCreateProgress(branch);
|
|
8735
8807
|
}
|
|
8736
8808
|
}
|
|
8737
8809
|
async openWorktree(branch) {
|
|
8738
8810
|
try {
|
|
8739
8811
|
const resolved = await this.resolveExistingWorktree(branch);
|
|
8812
|
+
const launchMode = resolved.meta ? "resume" : "fresh";
|
|
8740
8813
|
const initialized = resolved.meta ? await this.refreshManagedArtifacts(resolved) : await this.initializeUnmanagedWorktree(resolved);
|
|
8741
8814
|
const { profile } = this.resolveProfile(initialized.meta.profile);
|
|
8742
8815
|
await ensureAgentRuntimeArtifacts({
|
|
@@ -8748,7 +8821,8 @@ class LifecycleService {
|
|
|
8748
8821
|
profile,
|
|
8749
8822
|
agent: initialized.meta.agent,
|
|
8750
8823
|
initialized,
|
|
8751
|
-
worktreePath: resolved.entry.path
|
|
8824
|
+
worktreePath: resolved.entry.path,
|
|
8825
|
+
launchMode
|
|
8752
8826
|
});
|
|
8753
8827
|
await this.deps.reconciliation.reconcile(this.deps.projectRoot);
|
|
8754
8828
|
return {
|
|
@@ -8776,6 +8850,20 @@ class LifecycleService {
|
|
|
8776
8850
|
throw this.wrapOperationError(error);
|
|
8777
8851
|
}
|
|
8778
8852
|
}
|
|
8853
|
+
async pruneWorktrees() {
|
|
8854
|
+
try {
|
|
8855
|
+
const resolvedWorktrees = await this.resolveAllWorktrees();
|
|
8856
|
+
const removedBranches = [];
|
|
8857
|
+
for (const resolved of resolvedWorktrees) {
|
|
8858
|
+
const branch = resolved.entry.branch ?? resolved.entry.path;
|
|
8859
|
+
await this.removeResolvedWorktree(resolved);
|
|
8860
|
+
removedBranches.push(branch);
|
|
8861
|
+
}
|
|
8862
|
+
return { removedBranches };
|
|
8863
|
+
} catch (error) {
|
|
8864
|
+
throw this.wrapOperationError(error);
|
|
8865
|
+
}
|
|
8866
|
+
}
|
|
8779
8867
|
async mergeWorktree(branch) {
|
|
8780
8868
|
try {
|
|
8781
8869
|
const resolved = await this.resolveExistingWorktree(branch);
|
|
@@ -8794,9 +8882,17 @@ class LifecycleService {
|
|
|
8794
8882
|
throw this.wrapOperationError(error);
|
|
8795
8883
|
}
|
|
8796
8884
|
}
|
|
8797
|
-
|
|
8885
|
+
listAvailableBranches() {
|
|
8886
|
+
const localBranches = this.listLocalBranches().filter((branch) => isValidBranchName(branch));
|
|
8887
|
+
const checkedOutBranches = this.listCheckedOutBranches();
|
|
8888
|
+
return localBranches.filter((branch) => !checkedOutBranches.has(branch)).sort((left, right) => left.localeCompare(right)).map((name) => ({ name }));
|
|
8889
|
+
}
|
|
8890
|
+
async resolveBranch(rawBranch, prompt, mode) {
|
|
8798
8891
|
const explicitBranch = rawBranch?.trim();
|
|
8799
|
-
const branch = explicitBranch || await this.generateAutoName(prompt) || generateBranchName();
|
|
8892
|
+
const branch = mode === "existing" ? explicitBranch : explicitBranch || await this.generateAutoName(prompt) || generateBranchName();
|
|
8893
|
+
if (!branch) {
|
|
8894
|
+
throw new LifecycleError("Existing branch is required", 400);
|
|
8895
|
+
}
|
|
8800
8896
|
if (!isValidBranchName(branch)) {
|
|
8801
8897
|
throw new LifecycleError(`Invalid branch name: ${branch}`, 400);
|
|
8802
8898
|
}
|
|
@@ -8808,10 +8904,19 @@ class LifecycleService {
|
|
|
8808
8904
|
}
|
|
8809
8905
|
return await this.deps.autoName.generateBranchName(this.deps.config.autoName, prompt);
|
|
8810
8906
|
}
|
|
8811
|
-
ensureBranchAvailable(branch) {
|
|
8812
|
-
const
|
|
8813
|
-
if (
|
|
8814
|
-
|
|
8907
|
+
ensureBranchAvailable(branch, mode) {
|
|
8908
|
+
const localBranches = new Set(this.listLocalBranches());
|
|
8909
|
+
if (mode === "new") {
|
|
8910
|
+
if (localBranches.has(branch)) {
|
|
8911
|
+
throw new LifecycleError(`Branch already exists: ${branch}`, 409);
|
|
8912
|
+
}
|
|
8913
|
+
return;
|
|
8914
|
+
}
|
|
8915
|
+
if (!localBranches.has(branch)) {
|
|
8916
|
+
throw new LifecycleError(`Branch not found: ${branch}`, 404);
|
|
8917
|
+
}
|
|
8918
|
+
if (this.listCheckedOutBranches().has(branch)) {
|
|
8919
|
+
throw new LifecycleError(`Branch already has a worktree: ${branch}`, 409);
|
|
8815
8920
|
}
|
|
8816
8921
|
}
|
|
8817
8922
|
resolveProfile(profileName) {
|
|
@@ -8850,6 +8955,12 @@ class LifecycleService {
|
|
|
8850
8955
|
resolveWorktreePath(branch) {
|
|
8851
8956
|
return resolve3(this.deps.projectRoot, this.deps.config.workspace.worktreeRoot, branch);
|
|
8852
8957
|
}
|
|
8958
|
+
listLocalBranches() {
|
|
8959
|
+
return this.deps.git.listLocalBranches(resolve3(this.deps.projectRoot));
|
|
8960
|
+
}
|
|
8961
|
+
listCheckedOutBranches() {
|
|
8962
|
+
return new Set(this.deps.git.listWorktrees(resolve3(this.deps.projectRoot)).filter((entry) => !entry.bare && entry.branch !== null).map((entry) => entry.branch));
|
|
8963
|
+
}
|
|
8853
8964
|
listProjectWorktrees() {
|
|
8854
8965
|
const projectRoot = resolve3(this.deps.projectRoot);
|
|
8855
8966
|
return this.deps.git.listWorktrees(projectRoot).filter((entry) => !entry.bare && resolve3(entry.path) !== projectRoot);
|
|
@@ -8870,6 +8981,14 @@ class LifecycleService {
|
|
|
8870
8981
|
const meta = await readWorktreeMeta(gitDir);
|
|
8871
8982
|
return { entry, gitDir, meta };
|
|
8872
8983
|
}
|
|
8984
|
+
async resolveAllWorktrees() {
|
|
8985
|
+
const entries = this.listProjectWorktrees().sort((left, right) => (left.branch ?? left.path).localeCompare(right.branch ?? right.path));
|
|
8986
|
+
return await Promise.all(entries.map(async (entry) => {
|
|
8987
|
+
const gitDir = this.deps.git.resolveWorktreeGitDir(entry.path);
|
|
8988
|
+
const meta = await readWorktreeMeta(gitDir);
|
|
8989
|
+
return { entry, gitDir, meta };
|
|
8990
|
+
}));
|
|
8991
|
+
}
|
|
8873
8992
|
async initializeUnmanagedWorktree(resolved) {
|
|
8874
8993
|
const { profileName, profile } = this.resolveProfile(undefined);
|
|
8875
8994
|
const dotenvValues = await loadDotenvLocal(resolved.entry.path);
|
|
@@ -8891,21 +9010,28 @@ class LifecycleService {
|
|
|
8891
9010
|
if (!resolved.meta) {
|
|
8892
9011
|
throw new Error("Missing managed metadata");
|
|
8893
9012
|
}
|
|
8894
|
-
|
|
8895
|
-
|
|
8896
|
-
|
|
9013
|
+
return await this.refreshManagedArtifactsFromMeta({
|
|
9014
|
+
gitDir: resolved.gitDir,
|
|
9015
|
+
meta: resolved.meta,
|
|
9016
|
+
worktreePath: resolved.entry.path
|
|
9017
|
+
});
|
|
9018
|
+
}
|
|
9019
|
+
async refreshManagedArtifactsFromMeta(input) {
|
|
9020
|
+
const dotenvValues = await loadDotenvLocal(input.worktreePath);
|
|
9021
|
+
const runtimeEnv = buildRuntimeEnvMap(input.meta, {
|
|
9022
|
+
WEBMUX_WORKTREE_PATH: input.worktreePath
|
|
8897
9023
|
}, dotenvValues);
|
|
8898
|
-
await writeRuntimeEnv(
|
|
9024
|
+
await writeRuntimeEnv(input.gitDir, runtimeEnv);
|
|
8899
9025
|
const controlEnv = buildControlEnvMap({
|
|
8900
9026
|
controlUrl: this.controlUrl(),
|
|
8901
9027
|
controlToken: await this.deps.getControlToken(),
|
|
8902
|
-
worktreeId:
|
|
8903
|
-
branch:
|
|
9028
|
+
worktreeId: input.meta.worktreeId,
|
|
9029
|
+
branch: input.meta.branch
|
|
8904
9030
|
});
|
|
8905
|
-
await writeControlEnv(
|
|
9031
|
+
await writeControlEnv(input.gitDir, controlEnv);
|
|
8906
9032
|
return {
|
|
8907
|
-
meta:
|
|
8908
|
-
paths: getWorktreeStoragePaths(
|
|
9033
|
+
meta: input.meta,
|
|
9034
|
+
paths: getWorktreeStoragePaths(input.gitDir),
|
|
8909
9035
|
runtimeEnv,
|
|
8910
9036
|
controlEnv
|
|
8911
9037
|
};
|
|
@@ -8928,6 +9054,7 @@ class LifecycleService {
|
|
|
8928
9054
|
initialized: input.initialized,
|
|
8929
9055
|
worktreePath: input.worktreePath,
|
|
8930
9056
|
prompt: input.prompt,
|
|
9057
|
+
launchMode: input.launchMode,
|
|
8931
9058
|
containerName
|
|
8932
9059
|
}));
|
|
8933
9060
|
return;
|
|
@@ -8938,11 +9065,12 @@ class LifecycleService {
|
|
|
8938
9065
|
agent: input.agent,
|
|
8939
9066
|
initialized: input.initialized,
|
|
8940
9067
|
worktreePath: input.worktreePath,
|
|
8941
|
-
prompt: input.prompt
|
|
9068
|
+
prompt: input.prompt,
|
|
9069
|
+
launchMode: input.launchMode
|
|
8942
9070
|
}));
|
|
8943
9071
|
}
|
|
8944
9072
|
buildSessionLayout(input) {
|
|
8945
|
-
const systemPrompt = input.profile.systemPrompt ? expandTemplate(input.profile.systemPrompt, input.initialized.runtimeEnv) : undefined;
|
|
9073
|
+
const systemPrompt = input.launchMode === "fresh" && input.profile.systemPrompt ? expandTemplate(input.profile.systemPrompt, input.initialized.runtimeEnv) : undefined;
|
|
8946
9074
|
const containerName = input.containerName;
|
|
8947
9075
|
return planSessionLayout(this.deps.projectRoot, input.branch, input.profile.panes, {
|
|
8948
9076
|
repoRoot: this.deps.projectRoot,
|
|
@@ -8955,7 +9083,8 @@ class LifecycleService {
|
|
|
8955
9083
|
runtimeEnvPath: input.initialized.paths.runtimeEnvPath,
|
|
8956
9084
|
yolo: input.profile.yolo === true,
|
|
8957
9085
|
systemPrompt,
|
|
8958
|
-
prompt: input.prompt
|
|
9086
|
+
prompt: input.launchMode === "fresh" ? input.prompt : undefined,
|
|
9087
|
+
launchMode: input.launchMode
|
|
8959
9088
|
}),
|
|
8960
9089
|
shell: buildDockerShellCommand(containerName, input.worktreePath, input.initialized.paths.runtimeEnvPath)
|
|
8961
9090
|
} : {
|
|
@@ -8964,7 +9093,8 @@ class LifecycleService {
|
|
|
8964
9093
|
runtimeEnvPath: input.initialized.paths.runtimeEnvPath,
|
|
8965
9094
|
yolo: input.profile.yolo === true,
|
|
8966
9095
|
systemPrompt,
|
|
8967
|
-
prompt: input.prompt
|
|
9096
|
+
prompt: input.launchMode === "fresh" ? input.prompt : undefined,
|
|
9097
|
+
launchMode: input.launchMode
|
|
8968
9098
|
}),
|
|
8969
9099
|
shell: buildManagedShellCommand(input.initialized.paths.runtimeEnvPath)
|
|
8970
9100
|
}
|
|
@@ -8976,7 +9106,7 @@ class LifecycleService {
|
|
|
8976
9106
|
}
|
|
8977
9107
|
return profile;
|
|
8978
9108
|
}
|
|
8979
|
-
async cleanupFailedCreate(branch, worktreePath, runtime) {
|
|
9109
|
+
async cleanupFailedCreate(branch, worktreePath, runtime, deleteBranch) {
|
|
8980
9110
|
const cleanupErrors = [];
|
|
8981
9111
|
if (runtime === "docker") {
|
|
8982
9112
|
try {
|
|
@@ -8996,8 +9126,8 @@ class LifecycleService {
|
|
|
8996
9126
|
worktreePath,
|
|
8997
9127
|
branch,
|
|
8998
9128
|
force: true,
|
|
8999
|
-
deleteBranch
|
|
9000
|
-
deleteBranchForce:
|
|
9129
|
+
deleteBranch,
|
|
9130
|
+
deleteBranchForce: deleteBranch
|
|
9001
9131
|
}, this.deps.git);
|
|
9002
9132
|
} catch (error) {
|
|
9003
9133
|
cleanupErrors.push(`worktree cleanup failed: ${toErrorMessage2(error)}`);
|
|
@@ -9053,6 +9183,12 @@ class LifecycleService {
|
|
|
9053
9183
|
});
|
|
9054
9184
|
console.debug(`[lifecycle-hook] COMPLETED ${input.name}`);
|
|
9055
9185
|
}
|
|
9186
|
+
async reportCreateProgress(progress) {
|
|
9187
|
+
await this.deps.onCreateProgress?.(progress);
|
|
9188
|
+
}
|
|
9189
|
+
async finishCreateProgress(branch) {
|
|
9190
|
+
await this.deps.onCreateFinished?.(branch);
|
|
9191
|
+
}
|
|
9056
9192
|
wrapOperationError(error) {
|
|
9057
9193
|
if (error instanceof LifecycleError) {
|
|
9058
9194
|
return error;
|
|
@@ -9420,7 +9556,12 @@ function clonePrEntry(pr) {
|
|
|
9420
9556
|
comments: pr.comments.map((comment) => ({ ...comment }))
|
|
9421
9557
|
};
|
|
9422
9558
|
}
|
|
9423
|
-
function
|
|
9559
|
+
function mapCreationSnapshot(creating) {
|
|
9560
|
+
return creating ? {
|
|
9561
|
+
phase: creating.phase
|
|
9562
|
+
} : null;
|
|
9563
|
+
}
|
|
9564
|
+
function mapWorktreeSnapshot(state, now, creating, findLinearIssue) {
|
|
9424
9565
|
return {
|
|
9425
9566
|
branch: state.branch,
|
|
9426
9567
|
path: state.path,
|
|
@@ -9430,21 +9571,51 @@ function mapWorktreeSnapshot(state, now, findLinearIssue) {
|
|
|
9430
9571
|
mux: state.session.exists,
|
|
9431
9572
|
dirty: state.git.dirty || state.git.aheadCount > 0,
|
|
9432
9573
|
paneCount: state.session.paneCount,
|
|
9433
|
-
status: state.agent.lifecycle,
|
|
9574
|
+
status: creating ? "creating" : state.agent.lifecycle,
|
|
9434
9575
|
elapsed: formatElapsedSince(state.agent.lastStartedAt, now),
|
|
9435
9576
|
services: state.services.map((service) => ({ ...service })),
|
|
9436
9577
|
prs: state.prs.map((pr) => clonePrEntry(pr)),
|
|
9437
|
-
linearIssue: findLinearIssue ? findLinearIssue(state.branch) : null
|
|
9578
|
+
linearIssue: findLinearIssue ? findLinearIssue(state.branch) : null,
|
|
9579
|
+
creation: mapCreationSnapshot(creating)
|
|
9580
|
+
};
|
|
9581
|
+
}
|
|
9582
|
+
function mapCreatingWorktreeSnapshot(creating, findLinearIssue) {
|
|
9583
|
+
return {
|
|
9584
|
+
branch: creating.branch,
|
|
9585
|
+
path: creating.path,
|
|
9586
|
+
dir: creating.path,
|
|
9587
|
+
profile: creating.profile,
|
|
9588
|
+
agentName: creating.agentName,
|
|
9589
|
+
mux: false,
|
|
9590
|
+
dirty: false,
|
|
9591
|
+
paneCount: 0,
|
|
9592
|
+
status: "creating",
|
|
9593
|
+
elapsed: "",
|
|
9594
|
+
services: [],
|
|
9595
|
+
prs: [],
|
|
9596
|
+
linearIssue: findLinearIssue ? findLinearIssue(creating.branch) : null,
|
|
9597
|
+
creation: mapCreationSnapshot(creating)
|
|
9438
9598
|
};
|
|
9439
9599
|
}
|
|
9440
9600
|
function buildProjectSnapshot(input) {
|
|
9441
9601
|
const now = input.now ?? (() => new Date);
|
|
9602
|
+
const creatingWorktrees = input.creatingWorktrees ?? [];
|
|
9603
|
+
const creatingByBranch = new Map(creatingWorktrees.map((worktree) => [worktree.branch, worktree]));
|
|
9604
|
+
const runtimeWorktrees = input.runtime.listWorktrees();
|
|
9605
|
+
const runtimeBranches = new Set(runtimeWorktrees.map((worktree) => worktree.branch));
|
|
9606
|
+
const worktrees = runtimeWorktrees.map((state) => mapWorktreeSnapshot(state, now, creatingByBranch.get(state.branch) ?? null, input.findLinearIssue));
|
|
9607
|
+
for (const creating of creatingWorktrees) {
|
|
9608
|
+
if (!runtimeBranches.has(creating.branch)) {
|
|
9609
|
+
worktrees.push(mapCreatingWorktreeSnapshot(creating, input.findLinearIssue));
|
|
9610
|
+
}
|
|
9611
|
+
}
|
|
9612
|
+
worktrees.sort((left, right) => left.branch.localeCompare(right.branch));
|
|
9442
9613
|
return {
|
|
9443
9614
|
project: {
|
|
9444
9615
|
name: input.projectName,
|
|
9445
9616
|
mainBranch: input.mainBranch
|
|
9446
9617
|
},
|
|
9447
|
-
worktrees
|
|
9618
|
+
worktrees,
|
|
9448
9619
|
notifications: input.notifications.map((notification) => ({ ...notification }))
|
|
9449
9620
|
};
|
|
9450
9621
|
}
|
|
@@ -9859,16 +10030,14 @@ class BunPortProbe {
|
|
|
9859
10030
|
}
|
|
9860
10031
|
|
|
9861
10032
|
// backend/src/services/auto-name-service.ts
|
|
10033
|
+
var MAX_BRANCH_LENGTH = 40;
|
|
9862
10034
|
var DEFAULT_SYSTEM_PROMPT = [
|
|
9863
10035
|
"Generate a concise git branch name from the task description.",
|
|
9864
10036
|
"Return only the branch name.",
|
|
9865
10037
|
"Use lowercase kebab-case.",
|
|
10038
|
+
`Maximum ${MAX_BRANCH_LENGTH} characters.`,
|
|
9866
10039
|
"Do not include quotes, code fences, or prefixes like feature/ or fix/."
|
|
9867
10040
|
].join(" ");
|
|
9868
|
-
function buildPrompt(task) {
|
|
9869
|
-
return `Task description:
|
|
9870
|
-
${task.trim()}`;
|
|
9871
|
-
}
|
|
9872
10041
|
function normalizeGeneratedBranchName(raw) {
|
|
9873
10042
|
let branch = raw.trim();
|
|
9874
10043
|
branch = branch.replace(/^```[\w-]*\s*/, "").replace(/\s*```$/, "");
|
|
@@ -9880,6 +10049,7 @@ function normalizeGeneratedBranchName(raw) {
|
|
|
9880
10049
|
branch = branch.replace(/[/.]+/g, "-");
|
|
9881
10050
|
branch = branch.replace(/-+/g, "-");
|
|
9882
10051
|
branch = branch.replace(/^-+|-+$/g, "");
|
|
10052
|
+
branch = branch.slice(0, MAX_BRANCH_LENGTH).replace(/-+$/, "");
|
|
9883
10053
|
if (!branch) {
|
|
9884
10054
|
throw new Error("Auto-name model returned an empty branch name");
|
|
9885
10055
|
}
|
|
@@ -9922,6 +10092,9 @@ function buildClaudeArgs(model, systemPrompt, prompt) {
|
|
|
9922
10092
|
function escapeTomlString(s) {
|
|
9923
10093
|
return s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n");
|
|
9924
10094
|
}
|
|
10095
|
+
function buildPrompt(prompt) {
|
|
10096
|
+
return `Here is the task description: ${prompt}. You MUST return the branch name only, no other text or comments. Be fast, make it simple, and concise.`;
|
|
10097
|
+
}
|
|
9925
10098
|
function buildCodexArgs(model, systemPrompt, prompt) {
|
|
9926
10099
|
const args = [
|
|
9927
10100
|
"codex",
|
|
@@ -10348,9 +10521,33 @@ class ReconciliationService {
|
|
|
10348
10521
|
}
|
|
10349
10522
|
}
|
|
10350
10523
|
|
|
10524
|
+
// backend/src/services/worktree-creation-service.ts
|
|
10525
|
+
class WorktreeCreationTracker {
|
|
10526
|
+
worktrees = new Map;
|
|
10527
|
+
set(progress) {
|
|
10528
|
+
const next = {
|
|
10529
|
+
branch: progress.branch,
|
|
10530
|
+
path: progress.path,
|
|
10531
|
+
profile: progress.profile,
|
|
10532
|
+
agentName: progress.agent,
|
|
10533
|
+
phase: progress.phase
|
|
10534
|
+
};
|
|
10535
|
+
this.worktrees.set(progress.branch, next);
|
|
10536
|
+
}
|
|
10537
|
+
clear(branch) {
|
|
10538
|
+
return this.worktrees.delete(branch);
|
|
10539
|
+
}
|
|
10540
|
+
has(branch) {
|
|
10541
|
+
return this.worktrees.has(branch);
|
|
10542
|
+
}
|
|
10543
|
+
list() {
|
|
10544
|
+
return [...this.worktrees.values()].sort((left, right) => left.branch.localeCompare(right.branch)).map((state) => ({ ...state }));
|
|
10545
|
+
}
|
|
10546
|
+
}
|
|
10547
|
+
|
|
10351
10548
|
// backend/src/runtime.ts
|
|
10352
10549
|
function createWebmuxRuntime(options = {}) {
|
|
10353
|
-
const port = options.port ?? parseInt(Bun.env.
|
|
10550
|
+
const port = options.port ?? parseInt(Bun.env.PORT || "5111", 10);
|
|
10354
10551
|
const projectDir = gitRoot(options.projectDir ?? Bun.env.WEBMUX_PROJECT_DIR ?? process.cwd());
|
|
10355
10552
|
const config = loadConfig(projectDir);
|
|
10356
10553
|
const git = new BunGitGateway;
|
|
@@ -10360,6 +10557,7 @@ function createWebmuxRuntime(options = {}) {
|
|
|
10360
10557
|
const hooks = new BunLifecycleHookRunner;
|
|
10361
10558
|
const autoName = new AutoNameService;
|
|
10362
10559
|
const projectRuntime = new ProjectRuntime;
|
|
10560
|
+
const worktreeCreationTracker = new WorktreeCreationTracker;
|
|
10363
10561
|
const runtimeNotifications = new NotificationService;
|
|
10364
10562
|
const reconciliationService = new ReconciliationService({
|
|
10365
10563
|
config,
|
|
@@ -10378,7 +10576,13 @@ function createWebmuxRuntime(options = {}) {
|
|
|
10378
10576
|
docker,
|
|
10379
10577
|
reconciliation: reconciliationService,
|
|
10380
10578
|
hooks,
|
|
10381
|
-
autoName
|
|
10579
|
+
autoName,
|
|
10580
|
+
onCreateProgress: (progress) => {
|
|
10581
|
+
worktreeCreationTracker.set(progress);
|
|
10582
|
+
},
|
|
10583
|
+
onCreateFinished: (branch) => {
|
|
10584
|
+
worktreeCreationTracker.clear(branch);
|
|
10585
|
+
}
|
|
10382
10586
|
});
|
|
10383
10587
|
return {
|
|
10384
10588
|
port,
|
|
@@ -10391,6 +10595,7 @@ function createWebmuxRuntime(options = {}) {
|
|
|
10391
10595
|
hooks,
|
|
10392
10596
|
autoName,
|
|
10393
10597
|
projectRuntime,
|
|
10598
|
+
worktreeCreationTracker,
|
|
10394
10599
|
runtimeNotifications,
|
|
10395
10600
|
reconciliationService,
|
|
10396
10601
|
lifecycleService
|
|
@@ -10398,7 +10603,7 @@ function createWebmuxRuntime(options = {}) {
|
|
|
10398
10603
|
}
|
|
10399
10604
|
|
|
10400
10605
|
// backend/src/server.ts
|
|
10401
|
-
var PORT = parseInt(Bun.env.
|
|
10606
|
+
var PORT = parseInt(Bun.env.PORT || "5111", 10);
|
|
10402
10607
|
var STATIC_DIR = Bun.env.WEBMUX_STATIC_DIR || "";
|
|
10403
10608
|
var runtime = createWebmuxRuntime({
|
|
10404
10609
|
port: PORT,
|
|
@@ -10409,6 +10614,7 @@ var config = runtime.config;
|
|
|
10409
10614
|
var git = runtime.git;
|
|
10410
10615
|
var tmux = runtime.tmux;
|
|
10411
10616
|
var projectRuntime = runtime.projectRuntime;
|
|
10617
|
+
var worktreeCreationTracker = runtime.worktreeCreationTracker;
|
|
10412
10618
|
var runtimeNotifications = runtime.runtimeNotifications;
|
|
10413
10619
|
var reconciliationService = runtime.reconciliationService;
|
|
10414
10620
|
var removingBranches = new Set;
|
|
@@ -10431,7 +10637,11 @@ function getFrontendConfig() {
|
|
|
10431
10637
|
})),
|
|
10432
10638
|
defaultProfileName,
|
|
10433
10639
|
autoName: config.autoName !== null,
|
|
10434
|
-
startupEnvs: config.startupEnvs
|
|
10640
|
+
startupEnvs: config.startupEnvs,
|
|
10641
|
+
linkedRepos: config.integrations.github.linkedRepos.map((lr) => ({
|
|
10642
|
+
alias: lr.alias,
|
|
10643
|
+
...lr.dir ? { dir: resolve5(PROJECT_DIR, lr.dir) } : {}
|
|
10644
|
+
}))
|
|
10435
10645
|
};
|
|
10436
10646
|
}
|
|
10437
10647
|
function parseWsMessage(raw) {
|
|
@@ -10489,8 +10699,17 @@ function ensureBranchNotRemoving(branch) {
|
|
|
10489
10699
|
throw new LifecycleError(`Worktree is being removed: ${branch}`, 409);
|
|
10490
10700
|
}
|
|
10491
10701
|
}
|
|
10492
|
-
|
|
10702
|
+
function ensureBranchNotCreating(branch) {
|
|
10703
|
+
if (worktreeCreationTracker.has(branch)) {
|
|
10704
|
+
throw new LifecycleError(`Worktree is being created: ${branch}`, 409);
|
|
10705
|
+
}
|
|
10706
|
+
}
|
|
10707
|
+
function ensureBranchNotBusy(branch) {
|
|
10493
10708
|
ensureBranchNotRemoving(branch);
|
|
10709
|
+
ensureBranchNotCreating(branch);
|
|
10710
|
+
}
|
|
10711
|
+
async function withRemovingBranch(branch, fn) {
|
|
10712
|
+
ensureBranchNotBusy(branch);
|
|
10494
10713
|
removingBranches.add(branch);
|
|
10495
10714
|
try {
|
|
10496
10715
|
return await fn();
|
|
@@ -10499,7 +10718,7 @@ async function withRemovingBranch(branch, fn) {
|
|
|
10499
10718
|
}
|
|
10500
10719
|
}
|
|
10501
10720
|
async function resolveTerminalWorktree(branch) {
|
|
10502
|
-
|
|
10721
|
+
ensureBranchNotBusy(branch);
|
|
10503
10722
|
await reconciliationService.reconcile(PROJECT_DIR);
|
|
10504
10723
|
const state = projectRuntime.getWorktreeByBranch(branch);
|
|
10505
10724
|
if (!state) {
|
|
@@ -10562,6 +10781,7 @@ async function apiGetProject() {
|
|
|
10562
10781
|
projectName: config.name,
|
|
10563
10782
|
mainBranch: config.workspace.mainBranch,
|
|
10564
10783
|
runtime: projectRuntime,
|
|
10784
|
+
creatingWorktrees: worktreeCreationTracker.list(),
|
|
10565
10785
|
notifications: runtimeNotifications.list(),
|
|
10566
10786
|
findLinearIssue: (branch) => {
|
|
10567
10787
|
const match = linearIssues.find((issue) => branchMatchesIssue(branch, issue.branchName));
|
|
@@ -10602,6 +10822,11 @@ async function apiRuntimeEvent(req) {
|
|
|
10602
10822
|
...notification ? { notification } : {}
|
|
10603
10823
|
});
|
|
10604
10824
|
}
|
|
10825
|
+
async function apiListBranches() {
|
|
10826
|
+
return jsonResponse({
|
|
10827
|
+
branches: lifecycleService.listAvailableBranches()
|
|
10828
|
+
});
|
|
10829
|
+
}
|
|
10605
10830
|
async function apiCreateWorktree(req) {
|
|
10606
10831
|
const raw = await req.json();
|
|
10607
10832
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
@@ -10623,8 +10848,16 @@ async function apiCreateWorktree(req) {
|
|
|
10623
10848
|
const prompt = typeof body.prompt === "string" ? body.prompt : undefined;
|
|
10624
10849
|
const profile = typeof body.profile === "string" ? body.profile : undefined;
|
|
10625
10850
|
const agent = body.agent === "claude" || body.agent === "codex" ? body.agent : undefined;
|
|
10626
|
-
|
|
10851
|
+
const mode = body.mode;
|
|
10852
|
+
if (mode !== undefined && mode !== "new" && mode !== "existing") {
|
|
10853
|
+
return errorResponse("Invalid worktree create mode", 400);
|
|
10854
|
+
}
|
|
10855
|
+
if (branch) {
|
|
10856
|
+
ensureBranchNotCreating(branch);
|
|
10857
|
+
}
|
|
10858
|
+
log.info(`[worktree:add] mode=${mode ?? "new"}${branch ? ` branch=${branch}` : ""}${profile ? ` profile=${profile}` : ""}${agent ? ` agent=${agent}` : ""}${prompt ? ` prompt="${prompt.slice(0, 80)}"` : ""}`);
|
|
10627
10859
|
const result = await lifecycleService.createWorktree({
|
|
10860
|
+
mode,
|
|
10628
10861
|
branch,
|
|
10629
10862
|
prompt,
|
|
10630
10863
|
profile,
|
|
@@ -10643,19 +10876,21 @@ async function apiDeleteWorktree(name) {
|
|
|
10643
10876
|
});
|
|
10644
10877
|
}
|
|
10645
10878
|
async function apiOpenWorktree(name) {
|
|
10646
|
-
|
|
10879
|
+
ensureBranchNotBusy(name);
|
|
10647
10880
|
log.info(`[worktree:open] name=${name}`);
|
|
10648
10881
|
const result = await lifecycleService.openWorktree(name);
|
|
10649
10882
|
log.debug(`[worktree:open] done name=${name} worktreeId=${result.worktreeId}`);
|
|
10650
10883
|
return jsonResponse({ ok: true });
|
|
10651
10884
|
}
|
|
10652
10885
|
async function apiCloseWorktree(name) {
|
|
10886
|
+
ensureBranchNotBusy(name);
|
|
10653
10887
|
log.info(`[worktree:close] name=${name}`);
|
|
10654
10888
|
await lifecycleService.closeWorktree(name);
|
|
10655
10889
|
log.debug(`[worktree:close] done name=${name}`);
|
|
10656
10890
|
return jsonResponse({ ok: true });
|
|
10657
10891
|
}
|
|
10658
10892
|
async function apiSendPrompt(name, req) {
|
|
10893
|
+
ensureBranchNotBusy(name);
|
|
10659
10894
|
const raw = await req.json();
|
|
10660
10895
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
10661
10896
|
return errorResponse("Invalid request body", 400);
|
|
@@ -10673,6 +10908,7 @@ async function apiSendPrompt(name, req) {
|
|
|
10673
10908
|
return jsonResponse({ ok: true });
|
|
10674
10909
|
}
|
|
10675
10910
|
async function apiMergeWorktree(name) {
|
|
10911
|
+
ensureBranchNotBusy(name);
|
|
10676
10912
|
log.info(`[worktree:merge] name=${name}`);
|
|
10677
10913
|
await lifecycleService.mergeWorktree(name);
|
|
10678
10914
|
log.debug(`[worktree:merge] done name=${name}`);
|
|
@@ -10710,6 +10946,9 @@ Bun.serve({
|
|
|
10710
10946
|
"/api/config": {
|
|
10711
10947
|
GET: () => jsonResponse(getFrontendConfig())
|
|
10712
10948
|
},
|
|
10949
|
+
"/api/branches": {
|
|
10950
|
+
GET: () => catching("GET /api/branches", () => apiListBranches())
|
|
10951
|
+
},
|
|
10713
10952
|
"/api/project": {
|
|
10714
10953
|
GET: () => catching("GET /api/project", () => apiGetProject())
|
|
10715
10954
|
},
|