webmux 0.11.0 → 0.12.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 +180 -33
- package/bin/webmux.js +715 -370
- package/frontend/dist/assets/index-DIWwx16E.css +32 -0
- package/frontend/dist/assets/index-Pz_SK2_d.js +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/README.md
CHANGED
|
@@ -89,7 +89,7 @@ workspace:
|
|
|
89
89
|
|
|
90
90
|
services:
|
|
91
91
|
- name: BE
|
|
92
|
-
portEnv:
|
|
92
|
+
portEnv: PORT
|
|
93
93
|
portStart: 5111
|
|
94
94
|
portStep: 10
|
|
95
95
|
- name: FE
|
|
@@ -124,7 +124,7 @@ profiles:
|
|
|
124
124
|
writable: true
|
|
125
125
|
systemPrompt: >
|
|
126
126
|
You are running inside a sandboxed container.
|
|
127
|
-
Backend port: ${
|
|
127
|
+
Backend port: ${PORT}. Frontend port: ${FRONTEND_PORT}.
|
|
128
128
|
|
|
129
129
|
linkedRepos:
|
|
130
130
|
- repo: myorg/related-service
|
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,10 @@ 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
|
+
}
|
|
8095
|
+
assertTmuxOk(["set-option", "-t", sessionName, "pane-base-index", "0"], `set pane-base-index on ${sessionName}`);
|
|
8094
8096
|
}
|
|
8095
8097
|
hasWindow(sessionName, windowName) {
|
|
8096
8098
|
const result = runTmux(["list-windows", "-t", sessionName, "-F", "#{window_name}"]);
|
|
@@ -8684,6 +8686,13 @@ class LifecycleService {
|
|
|
8684
8686
|
const worktreePath = this.resolveWorktreePath(branch);
|
|
8685
8687
|
let initialized = null;
|
|
8686
8688
|
try {
|
|
8689
|
+
await this.reportCreateProgress({
|
|
8690
|
+
branch,
|
|
8691
|
+
path: worktreePath,
|
|
8692
|
+
profile: profileName,
|
|
8693
|
+
agent,
|
|
8694
|
+
phase: "creating_worktree"
|
|
8695
|
+
});
|
|
8687
8696
|
await mkdir4(dirname3(worktreePath), { recursive: true });
|
|
8688
8697
|
initialized = await createManagedWorktree({
|
|
8689
8698
|
repoRoot: this.deps.projectRoot,
|
|
@@ -8701,9 +8710,12 @@ class LifecycleService {
|
|
|
8701
8710
|
}, {
|
|
8702
8711
|
git: this.deps.git
|
|
8703
8712
|
});
|
|
8704
|
-
await
|
|
8705
|
-
|
|
8706
|
-
worktreePath
|
|
8713
|
+
await this.reportCreateProgress({
|
|
8714
|
+
branch,
|
|
8715
|
+
path: worktreePath,
|
|
8716
|
+
profile: profileName,
|
|
8717
|
+
agent,
|
|
8718
|
+
phase: "running_post_create_hook"
|
|
8707
8719
|
});
|
|
8708
8720
|
await this.runLifecycleHook({
|
|
8709
8721
|
name: "postCreate",
|
|
@@ -8711,6 +8723,29 @@ class LifecycleService {
|
|
|
8711
8723
|
meta: initialized.meta,
|
|
8712
8724
|
worktreePath
|
|
8713
8725
|
});
|
|
8726
|
+
initialized = await this.refreshManagedArtifactsFromMeta({
|
|
8727
|
+
gitDir: initialized.paths.gitDir,
|
|
8728
|
+
meta: initialized.meta,
|
|
8729
|
+
worktreePath
|
|
8730
|
+
});
|
|
8731
|
+
await this.reportCreateProgress({
|
|
8732
|
+
branch,
|
|
8733
|
+
path: worktreePath,
|
|
8734
|
+
profile: profileName,
|
|
8735
|
+
agent,
|
|
8736
|
+
phase: "preparing_runtime"
|
|
8737
|
+
});
|
|
8738
|
+
await ensureAgentRuntimeArtifacts({
|
|
8739
|
+
gitDir: initialized.paths.gitDir,
|
|
8740
|
+
worktreePath
|
|
8741
|
+
});
|
|
8742
|
+
await this.reportCreateProgress({
|
|
8743
|
+
branch,
|
|
8744
|
+
path: worktreePath,
|
|
8745
|
+
profile: profileName,
|
|
8746
|
+
agent,
|
|
8747
|
+
phase: "starting_session"
|
|
8748
|
+
});
|
|
8714
8749
|
await this.materializeRuntimeSession({
|
|
8715
8750
|
branch,
|
|
8716
8751
|
profile,
|
|
@@ -8719,6 +8754,13 @@ class LifecycleService {
|
|
|
8719
8754
|
worktreePath,
|
|
8720
8755
|
prompt: input.prompt
|
|
8721
8756
|
});
|
|
8757
|
+
await this.reportCreateProgress({
|
|
8758
|
+
branch,
|
|
8759
|
+
path: worktreePath,
|
|
8760
|
+
profile: profileName,
|
|
8761
|
+
agent,
|
|
8762
|
+
phase: "reconciling"
|
|
8763
|
+
});
|
|
8722
8764
|
await this.deps.reconciliation.reconcile(this.deps.projectRoot);
|
|
8723
8765
|
return {
|
|
8724
8766
|
branch,
|
|
@@ -8732,6 +8774,8 @@ class LifecycleService {
|
|
|
8732
8774
|
}
|
|
8733
8775
|
}
|
|
8734
8776
|
throw this.wrapOperationError(error);
|
|
8777
|
+
} finally {
|
|
8778
|
+
await this.finishCreateProgress(branch);
|
|
8735
8779
|
}
|
|
8736
8780
|
}
|
|
8737
8781
|
async openWorktree(branch) {
|
|
@@ -8891,21 +8935,28 @@ class LifecycleService {
|
|
|
8891
8935
|
if (!resolved.meta) {
|
|
8892
8936
|
throw new Error("Missing managed metadata");
|
|
8893
8937
|
}
|
|
8894
|
-
|
|
8895
|
-
|
|
8896
|
-
|
|
8938
|
+
return await this.refreshManagedArtifactsFromMeta({
|
|
8939
|
+
gitDir: resolved.gitDir,
|
|
8940
|
+
meta: resolved.meta,
|
|
8941
|
+
worktreePath: resolved.entry.path
|
|
8942
|
+
});
|
|
8943
|
+
}
|
|
8944
|
+
async refreshManagedArtifactsFromMeta(input) {
|
|
8945
|
+
const dotenvValues = await loadDotenvLocal(input.worktreePath);
|
|
8946
|
+
const runtimeEnv = buildRuntimeEnvMap(input.meta, {
|
|
8947
|
+
WEBMUX_WORKTREE_PATH: input.worktreePath
|
|
8897
8948
|
}, dotenvValues);
|
|
8898
|
-
await writeRuntimeEnv(
|
|
8949
|
+
await writeRuntimeEnv(input.gitDir, runtimeEnv);
|
|
8899
8950
|
const controlEnv = buildControlEnvMap({
|
|
8900
8951
|
controlUrl: this.controlUrl(),
|
|
8901
8952
|
controlToken: await this.deps.getControlToken(),
|
|
8902
|
-
worktreeId:
|
|
8903
|
-
branch:
|
|
8953
|
+
worktreeId: input.meta.worktreeId,
|
|
8954
|
+
branch: input.meta.branch
|
|
8904
8955
|
});
|
|
8905
|
-
await writeControlEnv(
|
|
8956
|
+
await writeControlEnv(input.gitDir, controlEnv);
|
|
8906
8957
|
return {
|
|
8907
|
-
meta:
|
|
8908
|
-
paths: getWorktreeStoragePaths(
|
|
8958
|
+
meta: input.meta,
|
|
8959
|
+
paths: getWorktreeStoragePaths(input.gitDir),
|
|
8909
8960
|
runtimeEnv,
|
|
8910
8961
|
controlEnv
|
|
8911
8962
|
};
|
|
@@ -9053,6 +9104,12 @@ class LifecycleService {
|
|
|
9053
9104
|
});
|
|
9054
9105
|
console.debug(`[lifecycle-hook] COMPLETED ${input.name}`);
|
|
9055
9106
|
}
|
|
9107
|
+
async reportCreateProgress(progress) {
|
|
9108
|
+
await this.deps.onCreateProgress?.(progress);
|
|
9109
|
+
}
|
|
9110
|
+
async finishCreateProgress(branch) {
|
|
9111
|
+
await this.deps.onCreateFinished?.(branch);
|
|
9112
|
+
}
|
|
9056
9113
|
wrapOperationError(error) {
|
|
9057
9114
|
if (error instanceof LifecycleError) {
|
|
9058
9115
|
return error;
|
|
@@ -9420,7 +9477,12 @@ function clonePrEntry(pr) {
|
|
|
9420
9477
|
comments: pr.comments.map((comment) => ({ ...comment }))
|
|
9421
9478
|
};
|
|
9422
9479
|
}
|
|
9423
|
-
function
|
|
9480
|
+
function mapCreationSnapshot(creating) {
|
|
9481
|
+
return creating ? {
|
|
9482
|
+
phase: creating.phase
|
|
9483
|
+
} : null;
|
|
9484
|
+
}
|
|
9485
|
+
function mapWorktreeSnapshot(state, now, creating, findLinearIssue) {
|
|
9424
9486
|
return {
|
|
9425
9487
|
branch: state.branch,
|
|
9426
9488
|
path: state.path,
|
|
@@ -9430,21 +9492,51 @@ function mapWorktreeSnapshot(state, now, findLinearIssue) {
|
|
|
9430
9492
|
mux: state.session.exists,
|
|
9431
9493
|
dirty: state.git.dirty || state.git.aheadCount > 0,
|
|
9432
9494
|
paneCount: state.session.paneCount,
|
|
9433
|
-
status: state.agent.lifecycle,
|
|
9495
|
+
status: creating ? "creating" : state.agent.lifecycle,
|
|
9434
9496
|
elapsed: formatElapsedSince(state.agent.lastStartedAt, now),
|
|
9435
9497
|
services: state.services.map((service) => ({ ...service })),
|
|
9436
9498
|
prs: state.prs.map((pr) => clonePrEntry(pr)),
|
|
9437
|
-
linearIssue: findLinearIssue ? findLinearIssue(state.branch) : null
|
|
9499
|
+
linearIssue: findLinearIssue ? findLinearIssue(state.branch) : null,
|
|
9500
|
+
creation: mapCreationSnapshot(creating)
|
|
9501
|
+
};
|
|
9502
|
+
}
|
|
9503
|
+
function mapCreatingWorktreeSnapshot(creating, findLinearIssue) {
|
|
9504
|
+
return {
|
|
9505
|
+
branch: creating.branch,
|
|
9506
|
+
path: creating.path,
|
|
9507
|
+
dir: creating.path,
|
|
9508
|
+
profile: creating.profile,
|
|
9509
|
+
agentName: creating.agentName,
|
|
9510
|
+
mux: false,
|
|
9511
|
+
dirty: false,
|
|
9512
|
+
paneCount: 0,
|
|
9513
|
+
status: "creating",
|
|
9514
|
+
elapsed: "",
|
|
9515
|
+
services: [],
|
|
9516
|
+
prs: [],
|
|
9517
|
+
linearIssue: findLinearIssue ? findLinearIssue(creating.branch) : null,
|
|
9518
|
+
creation: mapCreationSnapshot(creating)
|
|
9438
9519
|
};
|
|
9439
9520
|
}
|
|
9440
9521
|
function buildProjectSnapshot(input) {
|
|
9441
9522
|
const now = input.now ?? (() => new Date);
|
|
9523
|
+
const creatingWorktrees = input.creatingWorktrees ?? [];
|
|
9524
|
+
const creatingByBranch = new Map(creatingWorktrees.map((worktree) => [worktree.branch, worktree]));
|
|
9525
|
+
const runtimeWorktrees = input.runtime.listWorktrees();
|
|
9526
|
+
const runtimeBranches = new Set(runtimeWorktrees.map((worktree) => worktree.branch));
|
|
9527
|
+
const worktrees = runtimeWorktrees.map((state) => mapWorktreeSnapshot(state, now, creatingByBranch.get(state.branch) ?? null, input.findLinearIssue));
|
|
9528
|
+
for (const creating of creatingWorktrees) {
|
|
9529
|
+
if (!runtimeBranches.has(creating.branch)) {
|
|
9530
|
+
worktrees.push(mapCreatingWorktreeSnapshot(creating, input.findLinearIssue));
|
|
9531
|
+
}
|
|
9532
|
+
}
|
|
9533
|
+
worktrees.sort((left, right) => left.branch.localeCompare(right.branch));
|
|
9442
9534
|
return {
|
|
9443
9535
|
project: {
|
|
9444
9536
|
name: input.projectName,
|
|
9445
9537
|
mainBranch: input.mainBranch
|
|
9446
9538
|
},
|
|
9447
|
-
worktrees
|
|
9539
|
+
worktrees,
|
|
9448
9540
|
notifications: input.notifications.map((notification) => ({ ...notification }))
|
|
9449
9541
|
};
|
|
9450
9542
|
}
|
|
@@ -9859,16 +9951,14 @@ class BunPortProbe {
|
|
|
9859
9951
|
}
|
|
9860
9952
|
|
|
9861
9953
|
// backend/src/services/auto-name-service.ts
|
|
9954
|
+
var MAX_BRANCH_LENGTH = 40;
|
|
9862
9955
|
var DEFAULT_SYSTEM_PROMPT = [
|
|
9863
9956
|
"Generate a concise git branch name from the task description.",
|
|
9864
9957
|
"Return only the branch name.",
|
|
9865
9958
|
"Use lowercase kebab-case.",
|
|
9959
|
+
`Maximum ${MAX_BRANCH_LENGTH} characters.`,
|
|
9866
9960
|
"Do not include quotes, code fences, or prefixes like feature/ or fix/."
|
|
9867
9961
|
].join(" ");
|
|
9868
|
-
function buildPrompt(task) {
|
|
9869
|
-
return `Task description:
|
|
9870
|
-
${task.trim()}`;
|
|
9871
|
-
}
|
|
9872
9962
|
function normalizeGeneratedBranchName(raw) {
|
|
9873
9963
|
let branch = raw.trim();
|
|
9874
9964
|
branch = branch.replace(/^```[\w-]*\s*/, "").replace(/\s*```$/, "");
|
|
@@ -9880,6 +9970,7 @@ function normalizeGeneratedBranchName(raw) {
|
|
|
9880
9970
|
branch = branch.replace(/[/.]+/g, "-");
|
|
9881
9971
|
branch = branch.replace(/-+/g, "-");
|
|
9882
9972
|
branch = branch.replace(/^-+|-+$/g, "");
|
|
9973
|
+
branch = branch.slice(0, MAX_BRANCH_LENGTH).replace(/-+$/, "");
|
|
9883
9974
|
if (!branch) {
|
|
9884
9975
|
throw new Error("Auto-name model returned an empty branch name");
|
|
9885
9976
|
}
|
|
@@ -9922,6 +10013,9 @@ function buildClaudeArgs(model, systemPrompt, prompt) {
|
|
|
9922
10013
|
function escapeTomlString(s) {
|
|
9923
10014
|
return s.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n");
|
|
9924
10015
|
}
|
|
10016
|
+
function buildPrompt(prompt) {
|
|
10017
|
+
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.`;
|
|
10018
|
+
}
|
|
9925
10019
|
function buildCodexArgs(model, systemPrompt, prompt) {
|
|
9926
10020
|
const args = [
|
|
9927
10021
|
"codex",
|
|
@@ -10348,9 +10442,33 @@ class ReconciliationService {
|
|
|
10348
10442
|
}
|
|
10349
10443
|
}
|
|
10350
10444
|
|
|
10445
|
+
// backend/src/services/worktree-creation-service.ts
|
|
10446
|
+
class WorktreeCreationTracker {
|
|
10447
|
+
worktrees = new Map;
|
|
10448
|
+
set(progress) {
|
|
10449
|
+
const next = {
|
|
10450
|
+
branch: progress.branch,
|
|
10451
|
+
path: progress.path,
|
|
10452
|
+
profile: progress.profile,
|
|
10453
|
+
agentName: progress.agent,
|
|
10454
|
+
phase: progress.phase
|
|
10455
|
+
};
|
|
10456
|
+
this.worktrees.set(progress.branch, next);
|
|
10457
|
+
}
|
|
10458
|
+
clear(branch) {
|
|
10459
|
+
return this.worktrees.delete(branch);
|
|
10460
|
+
}
|
|
10461
|
+
has(branch) {
|
|
10462
|
+
return this.worktrees.has(branch);
|
|
10463
|
+
}
|
|
10464
|
+
list() {
|
|
10465
|
+
return [...this.worktrees.values()].sort((left, right) => left.branch.localeCompare(right.branch)).map((state) => ({ ...state }));
|
|
10466
|
+
}
|
|
10467
|
+
}
|
|
10468
|
+
|
|
10351
10469
|
// backend/src/runtime.ts
|
|
10352
10470
|
function createWebmuxRuntime(options = {}) {
|
|
10353
|
-
const port = options.port ?? parseInt(Bun.env.
|
|
10471
|
+
const port = options.port ?? parseInt(Bun.env.PORT || "5111", 10);
|
|
10354
10472
|
const projectDir = gitRoot(options.projectDir ?? Bun.env.WEBMUX_PROJECT_DIR ?? process.cwd());
|
|
10355
10473
|
const config = loadConfig(projectDir);
|
|
10356
10474
|
const git = new BunGitGateway;
|
|
@@ -10360,6 +10478,7 @@ function createWebmuxRuntime(options = {}) {
|
|
|
10360
10478
|
const hooks = new BunLifecycleHookRunner;
|
|
10361
10479
|
const autoName = new AutoNameService;
|
|
10362
10480
|
const projectRuntime = new ProjectRuntime;
|
|
10481
|
+
const worktreeCreationTracker = new WorktreeCreationTracker;
|
|
10363
10482
|
const runtimeNotifications = new NotificationService;
|
|
10364
10483
|
const reconciliationService = new ReconciliationService({
|
|
10365
10484
|
config,
|
|
@@ -10378,7 +10497,13 @@ function createWebmuxRuntime(options = {}) {
|
|
|
10378
10497
|
docker,
|
|
10379
10498
|
reconciliation: reconciliationService,
|
|
10380
10499
|
hooks,
|
|
10381
|
-
autoName
|
|
10500
|
+
autoName,
|
|
10501
|
+
onCreateProgress: (progress) => {
|
|
10502
|
+
worktreeCreationTracker.set(progress);
|
|
10503
|
+
},
|
|
10504
|
+
onCreateFinished: (branch) => {
|
|
10505
|
+
worktreeCreationTracker.clear(branch);
|
|
10506
|
+
}
|
|
10382
10507
|
});
|
|
10383
10508
|
return {
|
|
10384
10509
|
port,
|
|
@@ -10391,6 +10516,7 @@ function createWebmuxRuntime(options = {}) {
|
|
|
10391
10516
|
hooks,
|
|
10392
10517
|
autoName,
|
|
10393
10518
|
projectRuntime,
|
|
10519
|
+
worktreeCreationTracker,
|
|
10394
10520
|
runtimeNotifications,
|
|
10395
10521
|
reconciliationService,
|
|
10396
10522
|
lifecycleService
|
|
@@ -10398,7 +10524,7 @@ function createWebmuxRuntime(options = {}) {
|
|
|
10398
10524
|
}
|
|
10399
10525
|
|
|
10400
10526
|
// backend/src/server.ts
|
|
10401
|
-
var PORT = parseInt(Bun.env.
|
|
10527
|
+
var PORT = parseInt(Bun.env.PORT || "5111", 10);
|
|
10402
10528
|
var STATIC_DIR = Bun.env.WEBMUX_STATIC_DIR || "";
|
|
10403
10529
|
var runtime = createWebmuxRuntime({
|
|
10404
10530
|
port: PORT,
|
|
@@ -10409,6 +10535,7 @@ var config = runtime.config;
|
|
|
10409
10535
|
var git = runtime.git;
|
|
10410
10536
|
var tmux = runtime.tmux;
|
|
10411
10537
|
var projectRuntime = runtime.projectRuntime;
|
|
10538
|
+
var worktreeCreationTracker = runtime.worktreeCreationTracker;
|
|
10412
10539
|
var runtimeNotifications = runtime.runtimeNotifications;
|
|
10413
10540
|
var reconciliationService = runtime.reconciliationService;
|
|
10414
10541
|
var removingBranches = new Set;
|
|
@@ -10431,7 +10558,11 @@ function getFrontendConfig() {
|
|
|
10431
10558
|
})),
|
|
10432
10559
|
defaultProfileName,
|
|
10433
10560
|
autoName: config.autoName !== null,
|
|
10434
|
-
startupEnvs: config.startupEnvs
|
|
10561
|
+
startupEnvs: config.startupEnvs,
|
|
10562
|
+
linkedRepos: config.integrations.github.linkedRepos.map((lr) => ({
|
|
10563
|
+
alias: lr.alias,
|
|
10564
|
+
...lr.dir ? { dir: resolve5(PROJECT_DIR, lr.dir) } : {}
|
|
10565
|
+
}))
|
|
10435
10566
|
};
|
|
10436
10567
|
}
|
|
10437
10568
|
function parseWsMessage(raw) {
|
|
@@ -10489,8 +10620,17 @@ function ensureBranchNotRemoving(branch) {
|
|
|
10489
10620
|
throw new LifecycleError(`Worktree is being removed: ${branch}`, 409);
|
|
10490
10621
|
}
|
|
10491
10622
|
}
|
|
10492
|
-
|
|
10623
|
+
function ensureBranchNotCreating(branch) {
|
|
10624
|
+
if (worktreeCreationTracker.has(branch)) {
|
|
10625
|
+
throw new LifecycleError(`Worktree is being created: ${branch}`, 409);
|
|
10626
|
+
}
|
|
10627
|
+
}
|
|
10628
|
+
function ensureBranchNotBusy(branch) {
|
|
10493
10629
|
ensureBranchNotRemoving(branch);
|
|
10630
|
+
ensureBranchNotCreating(branch);
|
|
10631
|
+
}
|
|
10632
|
+
async function withRemovingBranch(branch, fn) {
|
|
10633
|
+
ensureBranchNotBusy(branch);
|
|
10494
10634
|
removingBranches.add(branch);
|
|
10495
10635
|
try {
|
|
10496
10636
|
return await fn();
|
|
@@ -10499,7 +10639,7 @@ async function withRemovingBranch(branch, fn) {
|
|
|
10499
10639
|
}
|
|
10500
10640
|
}
|
|
10501
10641
|
async function resolveTerminalWorktree(branch) {
|
|
10502
|
-
|
|
10642
|
+
ensureBranchNotBusy(branch);
|
|
10503
10643
|
await reconciliationService.reconcile(PROJECT_DIR);
|
|
10504
10644
|
const state = projectRuntime.getWorktreeByBranch(branch);
|
|
10505
10645
|
if (!state) {
|
|
@@ -10562,6 +10702,7 @@ async function apiGetProject() {
|
|
|
10562
10702
|
projectName: config.name,
|
|
10563
10703
|
mainBranch: config.workspace.mainBranch,
|
|
10564
10704
|
runtime: projectRuntime,
|
|
10705
|
+
creatingWorktrees: worktreeCreationTracker.list(),
|
|
10565
10706
|
notifications: runtimeNotifications.list(),
|
|
10566
10707
|
findLinearIssue: (branch) => {
|
|
10567
10708
|
const match = linearIssues.find((issue) => branchMatchesIssue(branch, issue.branchName));
|
|
@@ -10623,6 +10764,9 @@ async function apiCreateWorktree(req) {
|
|
|
10623
10764
|
const prompt = typeof body.prompt === "string" ? body.prompt : undefined;
|
|
10624
10765
|
const profile = typeof body.profile === "string" ? body.profile : undefined;
|
|
10625
10766
|
const agent = body.agent === "claude" || body.agent === "codex" ? body.agent : undefined;
|
|
10767
|
+
if (branch) {
|
|
10768
|
+
ensureBranchNotCreating(branch);
|
|
10769
|
+
}
|
|
10626
10770
|
log.info(`[worktree:add]${branch ? ` branch=${branch}` : ""}${profile ? ` profile=${profile}` : ""}${agent ? ` agent=${agent}` : ""}${prompt ? ` prompt="${prompt.slice(0, 80)}"` : ""}`);
|
|
10627
10771
|
const result = await lifecycleService.createWorktree({
|
|
10628
10772
|
branch,
|
|
@@ -10643,19 +10787,21 @@ async function apiDeleteWorktree(name) {
|
|
|
10643
10787
|
});
|
|
10644
10788
|
}
|
|
10645
10789
|
async function apiOpenWorktree(name) {
|
|
10646
|
-
|
|
10790
|
+
ensureBranchNotBusy(name);
|
|
10647
10791
|
log.info(`[worktree:open] name=${name}`);
|
|
10648
10792
|
const result = await lifecycleService.openWorktree(name);
|
|
10649
10793
|
log.debug(`[worktree:open] done name=${name} worktreeId=${result.worktreeId}`);
|
|
10650
10794
|
return jsonResponse({ ok: true });
|
|
10651
10795
|
}
|
|
10652
10796
|
async function apiCloseWorktree(name) {
|
|
10797
|
+
ensureBranchNotBusy(name);
|
|
10653
10798
|
log.info(`[worktree:close] name=${name}`);
|
|
10654
10799
|
await lifecycleService.closeWorktree(name);
|
|
10655
10800
|
log.debug(`[worktree:close] done name=${name}`);
|
|
10656
10801
|
return jsonResponse({ ok: true });
|
|
10657
10802
|
}
|
|
10658
10803
|
async function apiSendPrompt(name, req) {
|
|
10804
|
+
ensureBranchNotBusy(name);
|
|
10659
10805
|
const raw = await req.json();
|
|
10660
10806
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
10661
10807
|
return errorResponse("Invalid request body", 400);
|
|
@@ -10673,6 +10819,7 @@ async function apiSendPrompt(name, req) {
|
|
|
10673
10819
|
return jsonResponse({ ok: true });
|
|
10674
10820
|
}
|
|
10675
10821
|
async function apiMergeWorktree(name) {
|
|
10822
|
+
ensureBranchNotBusy(name);
|
|
10676
10823
|
log.info(`[worktree:merge] name=${name}`);
|
|
10677
10824
|
await lifecycleService.mergeWorktree(name);
|
|
10678
10825
|
log.debug(`[worktree:merge] done name=${name}`);
|