webmux 0.39.0 → 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/backend/dist/server.js +91 -21
- package/bin/webmux.js +383 -148
- package/frontend/dist/assets/{DiffDialog-CqFJ8AzJ.js → DiffDialog-DSiqODW5.js} +1 -1
- package/frontend/dist/assets/{index-CghYughj.css → index-C4i4-u87.css} +1 -1
- package/frontend/dist/assets/index-C_8-_vLc.js +39 -0
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
- package/frontend/dist/assets/index-BnMSlB_K.js +0 -39
package/backend/dist/server.js
CHANGED
|
@@ -6959,13 +6959,13 @@ var require_public_api = __commonJS((exports) => {
|
|
|
6959
6959
|
|
|
6960
6960
|
// backend/src/server.ts
|
|
6961
6961
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
6962
|
-
import { join as join12, resolve as
|
|
6962
|
+
import { join as join12, resolve as resolve10 } from "path";
|
|
6963
6963
|
import { existsSync as existsSync3, mkdirSync as mkdirSync3 } from "fs";
|
|
6964
6964
|
import { networkInterfaces } from "os";
|
|
6965
6965
|
// package.json
|
|
6966
6966
|
var package_default = {
|
|
6967
6967
|
name: "webmux",
|
|
6968
|
-
version: "0.
|
|
6968
|
+
version: "0.41.0",
|
|
6969
6969
|
description: "Web dashboard for workmux \u2014 browser UI with embedded terminals, PR monitoring, and CI integration",
|
|
6970
6970
|
type: "module",
|
|
6971
6971
|
repository: {
|
|
@@ -10994,7 +10994,7 @@ var coerce = {
|
|
|
10994
10994
|
date: (arg) => ZodDate.create({ ...arg, coerce: true })
|
|
10995
10995
|
};
|
|
10996
10996
|
var NEVER = INVALID;
|
|
10997
|
-
// node_modules/.bun/@ts-rest+core@3.52.1+
|
|
10997
|
+
// node_modules/.bun/@ts-rest+core@3.52.1+38b2c5aebf202339/node_modules/@ts-rest/core/index.esm.mjs
|
|
10998
10998
|
var isZodObjectStrict = (obj) => {
|
|
10999
10999
|
return typeof (obj === null || obj === undefined ? undefined : obj.passthrough) === "function";
|
|
11000
11000
|
};
|
|
@@ -12395,6 +12395,7 @@ import { join } from "path";
|
|
|
12395
12395
|
// backend/src/domain/model.ts
|
|
12396
12396
|
var WORKTREE_META_SCHEMA_VERSION = 1;
|
|
12397
12397
|
var WORKTREE_ARCHIVE_STATE_VERSION = 1;
|
|
12398
|
+
var OPEN_SESSIONS_STATE_VERSION = 1;
|
|
12398
12399
|
var ROOT_TAB_ID = "root";
|
|
12399
12400
|
function conversationSessionId(conversation) {
|
|
12400
12401
|
if (!conversation)
|
|
@@ -12456,6 +12457,9 @@ function getWorktreeStoragePaths(gitDir) {
|
|
|
12456
12457
|
function getProjectArchiveStatePath(gitDir) {
|
|
12457
12458
|
return join(gitDir, "webmux", "archive.json");
|
|
12458
12459
|
}
|
|
12460
|
+
function getProjectOpenSessionsStatePath(gitDir) {
|
|
12461
|
+
return join(gitDir, "webmux", "open-sessions.json");
|
|
12462
|
+
}
|
|
12459
12463
|
async function ensureWorktreeStorageDirs(gitDir) {
|
|
12460
12464
|
const paths = getWorktreeStoragePaths(gitDir);
|
|
12461
12465
|
await mkdir2(paths.webmuxDir, { recursive: true });
|
|
@@ -12505,6 +12509,12 @@ async function writeWorktreeArchiveState(gitDir, state) {
|
|
|
12505
12509
|
await Bun.write(archivePath, JSON.stringify(state, null, 2) + `
|
|
12506
12510
|
`);
|
|
12507
12511
|
}
|
|
12512
|
+
async function writeOpenSessionsState(gitDir, state) {
|
|
12513
|
+
const statePath = getProjectOpenSessionsStatePath(gitDir);
|
|
12514
|
+
await ensureWorktreeStorageDirs(gitDir);
|
|
12515
|
+
await Bun.write(statePath, JSON.stringify(state, null, 2) + `
|
|
12516
|
+
`);
|
|
12517
|
+
}
|
|
12508
12518
|
function buildRuntimeEnvMap(meta, extraEnv = {}, dotenvValues = {}) {
|
|
12509
12519
|
return {
|
|
12510
12520
|
...dotenvValues,
|
|
@@ -18304,9 +18314,13 @@ class LifecycleService {
|
|
|
18304
18314
|
async pruneWorktrees() {
|
|
18305
18315
|
try {
|
|
18306
18316
|
const resolvedWorktrees = await this.resolveAllWorktrees();
|
|
18317
|
+
const sessionName = buildProjectSessionName(this.deps.projectRoot);
|
|
18307
18318
|
const removedBranches = [];
|
|
18308
18319
|
for (const resolved of resolvedWorktrees) {
|
|
18309
18320
|
const branch = resolved.entry.branch ?? resolved.entry.path;
|
|
18321
|
+
if (this.deps.tmux.hasWindow(sessionName, buildWorktreeWindowName(branch))) {
|
|
18322
|
+
continue;
|
|
18323
|
+
}
|
|
18310
18324
|
await this.removeResolvedWorktree(resolved);
|
|
18311
18325
|
removedBranches.push(branch);
|
|
18312
18326
|
}
|
|
@@ -19601,6 +19615,58 @@ function startAutoPullMonitor(deps, intervalMs) {
|
|
|
19601
19615
|
return startSerializedInterval(run2, intervalMs);
|
|
19602
19616
|
}
|
|
19603
19617
|
|
|
19618
|
+
// backend/src/services/session-restore-service.ts
|
|
19619
|
+
import { basename as basename5, resolve as resolve8 } from "path";
|
|
19620
|
+
function entryBranch(entry) {
|
|
19621
|
+
return entry.branch ?? basename5(entry.path);
|
|
19622
|
+
}
|
|
19623
|
+
function computeOpenBranches(input) {
|
|
19624
|
+
const resolvedProjectDir = resolve8(input.projectDir);
|
|
19625
|
+
const openWindowNames = new Set(input.windows.filter((window) => window.sessionName === input.sessionName).map((window) => window.windowName));
|
|
19626
|
+
return input.worktrees.filter((entry) => !entry.bare && resolve8(entry.path) !== resolvedProjectDir).map((entry) => entryBranch(entry)).filter((branch) => openWindowNames.has(buildWorktreeWindowName(branch))).sort((left, right) => left.localeCompare(right));
|
|
19627
|
+
}
|
|
19628
|
+
function buildOpenSessionsState(branches, savedAt) {
|
|
19629
|
+
return {
|
|
19630
|
+
schemaVersion: OPEN_SESSIONS_STATE_VERSION,
|
|
19631
|
+
savedAt: savedAt.toISOString(),
|
|
19632
|
+
branches
|
|
19633
|
+
};
|
|
19634
|
+
}
|
|
19635
|
+
var DEFAULT_SESSION_SNAPSHOT_INTERVAL_MS = 30000;
|
|
19636
|
+
async function saveOpenSessionsSnapshot(deps) {
|
|
19637
|
+
const projectRoot2 = resolve8(deps.projectRoot);
|
|
19638
|
+
const sessionName = buildProjectSessionName(projectRoot2);
|
|
19639
|
+
let windows = [];
|
|
19640
|
+
try {
|
|
19641
|
+
windows = deps.tmux.listWindows();
|
|
19642
|
+
} catch {
|
|
19643
|
+
windows = [];
|
|
19644
|
+
}
|
|
19645
|
+
const worktrees = deps.git.listLiveWorktrees(projectRoot2);
|
|
19646
|
+
const branches = computeOpenBranches({ worktrees, windows, sessionName, projectDir: projectRoot2 });
|
|
19647
|
+
if (branches.length === 0)
|
|
19648
|
+
return null;
|
|
19649
|
+
const now = deps.now ?? (() => new Date);
|
|
19650
|
+
const writeState = deps.writeState ?? writeOpenSessionsState;
|
|
19651
|
+
const gitDir = deps.git.resolveWorktreeGitDir(projectRoot2);
|
|
19652
|
+
await writeState(gitDir, buildOpenSessionsState(branches, now()));
|
|
19653
|
+
return branches;
|
|
19654
|
+
}
|
|
19655
|
+
function startSessionSnapshotMonitor(deps, intervalMs = DEFAULT_SESSION_SNAPSHOT_INTERVAL_MS) {
|
|
19656
|
+
log.info(`[session-snapshot] monitor started (interval: ${intervalMs}ms)`);
|
|
19657
|
+
const run2 = async () => {
|
|
19658
|
+
try {
|
|
19659
|
+
const branches = await saveOpenSessionsSnapshot(deps);
|
|
19660
|
+
if (branches) {
|
|
19661
|
+
log.debug(`[session-snapshot] saved ${branches.length} open session(s): ${branches.join(", ")}`);
|
|
19662
|
+
}
|
|
19663
|
+
} catch (error) {
|
|
19664
|
+
log.warn(`[session-snapshot] save failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
19665
|
+
}
|
|
19666
|
+
};
|
|
19667
|
+
return startSerializedInterval(run2, intervalMs);
|
|
19668
|
+
}
|
|
19669
|
+
|
|
19604
19670
|
// backend/src/services/agents-ui-service.ts
|
|
19605
19671
|
function cloneConversationMeta(meta) {
|
|
19606
19672
|
return meta ? { ...meta } : null;
|
|
@@ -21660,7 +21726,7 @@ class BunPortProbe {
|
|
|
21660
21726
|
this.hostnames = hostnames;
|
|
21661
21727
|
}
|
|
21662
21728
|
isListening(port) {
|
|
21663
|
-
return new Promise((
|
|
21729
|
+
return new Promise((resolve9) => {
|
|
21664
21730
|
let settled = false;
|
|
21665
21731
|
let pending = this.hostnames.length;
|
|
21666
21732
|
const settle = (result) => {
|
|
@@ -21669,20 +21735,20 @@ class BunPortProbe {
|
|
|
21669
21735
|
if (result) {
|
|
21670
21736
|
settled = true;
|
|
21671
21737
|
clearTimeout(timer);
|
|
21672
|
-
|
|
21738
|
+
resolve9(true);
|
|
21673
21739
|
return;
|
|
21674
21740
|
}
|
|
21675
21741
|
pending--;
|
|
21676
21742
|
if (pending === 0) {
|
|
21677
21743
|
settled = true;
|
|
21678
21744
|
clearTimeout(timer);
|
|
21679
|
-
|
|
21745
|
+
resolve9(false);
|
|
21680
21746
|
}
|
|
21681
21747
|
};
|
|
21682
21748
|
const timer = setTimeout(() => {
|
|
21683
21749
|
if (!settled) {
|
|
21684
21750
|
settled = true;
|
|
21685
|
-
|
|
21751
|
+
resolve9(false);
|
|
21686
21752
|
}
|
|
21687
21753
|
}, this.timeoutMs);
|
|
21688
21754
|
for (const hostname of this.hostnames) {
|
|
@@ -21832,8 +21898,8 @@ class ArchiveStateService {
|
|
|
21832
21898
|
async withMutationLock(operation) {
|
|
21833
21899
|
const previous = this.mutationQueue;
|
|
21834
21900
|
let release = () => {};
|
|
21835
|
-
this.mutationQueue = new Promise((
|
|
21836
|
-
release =
|
|
21901
|
+
this.mutationQueue = new Promise((resolve9) => {
|
|
21902
|
+
release = resolve9;
|
|
21837
21903
|
});
|
|
21838
21904
|
await previous.catch(() => {});
|
|
21839
21905
|
try {
|
|
@@ -22136,9 +22202,9 @@ class ProjectRuntime {
|
|
|
22136
22202
|
}
|
|
22137
22203
|
|
|
22138
22204
|
// backend/src/services/reconciliation-service.ts
|
|
22139
|
-
import { basename as
|
|
22205
|
+
import { basename as basename6, resolve as resolve9 } from "path";
|
|
22140
22206
|
function makeUnmanagedWorktreeId(path) {
|
|
22141
|
-
return `unmanaged:${
|
|
22207
|
+
return `unmanaged:${resolve9(path)}`;
|
|
22142
22208
|
}
|
|
22143
22209
|
function isValidPort2(port) {
|
|
22144
22210
|
return port !== null && Number.isInteger(port) && port >= 1 && port <= 65535;
|
|
@@ -22171,7 +22237,7 @@ function findWindow(windows, sessionName, branch) {
|
|
|
22171
22237
|
return windows.find((window) => window.sessionName === sessionName && window.windowName === windowName) ?? null;
|
|
22172
22238
|
}
|
|
22173
22239
|
function resolveBranch(entry, metaBranch) {
|
|
22174
|
-
const fallback =
|
|
22240
|
+
const fallback = basename6(entry.path);
|
|
22175
22241
|
return entry.branch ?? metaBranch ?? (fallback.length > 0 ? fallback : "unknown");
|
|
22176
22242
|
}
|
|
22177
22243
|
|
|
@@ -22195,7 +22261,7 @@ class ReconciliationService {
|
|
|
22195
22261
|
if (!options.force && this.now() - this.lastReconciledAt < this.freshnessMs) {
|
|
22196
22262
|
return;
|
|
22197
22263
|
}
|
|
22198
|
-
const normalizedRepoRoot =
|
|
22264
|
+
const normalizedRepoRoot = resolve9(repoRoot);
|
|
22199
22265
|
const reconcilePromise = this.runReconcile(normalizedRepoRoot).then(() => {
|
|
22200
22266
|
this.lastReconciledAt = this.now();
|
|
22201
22267
|
});
|
|
@@ -22214,7 +22280,7 @@ class ReconciliationService {
|
|
|
22214
22280
|
windows = [];
|
|
22215
22281
|
}
|
|
22216
22282
|
const seenWorktreeIds = new Set;
|
|
22217
|
-
const candidateEntries = worktrees.filter((entry) => !entry.bare &&
|
|
22283
|
+
const candidateEntries = worktrees.filter((entry) => !entry.bare && resolve9(entry.path) !== normalizedRepoRoot);
|
|
22218
22284
|
const reconciledStates = await mapWithConcurrency(candidateEntries, this.concurrency, async (entry) => {
|
|
22219
22285
|
const gitDir = this.deps.git.resolveWorktreeGitDir(entry.path);
|
|
22220
22286
|
const meta = await readWorktreeMeta(gitDir);
|
|
@@ -22689,6 +22755,7 @@ function createProjectApp(runtime, instancePrefix) {
|
|
|
22689
22755
|
let stopPrMonitor = null;
|
|
22690
22756
|
let stopOneshotWatcher = null;
|
|
22691
22757
|
let stopAutoPullMonitor = null;
|
|
22758
|
+
let stopSessionSnapshot = null;
|
|
22692
22759
|
async function runOneshotForIssue(issueId) {
|
|
22693
22760
|
const seed = await buildSeedFromLinear({ issueId }, defaultSeedFromLinearDeps);
|
|
22694
22761
|
if (!seed.ok) {
|
|
@@ -22798,7 +22865,7 @@ function createProjectApp(runtime, instancePrefix) {
|
|
|
22798
22865
|
startupEnvs: config.startupEnvs,
|
|
22799
22866
|
linkedRepos: config.integrations.github.linkedRepos.map((lr) => ({
|
|
22800
22867
|
alias: lr.alias,
|
|
22801
|
-
...lr.dir ? { dir:
|
|
22868
|
+
...lr.dir ? { dir: resolve10(PROJECT_DIR, lr.dir) } : {}
|
|
22802
22869
|
})),
|
|
22803
22870
|
linearAutoCreateWorktrees: linearAutoCreateEnabled,
|
|
22804
22871
|
autoRemoveOnMerge: autoRemoveOnMergeEnabled,
|
|
@@ -22966,9 +23033,9 @@ function createProjectApp(runtime, instancePrefix) {
|
|
|
22966
23033
|
}
|
|
22967
23034
|
async function getWorktreeGitDirs() {
|
|
22968
23035
|
const gitDirs = new Map;
|
|
22969
|
-
const projectRoot2 =
|
|
23036
|
+
const projectRoot2 = resolve10(PROJECT_DIR);
|
|
22970
23037
|
for (const entry of git.listLiveWorktrees(projectRoot2)) {
|
|
22971
|
-
if (entry.bare ||
|
|
23038
|
+
if (entry.bare || resolve10(entry.path) === projectRoot2 || !entry.branch)
|
|
22972
23039
|
continue;
|
|
22973
23040
|
gitDirs.set(entry.branch, git.resolveWorktreeGitDir(entry.path));
|
|
22974
23041
|
}
|
|
@@ -23763,7 +23830,7 @@ ${resolvedPrompt}` : conversationContext;
|
|
|
23763
23830
|
return errorResponse(`Unknown linked repo: ${repo}`, 404);
|
|
23764
23831
|
if (!linkedRepo.dir)
|
|
23765
23832
|
return errorResponse(`Linked repo "${repo}" has no dir configured`, 400);
|
|
23766
|
-
const resolvedDir =
|
|
23833
|
+
const resolvedDir = resolve10(PROJECT_DIR, linkedRepo.dir);
|
|
23767
23834
|
const repoRoot = git.resolveRepoRoot(resolvedDir);
|
|
23768
23835
|
if (!repoRoot)
|
|
23769
23836
|
return errorResponse(`Linked repo "${repo}" dir is not a git repository: ${resolvedDir}`, 400);
|
|
@@ -23928,7 +23995,7 @@ ${resolvedPrompt}` : conversationContext;
|
|
|
23928
23995
|
}
|
|
23929
23996
|
const safeName = `${Date.now()}_${sanitizeFilename(entry.name)}`;
|
|
23930
23997
|
const destPath = join12(uploadDir, safeName);
|
|
23931
|
-
if (!
|
|
23998
|
+
if (!resolve10(destPath).startsWith(uploadDir + "/")) {
|
|
23932
23999
|
return errorResponse("Invalid filename", 400);
|
|
23933
24000
|
}
|
|
23934
24001
|
await Bun.write(destPath, entry);
|
|
@@ -24383,6 +24450,7 @@ ${resolvedPrompt}` : conversationContext;
|
|
|
24383
24450
|
if (config.workspace.autoPull.enabled) {
|
|
24384
24451
|
stopAutoPullMonitor = startAutoPullMonitor({ git, projectRoot: PROJECT_DIR, mainBranch: config.workspace.mainBranch }, config.workspace.autoPull.intervalSeconds * 1000);
|
|
24385
24452
|
}
|
|
24453
|
+
stopSessionSnapshot = startSessionSnapshotMonitor({ git, tmux, projectRoot: PROJECT_DIR });
|
|
24386
24454
|
}
|
|
24387
24455
|
function stopLight() {
|
|
24388
24456
|
stopPrMonitor?.();
|
|
@@ -24392,6 +24460,8 @@ ${resolvedPrompt}` : conversationContext;
|
|
|
24392
24460
|
stopOneshotWatcher = null;
|
|
24393
24461
|
stopAutoPullMonitor?.();
|
|
24394
24462
|
stopAutoPullMonitor = null;
|
|
24463
|
+
stopSessionSnapshot?.();
|
|
24464
|
+
stopSessionSnapshot = null;
|
|
24395
24465
|
}
|
|
24396
24466
|
return { prefix: instancePrefix, routes, wsHandlers, startLight, stopLight };
|
|
24397
24467
|
}
|
|
@@ -24565,8 +24635,8 @@ async function globalFetch(req) {
|
|
|
24565
24635
|
if (STATIC_DIR) {
|
|
24566
24636
|
const rawPath = url.pathname === "/" ? "index.html" : url.pathname;
|
|
24567
24637
|
const filePath = join12(STATIC_DIR, rawPath);
|
|
24568
|
-
const staticRoot =
|
|
24569
|
-
if (!
|
|
24638
|
+
const staticRoot = resolve10(STATIC_DIR);
|
|
24639
|
+
if (!resolve10(filePath).startsWith(staticRoot + "/")) {
|
|
24570
24640
|
return new Response("Forbidden", { status: 403 });
|
|
24571
24641
|
}
|
|
24572
24642
|
const file = Bun.file(filePath);
|