@spencer-kit/coder-studio 0.3.2 → 0.3.4

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.
@@ -115,8 +115,8 @@ var init_plugin = __esm({
115
115
  init_web_ui_routing();
116
116
  init_login_protection();
117
117
  AUTH_COOKIE_NAME = "coder_studio_auth";
118
- isPublicPath = (path9) => {
119
- const pathname = getRequestPathname(path9);
118
+ isPublicPath = (path10) => {
119
+ const pathname = getRequestPathname(path10);
120
120
  return pathname === "/" || pathname === "/login" || pathname === "/healthz" || pathname === "/auth/status" || pathname === "/auth/login" || pathname === "/auth/logout" || pathname.startsWith("/@") || isPublicStaticPath(pathname);
121
121
  };
122
122
  parseCookies = (cookieHeader) => {
@@ -787,11 +787,11 @@ var init_src = __esm({
787
787
 
788
788
  // packages/utils/src/direct-execution.ts
789
789
  import { posix, resolve as resolve2 } from "node:path";
790
- function isWindowsDrivePath(path9) {
791
- return /^[A-Za-z]:\//.test(path9);
790
+ function isWindowsDrivePath(path10) {
791
+ return /^[A-Za-z]:\//.test(path10);
792
792
  }
793
- function normalizeComparablePath(path9) {
794
- let normalized = path9.replace(/\\/g, "/");
793
+ function normalizeComparablePath(path10) {
794
+ let normalized = path10.replace(/\\/g, "/");
795
795
  if (/^\/[A-Za-z]:\//.test(normalized)) {
796
796
  normalized = normalized.slice(1);
797
797
  }
@@ -815,8 +815,8 @@ function normalizeModuleUrlPath(moduleUrl) {
815
815
  if (url.protocol !== "file:") {
816
816
  return null;
817
817
  }
818
- const path9 = `${url.host ? `//${url.host}` : ""}${decodeURIComponent(url.pathname)}`;
819
- return normalizeComparablePath(path9);
818
+ const path10 = `${url.host ? `//${url.host}` : ""}${decodeURIComponent(url.pathname)}`;
819
+ return normalizeComparablePath(path10);
820
820
  }
821
821
  function normalizeArgvPath(argv1) {
822
822
  const isAbsoluteWindowsPath = /^[A-Za-z]:[\\/]/.test(argv1) || /^\\\\/.test(argv1);
@@ -995,10 +995,10 @@ var init_image = __esm({
995
995
  // packages/server/src/fs/file-io.ts
996
996
  import { createHash } from "crypto";
997
997
  import { readFile as fsReadFile, writeFile as fsWriteFile, mkdir, rm, stat } from "fs/promises";
998
- import { dirname as dirname3, resolve as resolve3 } from "path";
999
- async function statSafe(path9) {
998
+ import { dirname as dirname3, isAbsolute, relative, resolve as resolve3 } from "path";
999
+ async function statSafe(path10) {
1000
1000
  try {
1001
- return await stat(path9);
1001
+ return await stat(path10);
1002
1002
  } catch {
1003
1003
  return null;
1004
1004
  }
@@ -1031,7 +1031,8 @@ async function deleteEntry(rootPath, relPath) {
1031
1031
  function resolveSafe(root, relPath) {
1032
1032
  const absRoot = resolve3(root);
1033
1033
  const abs = resolve3(absRoot, relPath);
1034
- if (!abs.startsWith(absRoot + "/") && abs !== absRoot) {
1034
+ const rel = relative(absRoot, abs);
1035
+ if (rel === ".." || rel.startsWith(`..${"/"}`) || isAbsolute(rel)) {
1035
1036
  throw { code: "path_escape", message: "Path escapes workspace root" };
1036
1037
  }
1037
1038
  return abs;
@@ -1093,8 +1094,10 @@ var init_file_io = __esm({
1093
1094
  // packages/server/src/routes/file-asset.ts
1094
1095
  import { createReadStream } from "fs";
1095
1096
  import { realpath, stat as stat2 } from "fs/promises";
1097
+ import { isAbsolute as isAbsolute2, relative as relative2 } from "path";
1096
1098
  function isPathInsideRoot(rootPath, targetPath) {
1097
- return targetPath === rootPath || targetPath.startsWith(`${rootPath}/`);
1099
+ const rel = relative2(rootPath, targetPath);
1100
+ return rel !== ".." && !rel.startsWith(`..${"/"}`) && !isAbsolute2(rel);
1098
1101
  }
1099
1102
  function registerFileAssetRoutes(app, deps) {
1100
1103
  app.get(
@@ -1219,12 +1222,12 @@ async function ensureSafeUploadDir(rootDir, targetDir) {
1219
1222
  await mkdir2(resolvedRoot, { recursive: true });
1220
1223
  await assertDirectorySegmentSafe(resolvedRoot);
1221
1224
  }
1222
- const relative3 = path3.relative(resolvedRoot, resolvedTarget);
1223
- if (!relative3) {
1225
+ const relative5 = path3.relative(resolvedRoot, resolvedTarget);
1226
+ if (!relative5) {
1224
1227
  return;
1225
1228
  }
1226
1229
  let current = resolvedRoot;
1227
- for (const segment of relative3.split(path3.sep)) {
1230
+ for (const segment of relative5.split(path3.sep)) {
1228
1231
  current = path3.join(current, segment);
1229
1232
  try {
1230
1233
  await assertDirectorySegmentSafe(current);
@@ -1773,189 +1776,265 @@ var init_app = __esm({
1773
1776
  }
1774
1777
  });
1775
1778
 
1776
- // packages/server/src/config/codex-config-audit.ts
1777
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync5, renameSync, writeFileSync as writeFileSync3 } from "node:fs";
1778
- import { homedir as homedir3 } from "node:os";
1779
- import { dirname as dirname4, join as join3 } from "node:path";
1780
- function resolveCodexConfigPath() {
1781
- const codexHome = process.env.CODEX_HOME;
1782
- if (codexHome && codexHome.trim()) {
1783
- return join3(codexHome, "config.toml");
1784
- }
1785
- return join3(homedir3(), ".codex", "config.toml");
1786
- }
1787
- function auditCodexConfigToml(configPath) {
1788
- const path9 = configPath ?? resolveCodexConfigPath();
1789
- if (!existsSync6(path9)) {
1790
- return { configPath: path9, exists: false, findings: [] };
1791
- }
1792
- let content;
1793
- try {
1794
- content = readFileSync5(path9, "utf-8");
1795
- } catch {
1796
- return { configPath: path9, exists: false, findings: [] };
1797
- }
1798
- const lines = content.split(/\r?\n/);
1799
- const findings = [];
1800
- const notifyFinding = detectTopLevelNotify(lines);
1801
- if (notifyFinding) findings.push(notifyFinding);
1802
- const codexHooksFinding = detectCodexHooksFlag(lines);
1803
- if (codexHooksFinding) findings.push(codexHooksFinding);
1804
- return { configPath: path9, exists: true, findings };
1805
- }
1806
- function cleanupCodexConfigToml(configPath, opts) {
1807
- if (opts.removeIds.length === 0) {
1808
- return { removed: [], backupPath: null, noop: true };
1809
- }
1810
- if (!existsSync6(configPath)) {
1811
- return { removed: [], backupPath: null, noop: true };
1812
- }
1813
- const audit = auditCodexConfigToml(configPath);
1814
- const selected = audit.findings.filter((f) => opts.removeIds.includes(f.id));
1815
- if (selected.length === 0) {
1816
- return { removed: [], backupPath: null, noop: true };
1817
- }
1818
- const original = readFileSync5(configPath, "utf-8");
1819
- const backupPath = writeBackup(configPath, original, opts.backupDir);
1820
- const linesToDrop = /* @__PURE__ */ new Set();
1821
- for (const finding of selected) {
1822
- for (let ln = finding.startLine; ln <= finding.endLine; ln++) {
1823
- linesToDrop.add(ln);
1824
- }
1825
- }
1826
- const originalLines = original.split(/\r?\n/);
1827
- const kept = [];
1828
- for (let i = 0; i < originalLines.length; i++) {
1829
- const ln = i + 1;
1830
- if (linesToDrop.has(ln)) continue;
1831
- kept.push(originalLines[i]);
1832
- }
1833
- const cleaned = collapseBlankRunsNearDeletions(kept);
1834
- const output = cleaned.join("\n");
1835
- atomicWrite(configPath, output);
1836
- return {
1837
- removed: selected.map((f) => f.id),
1838
- backupPath,
1839
- noop: false
1840
- };
1841
- }
1842
- function detectTopLevelNotify(lines) {
1843
- const headerRegex = /^\s*\[/;
1844
- const notifyRegex = /^\s*notify\s*=\s*(.*)$/;
1845
- let inTopLevel = true;
1846
- for (let i = 0; i < lines.length; i++) {
1847
- const line = lines[i];
1848
- const trimmed = line.trim();
1849
- if (headerRegex.test(trimmed)) {
1850
- inTopLevel = false;
1851
- continue;
1852
- }
1853
- if (!inTopLevel) continue;
1854
- const m = trimmed.match(notifyRegex);
1855
- if (!m) continue;
1856
- const rhs = (m[1] ?? "").trim();
1857
- if (rhs.startsWith("[") && rhs.endsWith("]") && countBrackets(rhs) === 0) {
1858
- return makeNotifyFinding(lines, i, i);
1859
- }
1860
- if (rhs.startsWith("[")) {
1861
- let depth = countBrackets(rhs);
1862
- for (let j = i + 1; j < lines.length; j++) {
1863
- depth += countBrackets(lines[j]);
1864
- if (depth === 0) {
1865
- return makeNotifyFinding(lines, i, j);
1779
+ // packages/server/src/git/auto-fetch.ts
1780
+ var PERIOD_SETTING_KEY, DEFAULT_PERIOD_SEC, TICK_INTERVAL_MS, OPEN_TIME_COOLDOWN_MS, MAX_CONSECUTIVE_FAILURES, JITTER_RATIO, AutoFetchScheduler;
1781
+ var init_auto_fetch = __esm({
1782
+ "packages/server/src/git/auto-fetch.ts"() {
1783
+ "use strict";
1784
+ PERIOD_SETTING_KEY = "git.autofetchPeriodSec";
1785
+ DEFAULT_PERIOD_SEC = 180;
1786
+ TICK_INTERVAL_MS = 1e3;
1787
+ OPEN_TIME_COOLDOWN_MS = 5 * 60 * 1e3;
1788
+ MAX_CONSECUTIVE_FAILURES = 3;
1789
+ JITTER_RATIO = 0.1;
1790
+ AutoFetchScheduler = class {
1791
+ constructor(deps) {
1792
+ this.deps = deps;
1793
+ this.now = deps.now ?? Date.now;
1794
+ this.random = deps.random ?? (() => 0.5);
1795
+ this.setTimeoutFn = deps.setTimeout ?? globalThis.setTimeout;
1796
+ this.setIntervalFn = deps.setInterval ?? globalThis.setInterval;
1797
+ this.clearIntervalFn = deps.clearInterval ?? globalThis.clearInterval;
1798
+ this.clearTimeoutFn = deps.clearTimeout ?? globalThis.clearTimeout;
1799
+ this.start();
1800
+ }
1801
+ deps;
1802
+ clientWorkspaceMap = /* @__PURE__ */ new Map();
1803
+ workspaceStateMap = /* @__PURE__ */ new Map();
1804
+ now;
1805
+ random;
1806
+ setTimeoutFn;
1807
+ setIntervalFn;
1808
+ clearIntervalFn;
1809
+ clearTimeoutFn;
1810
+ pendingTimeouts = /* @__PURE__ */ new Set();
1811
+ tickTimer = null;
1812
+ stopped = false;
1813
+ registerViewer(clientId, workspaceId) {
1814
+ const previousWorkspaceId = this.clientWorkspaceMap.get(clientId);
1815
+ if (previousWorkspaceId === workspaceId) {
1816
+ return;
1866
1817
  }
1818
+ if (previousWorkspaceId) {
1819
+ this.unregisterViewer(clientId);
1820
+ }
1821
+ this.clientWorkspaceMap.set(clientId, workspaceId);
1822
+ const state = this.getOrCreateState(workspaceId);
1823
+ state.viewerCount += 1;
1824
+ if (state.viewerCount === 1) {
1825
+ state.blocked = false;
1826
+ state.consecutiveFailures = 0;
1827
+ }
1828
+ this.ensureNextPeriodicFetch(state, false);
1829
+ }
1830
+ unregisterViewer(clientId) {
1831
+ const workspaceId = this.clientWorkspaceMap.get(clientId);
1832
+ if (!workspaceId) {
1833
+ return;
1834
+ }
1835
+ this.clientWorkspaceMap.delete(clientId);
1836
+ const state = this.workspaceStateMap.get(workspaceId);
1837
+ if (!state) {
1838
+ return;
1839
+ }
1840
+ state.viewerCount = Math.max(0, state.viewerCount - 1);
1841
+ if (state.viewerCount === 0) {
1842
+ state.blocked = false;
1843
+ state.consecutiveFailures = 0;
1844
+ state.nextFetchAt = void 0;
1845
+ }
1846
+ }
1847
+ triggerOpenTimeFetch(workspaceId) {
1848
+ const state = this.getOrCreateState(workspaceId);
1849
+ const lastFetchAt = state.lastFetchAt;
1850
+ if (this.stopped || state.inFlight) {
1851
+ return;
1852
+ }
1853
+ if (lastFetchAt !== void 0 && this.now() - lastFetchAt < OPEN_TIME_COOLDOWN_MS) {
1854
+ return;
1855
+ }
1856
+ const timer = this.setTimeoutFn(() => {
1857
+ this.pendingTimeouts.delete(timer);
1858
+ if (this.stopped) {
1859
+ return;
1860
+ }
1861
+ void this.fetchWorkspace(workspaceId, "open");
1862
+ }, 0);
1863
+ this.pendingTimeouts.add(timer);
1864
+ }
1865
+ recordSuccess(workspaceId) {
1866
+ const state = this.getOrCreateState(workspaceId);
1867
+ state.lastFetchAt = this.now();
1868
+ state.consecutiveFailures = 0;
1869
+ state.blocked = false;
1870
+ state.nextFetchAt = void 0;
1871
+ this.ensureNextPeriodicFetch(state, true);
1872
+ }
1873
+ recordFailure(workspaceId) {
1874
+ const state = this.getOrCreateState(workspaceId);
1875
+ state.consecutiveFailures += 1;
1876
+ state.nextFetchAt = void 0;
1877
+ if (state.consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
1878
+ state.blocked = true;
1879
+ return;
1880
+ }
1881
+ this.ensureNextPeriodicFetch(state, true);
1882
+ }
1883
+ getLastFetchAt(workspaceId) {
1884
+ return this.workspaceStateMap.get(workspaceId)?.lastFetchAt;
1885
+ }
1886
+ async runExclusive(workspaceId, op) {
1887
+ const release = await this.acquireWorkspaceOperation(workspaceId);
1888
+ try {
1889
+ return await op();
1890
+ } finally {
1891
+ release();
1892
+ }
1893
+ }
1894
+ start() {
1895
+ if (this.tickTimer) {
1896
+ return;
1897
+ }
1898
+ this.stopped = false;
1899
+ this.tickTimer = this.setIntervalFn(() => {
1900
+ this.evaluateDueFetches();
1901
+ }, TICK_INTERVAL_MS);
1902
+ }
1903
+ stop() {
1904
+ if (this.tickTimer) {
1905
+ this.clearIntervalFn(this.tickTimer);
1906
+ this.tickTimer = null;
1907
+ }
1908
+ this.stopped = true;
1909
+ for (const timer of this.pendingTimeouts) {
1910
+ this.clearTimeoutFn(timer);
1911
+ }
1912
+ this.pendingTimeouts.clear();
1913
+ }
1914
+ evaluateDueFetches() {
1915
+ const now = this.now();
1916
+ for (const [workspaceId, state] of this.workspaceStateMap) {
1917
+ if (!this.shouldCheckWorkspace(state)) {
1918
+ continue;
1919
+ }
1920
+ if (state.nextFetchAt === void 0) {
1921
+ this.ensureNextPeriodicFetch(state, false);
1922
+ }
1923
+ if (state.nextFetchAt === void 0 || state.nextFetchAt > now) {
1924
+ continue;
1925
+ }
1926
+ void this.fetchWorkspace(workspaceId, "periodic", state.nextFetchAt);
1927
+ }
1928
+ }
1929
+ shouldCheckWorkspace(state) {
1930
+ return this.getPeriodMs() > 0 && state.viewerCount > 0 && !state.inFlight && !state.blocked;
1931
+ }
1932
+ ensureNextPeriodicFetch(state, resetSchedule) {
1933
+ const periodMs = this.getPeriodMs();
1934
+ if (periodMs <= 0 || state.viewerCount <= 0 || state.blocked || state.inFlight) {
1935
+ return;
1936
+ }
1937
+ if (!resetSchedule && state.nextFetchAt !== void 0) {
1938
+ return;
1939
+ }
1940
+ if (state.lastFetchAt === void 0) {
1941
+ state.nextFetchAt = this.now();
1942
+ return;
1943
+ }
1944
+ const nextFetchAt = state.lastFetchAt + this.getJitteredPeriodMs(periodMs);
1945
+ state.nextFetchAt = Math.max(this.now(), nextFetchAt);
1946
+ }
1947
+ getPeriodMs() {
1948
+ const configuredPeriodSec = this.deps.settingsRepo.get(PERIOD_SETTING_KEY);
1949
+ const periodSec = configuredPeriodSec ?? DEFAULT_PERIOD_SEC;
1950
+ return Math.max(0, periodSec) * 1e3;
1951
+ }
1952
+ getJitteredPeriodMs(periodMs) {
1953
+ const jitterScale = 1 + (this.random() - 0.5) * 2 * JITTER_RATIO;
1954
+ return Math.round(periodMs * jitterScale);
1955
+ }
1956
+ getOrCreateState(workspaceId) {
1957
+ const existingState = this.workspaceStateMap.get(workspaceId);
1958
+ if (existingState) {
1959
+ return existingState;
1960
+ }
1961
+ const state = {
1962
+ viewerCount: 0,
1963
+ consecutiveFailures: 0,
1964
+ inFlight: false,
1965
+ blocked: false,
1966
+ waiters: []
1967
+ };
1968
+ this.workspaceStateMap.set(workspaceId, state);
1969
+ return state;
1970
+ }
1971
+ async fetchWorkspace(workspaceId, mode, scheduledAt) {
1972
+ if (this.stopped) {
1973
+ return;
1974
+ }
1975
+ const state = this.getOrCreateState(workspaceId);
1976
+ if (state.inFlight || state.blocked) {
1977
+ return;
1978
+ }
1979
+ if (!this.deps.workspaceMgr.get(workspaceId)) {
1980
+ state.nextFetchAt = void 0;
1981
+ return;
1982
+ }
1983
+ await this.runExclusive(workspaceId, async () => {
1984
+ const currentState = this.getOrCreateState(workspaceId);
1985
+ if (this.stopped || currentState.blocked) {
1986
+ return;
1987
+ }
1988
+ if (!this.deps.workspaceMgr.get(workspaceId)) {
1989
+ currentState.nextFetchAt = void 0;
1990
+ return;
1991
+ }
1992
+ if (mode === "open") {
1993
+ const lastFetchAt = currentState.lastFetchAt;
1994
+ if (lastFetchAt !== void 0 && this.now() - lastFetchAt < OPEN_TIME_COOLDOWN_MS) {
1995
+ return;
1996
+ }
1997
+ } else if (scheduledAt !== void 0 && currentState.lastFetchAt !== void 0 && currentState.lastFetchAt >= scheduledAt) {
1998
+ return;
1999
+ }
2000
+ try {
2001
+ await this.deps.runFetch(workspaceId);
2002
+ this.recordSuccess(workspaceId);
2003
+ } catch {
2004
+ this.recordFailure(workspaceId);
2005
+ }
2006
+ });
2007
+ }
2008
+ acquireWorkspaceOperation(workspaceId) {
2009
+ const state = this.getOrCreateState(workspaceId);
2010
+ return new Promise((resolve4) => {
2011
+ const grant = () => {
2012
+ state.inFlight = true;
2013
+ state.nextFetchAt = void 0;
2014
+ let released = false;
2015
+ resolve4(() => {
2016
+ if (released) {
2017
+ return;
2018
+ }
2019
+ released = true;
2020
+ const next = state.waiters.shift();
2021
+ if (next) {
2022
+ next();
2023
+ return;
2024
+ }
2025
+ state.inFlight = false;
2026
+ this.ensureNextPeriodicFetch(state, true);
2027
+ });
2028
+ };
2029
+ if (state.inFlight) {
2030
+ state.waiters.push(grant);
2031
+ return;
2032
+ }
2033
+ grant();
2034
+ });
1867
2035
  }
1868
- return null;
1869
- }
1870
- return makeNotifyFinding(lines, i, i);
1871
- }
1872
- return null;
1873
- }
1874
- function makeNotifyFinding(lines, startIdx, endIdx) {
1875
- return {
1876
- id: "toml_notify",
1877
- type: "toml_notify",
1878
- severity: "warn",
1879
- startLine: startIdx + 1,
1880
- endLine: endIdx + 1,
1881
- snippet: lines.slice(startIdx, endIdx + 1).join("\n"),
1882
- message: "config.toml \u9876\u5C42\u8BBE\u7F6E\u4E86 notify\uFF0C\u4F1A\u4E0E Coder Studio \u7684\u542F\u52A8\u53C2\u6570\u6CE8\u5165\u51B2\u7A81\uFF0C\u53EF\u80FD\u5BFC\u81F4 session \u72B6\u6001\u4E0D\u540C\u6B65\u3002"
1883
- };
1884
- }
1885
- function detectCodexHooksFlag(lines) {
1886
- const headerRegex = /^\s*\[([^\]]+)\]\s*$/;
1887
- const codexHooksRegex = /^\s*codex_hooks\s*=\s*true\b/;
1888
- let currentSection = null;
1889
- for (let i = 0; i < lines.length; i++) {
1890
- const line = lines[i];
1891
- const headerMatch = line.match(headerRegex);
1892
- if (headerMatch) {
1893
- currentSection = headerMatch[1].trim();
1894
- continue;
1895
- }
1896
- if (currentSection !== "features") continue;
1897
- if (!codexHooksRegex.test(line)) continue;
1898
- return {
1899
- id: "toml_codex_hooks",
1900
- type: "toml_codex_hooks",
1901
- severity: "info",
1902
- startLine: i + 1,
1903
- endLine: i + 1,
1904
- snippet: line,
1905
- message: "[features] codex_hooks = true \u542F\u7528\u4E86 Codex CLI \u7684\u5B9E\u9A8C\u6027 hook \u5F15\u64CE\uFF0C\u53EF\u80FD\u5F71\u54CD notify \u7684\u884C\u4E3A\u3002\u82E5\u4E0D\u4E3B\u52A8\u4F7F\u7528\u8BE5\u7279\u6027\uFF0C\u5EFA\u8BAE\u5173\u95ED\u3002"
1906
2036
  };
1907
2037
  }
1908
- return null;
1909
- }
1910
- function countBrackets(s) {
1911
- let n = 0;
1912
- for (const ch of s) {
1913
- if (ch === "[") n++;
1914
- else if (ch === "]") n--;
1915
- }
1916
- return n;
1917
- }
1918
- function writeBackup(configPath, original, backupDir) {
1919
- const dir = backupDir ?? dirname4(configPath);
1920
- if (!existsSync6(dir)) {
1921
- mkdirSync3(dir, { recursive: true });
1922
- }
1923
- const ts = formatTimestamp(/* @__PURE__ */ new Date());
1924
- const backupPath = join3(dir, `${basenameNoTomlExt(configPath)}.bak.${ts}.toml`);
1925
- writeFileSync3(backupPath, original, "utf-8");
1926
- return backupPath;
1927
- }
1928
- function atomicWrite(configPath, contents) {
1929
- const tempPath = `${configPath}.tmp`;
1930
- writeFileSync3(tempPath, contents, "utf-8");
1931
- renameSync(tempPath, configPath);
1932
- }
1933
- function basenameNoTomlExt(p) {
1934
- const base = p.split(/[\\/]/).pop() ?? "config.toml";
1935
- return base.replace(/\.toml$/i, "");
1936
- }
1937
- function formatTimestamp(d) {
1938
- const pad = (n) => String(n).padStart(2, "0");
1939
- return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
1940
- }
1941
- function collapseBlankRunsNearDeletions(lines) {
1942
- const out = [];
1943
- let blankRun = 0;
1944
- for (const line of lines) {
1945
- if (line.trim() === "") {
1946
- blankRun++;
1947
- if (blankRun <= 2) out.push(line);
1948
- } else {
1949
- blankRun = 0;
1950
- out.push(line);
1951
- }
1952
- }
1953
- return out;
1954
- }
1955
- var init_codex_config_audit = __esm({
1956
- "packages/server/src/config/codex-config-audit.ts"() {
1957
- "use strict";
1958
- }
1959
2038
  });
1960
2039
 
1961
2040
  // packages/server/src/provider-runtime/command-runner.ts
@@ -2028,6 +2107,157 @@ var init_command_check = __esm({
2028
2107
  }
2029
2108
  });
2030
2109
 
2110
+ // packages/server/src/provider-runtime/e2e-provider-mock.ts
2111
+ import { chmodSync, existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
2112
+ import { dirname as dirname4, join as join3 } from "node:path";
2113
+ function createE2EProviderMockOverrides(env = process.env) {
2114
+ const statePath = env.CODER_STUDIO_E2E_PROVIDER_STATE_PATH;
2115
+ if (!statePath) {
2116
+ return null;
2117
+ }
2118
+ const binDir = env.CODER_STUDIO_E2E_PROVIDER_BIN_DIR;
2119
+ const debugLogPath = env.CODER_STUDIO_E2E_PROVIDER_DEBUG_LOG_PATH;
2120
+ appendDebugLog(debugLogPath, `init statePath=${statePath} binDir=${binDir ?? ""}`);
2121
+ const commandExists = async (command) => {
2122
+ const state = readMockState(statePath);
2123
+ const override = state.commands?.[command];
2124
+ appendDebugLog(
2125
+ debugLogPath,
2126
+ `commandExists ${command} override=${String(override)} state=${JSON.stringify(state.commands ?? {})}`
2127
+ );
2128
+ if (typeof override === "boolean") {
2129
+ return override;
2130
+ }
2131
+ return checkCommandAvailable(command);
2132
+ };
2133
+ const runCommand2 = async (file, args, options) => {
2134
+ const providerId = getInstallProviderId(file, args);
2135
+ appendDebugLog(
2136
+ debugLogPath,
2137
+ `runCommand ${file} ${args.join(" ")} provider=${providerId ?? "none"}`
2138
+ );
2139
+ if (!providerId) {
2140
+ return runCommandAsString(file, args, options);
2141
+ }
2142
+ const state = readMockState(statePath);
2143
+ const behavior = state.installBehavior?.[providerId];
2144
+ appendDebugLog(
2145
+ debugLogPath,
2146
+ `behavior ${providerId} ${JSON.stringify(behavior)} state=${JSON.stringify(state)}`
2147
+ );
2148
+ if (!behavior) {
2149
+ return runCommandAsString(file, args, options);
2150
+ }
2151
+ if (behavior.result === "success") {
2152
+ writeMockState(statePath, (draft) => {
2153
+ draft.commands ??= {};
2154
+ draft.commands[providerId] = true;
2155
+ });
2156
+ if (binDir) {
2157
+ ensureProviderCommand(binDir, providerId);
2158
+ }
2159
+ appendDebugLog(debugLogPath, `install success ${providerId}`);
2160
+ return {
2161
+ stdout: `installed ${providerId}`,
2162
+ stderr: ""
2163
+ };
2164
+ }
2165
+ const message = behavior.message ?? (behavior.result === "permission_denied" ? "permission denied" : "command not found");
2166
+ throw Object.assign(new Error(message), {
2167
+ exitCode: 1,
2168
+ stdout: "",
2169
+ stderr: message
2170
+ });
2171
+ };
2172
+ return {
2173
+ commandExists,
2174
+ runCommand: runCommand2
2175
+ };
2176
+ }
2177
+ function getInstallProviderId(file, args) {
2178
+ if (file !== "npm" || args.length !== 3) {
2179
+ return null;
2180
+ }
2181
+ if (args[0] !== "install" || args[1] !== "-g") {
2182
+ return null;
2183
+ }
2184
+ const packageName = args[2];
2185
+ if (packageName === PROVIDER_INSTALL_PACKAGES.claude) {
2186
+ return "claude";
2187
+ }
2188
+ if (packageName === PROVIDER_INSTALL_PACKAGES.codex) {
2189
+ return "codex";
2190
+ }
2191
+ return null;
2192
+ }
2193
+ function readMockState(statePath) {
2194
+ if (!existsSync6(statePath)) {
2195
+ return {};
2196
+ }
2197
+ const raw = readFileSync5(statePath, "utf8");
2198
+ if (!raw.trim()) {
2199
+ return {};
2200
+ }
2201
+ try {
2202
+ return JSON.parse(raw);
2203
+ } catch (error) {
2204
+ throw new Error(
2205
+ `Invalid provider mock state at ${statePath}: ${error instanceof Error ? error.message : String(error)}`
2206
+ );
2207
+ }
2208
+ }
2209
+ function writeMockState(statePath, updater) {
2210
+ const nextState = readMockState(statePath);
2211
+ updater(nextState);
2212
+ mkdirSync3(dirname4(statePath), { recursive: true });
2213
+ writeFileSync3(statePath, JSON.stringify(nextState, null, 2));
2214
+ return nextState;
2215
+ }
2216
+ function ensureProviderCommand(binDir, providerId) {
2217
+ mkdirSync3(binDir, { recursive: true });
2218
+ const scriptPath = join3(binDir, providerId);
2219
+ writeFileSync3(scriptPath, PROVIDER_COMMAND_SCRIPTS[providerId], "utf8");
2220
+ chmodSync(scriptPath, 493);
2221
+ }
2222
+ function appendDebugLog(path10, line) {
2223
+ if (!path10) {
2224
+ return;
2225
+ }
2226
+ mkdirSync3(dirname4(path10), { recursive: true });
2227
+ writeFileSync3(path10, `${line}
2228
+ `, { flag: "a" });
2229
+ }
2230
+ var PROVIDER_INSTALL_PACKAGES, PROVIDER_COMMAND_SCRIPTS;
2231
+ var init_e2e_provider_mock = __esm({
2232
+ "packages/server/src/provider-runtime/e2e-provider-mock.ts"() {
2233
+ "use strict";
2234
+ init_command_check();
2235
+ init_command_runner();
2236
+ PROVIDER_INSTALL_PACKAGES = {
2237
+ claude: "@anthropic-ai/claude-code",
2238
+ codex: "@openai/codex"
2239
+ };
2240
+ PROVIDER_COMMAND_SCRIPTS = {
2241
+ claude: `#!/usr/bin/env bash
2242
+ set -euo pipefail
2243
+ trap 'exit 0' TERM INT
2244
+ printf 'Mock Claude ready\\n'
2245
+ while true; do
2246
+ sleep 1
2247
+ done
2248
+ `,
2249
+ codex: `#!/usr/bin/env bash
2250
+ set -euo pipefail
2251
+ trap 'exit 0' TERM INT
2252
+ printf 'Session ID: abcdef-123456\\n> '
2253
+ while true; do
2254
+ sleep 1
2255
+ done
2256
+ `
2257
+ };
2258
+ }
2259
+ });
2260
+
2031
2261
  // packages/server/src/provider-runtime/install-manager.ts
2032
2262
  import { randomUUID as randomUUID2 } from "node:crypto";
2033
2263
  function getErrorDetails(error) {
@@ -4188,7 +4418,7 @@ function parseStatus(porcelainV2) {
4188
4418
  continue;
4189
4419
  }
4190
4420
  if (record.startsWith("? ")) {
4191
- untracked.push({ path: record.substring(2) });
4421
+ untracked.push({ path: record.substring(2), status: "untracked" });
4192
4422
  }
4193
4423
  }
4194
4424
  return {
@@ -4209,11 +4439,11 @@ function parseOrdinaryChangedEntry(record, staged, modified, deleted) {
4209
4439
  if (!xy) {
4210
4440
  return;
4211
4441
  }
4212
- const path9 = parts.slice(8).join(" ");
4213
- if (!path9) {
4442
+ const path10 = parts.slice(8).join(" ");
4443
+ if (!path10) {
4214
4444
  return;
4215
4445
  }
4216
- pushChange({ path: path9 }, xy, staged, modified, deleted);
4446
+ pushChange({ path: path10 }, xy, staged, modified, deleted);
4217
4447
  }
4218
4448
  function parseRenamedEntry(record, oldPathRecord, staged, modified, deleted) {
4219
4449
  const parts = record.split(" ");
@@ -4225,27 +4455,52 @@ function parseRenamedEntry(record, oldPathRecord, staged, modified, deleted) {
4225
4455
  const pathAndMaybeOldPath = pathTokens.join(" ");
4226
4456
  const inlinePathParts = pathAndMaybeOldPath.split(" ");
4227
4457
  const fallbackPath = !oldPathRecord && inlinePathParts.length === 1 && pathTokens.length > 1 ? pathTokens.slice(0, -1).join(" ") : void 0;
4228
- const path9 = fallbackPath ?? inlinePathParts[0];
4229
- if (!path9) {
4458
+ const path10 = fallbackPath ?? inlinePathParts[0];
4459
+ if (!path10) {
4230
4460
  return;
4231
4461
  }
4232
4462
  const oldPath = (oldPathRecord && !oldPathRecord.startsWith("#") ? oldPathRecord : void 0) ?? inlinePathParts[1] ?? (pathTokens.length > 1 ? pathTokens[pathTokens.length - 1] : void 0);
4233
- pushChange({ path: path9, oldPath }, xy, staged, modified, deleted);
4463
+ pushChange({ path: path10, oldPath }, xy, staged, modified, deleted);
4234
4464
  }
4235
4465
  function pushChange(change, xy, staged, modified, deleted) {
4236
4466
  const indexStatus = xy[0];
4237
4467
  const worktreeStatus = xy[1];
4238
4468
  if (indexStatus && indexStatus !== "." && indexStatus !== " ") {
4239
- staged.push(change);
4469
+ staged.push({
4470
+ ...change,
4471
+ status: resolveGitChangeStatus(indexStatus, change.oldPath)
4472
+ });
4240
4473
  }
4241
4474
  if (!worktreeStatus || worktreeStatus === "." || worktreeStatus === " ") {
4242
4475
  return;
4243
4476
  }
4244
4477
  if (worktreeStatus === "D") {
4245
- deleted.push({ path: change.path });
4478
+ deleted.push({ path: change.path, status: "deleted" });
4246
4479
  return;
4247
4480
  }
4248
- modified.push({ path: change.path });
4481
+ modified.push({
4482
+ path: change.path,
4483
+ oldPath: change.oldPath,
4484
+ status: resolveGitChangeStatus(worktreeStatus, change.oldPath)
4485
+ });
4486
+ }
4487
+ function resolveGitChangeStatus(code, oldPath) {
4488
+ switch (code) {
4489
+ case "A":
4490
+ return "added";
4491
+ case "D":
4492
+ return "deleted";
4493
+ case "R":
4494
+ return "renamed";
4495
+ case "C":
4496
+ return oldPath ? "renamed" : "added";
4497
+ case "U":
4498
+ return "modified";
4499
+ case "M":
4500
+ case "T":
4501
+ default:
4502
+ return oldPath ? "renamed" : "modified";
4503
+ }
4249
4504
  }
4250
4505
  var init_status_parser = __esm({
4251
4506
  "packages/server/src/git/status-parser.ts"() {
@@ -4315,6 +4570,40 @@ async function getGitStatus(cwd) {
4315
4570
  headSubject: headSubjectOutput.trim()
4316
4571
  };
4317
4572
  }
4573
+ async function getGitHistory(cwd, limit = 5) {
4574
+ try {
4575
+ const { stdout } = await runGit(cwd, [
4576
+ "log",
4577
+ `--max-count=${Math.max(1, limit)}`,
4578
+ "--format=%H%x1f%h%x1f%s%x1f%an%x1f%at%x1e"
4579
+ ]);
4580
+ return stdout.split("").map((record) => record.trim()).filter((record) => record.length > 0).map((record) => {
4581
+ const [sha = "", shortSha = "", subject = "", authorName = "", authoredAt = "0"] = record.split("");
4582
+ return {
4583
+ sha,
4584
+ shortSha,
4585
+ subject,
4586
+ authorName,
4587
+ authoredAt: Number.parseInt(authoredAt, 10) * 1e3
4588
+ };
4589
+ }).filter((entry) => entry.sha && entry.subject);
4590
+ } catch (error) {
4591
+ if (error instanceof GitError && /does not have any commits yet/i.test(error.stderr)) {
4592
+ return [];
4593
+ }
4594
+ throw error;
4595
+ }
4596
+ }
4597
+ async function getGitCommitDiff(cwd, sha) {
4598
+ const { stdout } = await runGit(cwd, [
4599
+ "show",
4600
+ "--format=medium",
4601
+ "--no-color",
4602
+ "--end-of-options",
4603
+ sha
4604
+ ]);
4605
+ return stdout;
4606
+ }
4318
4607
  async function getGitStatusSummary(cwd) {
4319
4608
  const { stdout } = await runGit(cwd, ["status", "--short"]);
4320
4609
  return stdout.trim();
@@ -4335,12 +4624,12 @@ async function discardChanges(cwd, paths) {
4335
4624
  if (paths.length === 0) return;
4336
4625
  const trackedPaths = [];
4337
4626
  const untrackedPaths = [];
4338
- for (const path9 of paths) {
4627
+ for (const path10 of paths) {
4339
4628
  try {
4340
- await runGit(cwd, ["ls-files", "--error-unmatch", "--", path9]);
4341
- trackedPaths.push(path9);
4629
+ await runGit(cwd, ["ls-files", "--error-unmatch", "--", path10]);
4630
+ trackedPaths.push(path10);
4342
4631
  } catch {
4343
- untrackedPaths.push(path9);
4632
+ untrackedPaths.push(path10);
4344
4633
  }
4345
4634
  }
4346
4635
  if (trackedPaths.length > 0) {
@@ -4379,6 +4668,7 @@ async function runGitPush(cwd, options) {
4379
4668
  if (!remote) {
4380
4669
  remote = await getPreferredRemote(cwd) ?? void 0;
4381
4670
  }
4671
+ const summaryBranch = branch ?? await getCurrentBranchName(cwd);
4382
4672
  if (remote && branch) {
4383
4673
  args.push(remote, `HEAD:${branch}`);
4384
4674
  } else if (remote) {
@@ -4396,8 +4686,13 @@ async function runGitPush(cwd, options) {
4396
4686
  if (options?.auth) {
4397
4687
  await persistGitHttpCredentials(cwd, options.auth, remoteMetadata);
4398
4688
  }
4399
- const message = stdout || stderr || "Push completed successfully";
4400
- return { success: true, message };
4689
+ return {
4690
+ success: true,
4691
+ message: "Push completed successfully",
4692
+ remote,
4693
+ branch: summaryBranch,
4694
+ updated: !isPushUpToDate(stdout, stderr)
4695
+ };
4401
4696
  } catch (error) {
4402
4697
  throw normalizeGitAuthFailure(error, {
4403
4698
  operation: "push",
@@ -4421,6 +4716,7 @@ async function runGitPull(cwd, options) {
4421
4716
  if (!remote && branch) {
4422
4717
  remote = await getPreferredRemote(cwd) ?? "origin";
4423
4718
  }
4719
+ const summaryBranch = branch ?? await getCurrentBranchName(cwd);
4424
4720
  if (remote && branch) {
4425
4721
  args.push(remote, branch);
4426
4722
  }
@@ -4450,8 +4746,14 @@ async function runGitPull(cwd, options) {
4450
4746
  }
4451
4747
  }
4452
4748
  }
4453
- const message = stdout || stderr || "Pull completed successfully";
4454
- return { success: true, message, updatedFiles };
4749
+ return {
4750
+ success: true,
4751
+ message: "Pull completed successfully",
4752
+ remote,
4753
+ branch: summaryBranch,
4754
+ updated: !isPullUpToDate(stdout, stderr),
4755
+ updatedFiles
4756
+ };
4455
4757
  } catch (error) {
4456
4758
  throw normalizeGitAuthFailure(error, {
4457
4759
  operation: "pull",
@@ -4463,8 +4765,63 @@ async function runGitPull(cwd, options) {
4463
4765
  await authExecution.cleanup();
4464
4766
  }
4465
4767
  }
4768
+ async function runGitFetch(cwd, options) {
4769
+ const args = ["fetch"];
4770
+ const remote = options?.remote;
4771
+ const metadataRemote = remote ?? await getPreferredRemote(cwd) ?? void 0;
4772
+ const prune = options?.prune ?? true;
4773
+ if (remote) {
4774
+ args.push(remote);
4775
+ } else {
4776
+ args.push("--all");
4777
+ }
4778
+ if (prune) {
4779
+ args.push("--prune");
4780
+ }
4781
+ const remoteUrl = metadataRemote ? await getRemoteUrl(cwd, metadataRemote) : null;
4782
+ const remoteMetadata = parseRemoteUrlMetadata(remoteUrl ?? void 0);
4783
+ const authExecution = await prepareGitAuthExecution(options?.auth, remoteMetadata);
4784
+ try {
4785
+ const { stdout, stderr } = await runGit(cwd, args, {
4786
+ timeoutMs: options?.timeoutMs ?? GIT_NETWORK_TIMEOUT_MS,
4787
+ env: authExecution.env,
4788
+ config: authExecution.config
4789
+ });
4790
+ if (options?.auth) {
4791
+ await persistGitHttpCredentials(cwd, options.auth, remoteMetadata);
4792
+ }
4793
+ const message = stdout || stderr || "Fetch completed successfully";
4794
+ return {
4795
+ success: true,
4796
+ message,
4797
+ updatedRefs: parseFetchUpdatedRefs(stderr)
4798
+ };
4799
+ } catch (error) {
4800
+ throw normalizeGitAuthFailure(error, {
4801
+ operation: "fetch",
4802
+ remote: metadataRemote,
4803
+ remoteUrl: remoteMetadata.sanitizedUrl ?? remoteUrl ?? void 0,
4804
+ attemptedCredentialAuth: Boolean(options?.auth)
4805
+ });
4806
+ } finally {
4807
+ await authExecution.cleanup();
4808
+ }
4809
+ }
4810
+ function parseFetchUpdatedRefs(stderr) {
4811
+ const refs = [];
4812
+ for (const rawLine of stderr.split("\n")) {
4813
+ const line = rawLine.trimEnd();
4814
+ const arrowIndex = line.indexOf(" -> ");
4815
+ if (arrowIndex < 0) continue;
4816
+ const target = line.slice(arrowIndex + 4).trim();
4817
+ if (!target) continue;
4818
+ refs.push(target);
4819
+ }
4820
+ return refs;
4821
+ }
4466
4822
  async function runGitCheckout(cwd, ref, options) {
4467
4823
  const args = ["checkout"];
4824
+ const formatCheckoutError = (error, fallbackMessage) => error instanceof GitError ? error.stderr.trim() || error.message || fallbackMessage : fallbackMessage;
4468
4825
  let isRemoteRef = false;
4469
4826
  try {
4470
4827
  const { stdout: remoteList } = await runGit(cwd, ["remote"]);
@@ -4476,15 +4833,22 @@ async function runGitCheckout(cwd, ref, options) {
4476
4833
  if (isRemoteRef && !options?.createBranch) {
4477
4834
  const remoteSeparatorIndex = ref.indexOf("/");
4478
4835
  const branchName = remoteSeparatorIndex >= 0 ? ref.slice(remoteSeparatorIndex + 1) : ref;
4479
- args.push("-b", branchName, ref);
4836
+ try {
4837
+ await runGit(cwd, ["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`]);
4838
+ const { stdout, stderr } = await runGit(cwd, ["checkout", branchName]);
4839
+ const message = stdout || stderr || `Checkout to ${branchName} completed`;
4840
+ return { success: true, message, branch: branchName };
4841
+ } catch {
4842
+ args.push("-b", branchName, ref);
4843
+ }
4480
4844
  try {
4481
4845
  const { stdout, stderr } = await runGit(cwd, args);
4482
4846
  const message = stdout || stderr || `Checkout to ${ref} completed`;
4483
4847
  return { success: true, message, branch: branchName };
4484
- } catch {
4848
+ } catch (error) {
4485
4849
  return {
4486
4850
  success: false,
4487
- message: `Failed to checkout remote branch '${ref}'`
4851
+ message: formatCheckoutError(error, `Failed to checkout remote branch '${ref}'`)
4488
4852
  };
4489
4853
  }
4490
4854
  } else {
@@ -4498,10 +4862,10 @@ async function runGitCheckout(cwd, ref, options) {
4498
4862
  const branch = branchMatch?.[1] ?? ref;
4499
4863
  const message = stdout || stderr || `Checkout to ${ref} completed`;
4500
4864
  return { success: true, message, branch };
4501
- } catch {
4865
+ } catch (error) {
4502
4866
  return {
4503
4867
  success: false,
4504
- message: `Failed to checkout '${ref}'`
4868
+ message: formatCheckoutError(error, `Failed to checkout '${ref}'`)
4505
4869
  };
4506
4870
  }
4507
4871
  }
@@ -4516,23 +4880,42 @@ async function runGitCreateBranch(cwd, branchName, options) {
4516
4880
  }
4517
4881
  async function runGitListBranches(cwd) {
4518
4882
  const { stdout: localOutput } = await runGit(cwd, ["branch", "--list"]);
4883
+ const { stdout: localVerboseOutput } = await runGit(cwd, ["branch", "--list", "-vv"]);
4519
4884
  const { stdout: remoteOutput } = await runGit(cwd, ["branch", "-r"]);
4520
4885
  const branches = [];
4521
4886
  let current = "";
4887
+ const linkedWorktreePathsByBranch = /* @__PURE__ */ new Map();
4888
+ const localVerboseLines = localVerboseOutput.split("\n").filter((line) => line.trim());
4889
+ for (const line of localVerboseLines) {
4890
+ const normalizedLine = line.replace(/^[*+ ]\s+/, "");
4891
+ const branchMatch = normalizedLine.match(/^([^\s]+)\s+/);
4892
+ const worktreeMatch = line.match(/\((.+?)\)\s/);
4893
+ if (!branchMatch?.[1] || !worktreeMatch?.[1]) {
4894
+ continue;
4895
+ }
4896
+ const worktreePath = worktreeMatch[1];
4897
+ if (worktreePath.startsWith("/") || worktreePath.startsWith("~")) {
4898
+ linkedWorktreePathsByBranch.set(branchMatch[1], worktreePath);
4899
+ }
4900
+ }
4522
4901
  const localLines = localOutput.split("\n").filter((line) => line.trim());
4523
4902
  for (const line of localLines) {
4524
4903
  const isCurrent = line.startsWith("*");
4525
- const name = line.replace(/^\*?\s+/, "").trim();
4904
+ const name = line.replace(/^[*+ ]\s+/, "").trim();
4526
4905
  if (name.startsWith("(HEAD detached")) {
4527
4906
  if (isCurrent) {
4528
4907
  current = "";
4529
4908
  }
4530
4909
  continue;
4531
4910
  }
4911
+ if (linkedWorktreePathsByBranch.has(name) && !isCurrent) {
4912
+ continue;
4913
+ }
4532
4914
  branches.push({
4533
4915
  name,
4534
4916
  isRemote: false,
4535
- isCurrent
4917
+ isCurrent,
4918
+ linkedWorktreePath: linkedWorktreePathsByBranch.get(name)
4536
4919
  });
4537
4920
  if (isCurrent) {
4538
4921
  current = name;
@@ -4580,6 +4963,23 @@ async function resolveRemoteBranchTarget(cwd, mode) {
4580
4963
  return null;
4581
4964
  }
4582
4965
  }
4966
+ async function getCurrentBranchName(cwd) {
4967
+ try {
4968
+ const { stdout } = await runGit(cwd, ["branch", "--show-current"]);
4969
+ const branch = stdout.trim();
4970
+ return branch || void 0;
4971
+ } catch {
4972
+ return void 0;
4973
+ }
4974
+ }
4975
+ function isPushUpToDate(stdout, stderr) {
4976
+ return /Everything up-to-date/i.test(`${stdout}
4977
+ ${stderr}`);
4978
+ }
4979
+ function isPullUpToDate(stdout, stderr) {
4980
+ return /Already up[ -]to[ -]date\.?/i.test(`${stdout}
4981
+ ${stderr}`);
4982
+ }
4583
4983
  async function getPreferredRemote(cwd) {
4584
4984
  try {
4585
4985
  const { stdout } = await runGit(cwd, ["remote"]);
@@ -4964,7 +5364,7 @@ var init_context_builder = __esm({
4964
5364
  });
4965
5365
 
4966
5366
  // packages/server/src/terminal/pty-host.ts
4967
- import { chmodSync, existsSync as existsSync7, statSync } from "node:fs";
5367
+ import { chmodSync as chmodSync2, existsSync as existsSync7, statSync } from "node:fs";
4968
5368
  import { createRequire } from "node:module";
4969
5369
  import path6 from "node:path";
4970
5370
  function ensureNodePtySpawnHelperExecutable(deps = {}) {
@@ -4976,7 +5376,7 @@ function ensureNodePtySpawnHelperExecutable(deps = {}) {
4976
5376
  const resolve4 = deps.resolve ?? ((id) => require2.resolve(id));
4977
5377
  const fileExists = deps.existsSync ?? existsSync7;
4978
5378
  const stat7 = deps.statSync ?? statSync;
4979
- const chmod = deps.chmodSync ?? chmodSync;
5379
+ const chmod = deps.chmodSync ?? chmodSync2;
4980
5380
  let packageJsonPath;
4981
5381
  try {
4982
5382
  packageJsonPath = resolve4(NODE_PTY_PKG);
@@ -7011,14 +7411,14 @@ var init_manager3 = __esm({
7011
7411
  // packages/server/src/workspace/validator.ts
7012
7412
  import { constants } from "fs";
7013
7413
  import { access, stat as stat5 } from "fs/promises";
7014
- async function validatePath(path9) {
7414
+ async function validatePath(path10) {
7015
7415
  try {
7016
- const stats = await stat5(path9);
7416
+ const stats = await stat5(path10);
7017
7417
  if (!stats.isDirectory()) {
7018
7418
  return { valid: false, error: "Path is not a directory" };
7019
7419
  }
7020
- await access(path9, constants.R_OK);
7021
- await access(path9, constants.W_OK);
7420
+ await access(path10, constants.R_OK);
7421
+ await access(path10, constants.W_OK);
7022
7422
  return { valid: true };
7023
7423
  } catch (error) {
7024
7424
  if (error.code === "ENOENT") {
@@ -7035,8 +7435,8 @@ var init_validator = __esm({
7035
7435
  "packages/server/src/workspace/validator.ts"() {
7036
7436
  "use strict";
7037
7437
  WorkspaceValidator = class {
7038
- async validate(path9) {
7039
- const result = await validatePath(path9);
7438
+ async validate(path10) {
7439
+ const result = await validatePath(path10);
7040
7440
  if (!result.valid) {
7041
7441
  throw new Error(`Invalid workspace path: ${result.error}`);
7042
7442
  }
@@ -7048,12 +7448,12 @@ var init_validator = __esm({
7048
7448
  // packages/server/src/fs/gitignore.ts
7049
7449
  import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
7050
7450
  import ignore from "ignore";
7051
- import { join as join5, relative } from "path";
7052
- function normalizePath(path9) {
7053
- return path9.replace(/\\/g, "/");
7451
+ import { join as join5, relative as relative3 } from "path";
7452
+ function normalizePath(path10) {
7453
+ return path10.replace(/\\/g, "/");
7054
7454
  }
7055
- function relativeToRoot(rootPath, path9) {
7056
- return normalizePath(relative(rootPath, path9));
7455
+ function relativeToRoot(rootPath, path10) {
7456
+ return normalizePath(relative3(rootPath, path10));
7057
7457
  }
7058
7458
  function isDefaultTreeIgnored(name) {
7059
7459
  return name.startsWith(".") || name === "node_modules" || name === ".git";
@@ -7061,11 +7461,11 @@ function isDefaultTreeIgnored(name) {
7061
7461
  function isAlwaysTreeIgnored(name) {
7062
7462
  return name === "node_modules" || name === ".git";
7063
7463
  }
7064
- function isIgnoredByGitignore(ig, path9) {
7065
- if (!path9 || path9.startsWith("..")) {
7464
+ function isIgnoredByGitignore(ig, path10) {
7465
+ if (!path10 || path10.startsWith("..")) {
7066
7466
  return false;
7067
7467
  }
7068
- return ig.ignores(path9) || ig.ignores(`${path9}/`);
7468
+ return ig.ignores(path10) || ig.ignores(`${path10}/`);
7069
7469
  }
7070
7470
  function createGitignoreFilter(rootPath, dirPath) {
7071
7471
  const gitignorePath = join5(rootPath, ".gitignore");
@@ -7085,16 +7485,16 @@ function createGitignoreFilter(rootPath, dirPath) {
7085
7485
  function createWatcherIgnoreFilter(rootPath) {
7086
7486
  const gitignorePath = join5(rootPath, ".gitignore");
7087
7487
  if (!existsSync8(gitignorePath)) {
7088
- return (path9) => DEFAULT_WATCHER_IGNORED_PATTERNS.some((p) => p.test(normalizePath(path9)));
7488
+ return (path10) => DEFAULT_WATCHER_IGNORED_PATTERNS.some((p) => p.test(normalizePath(path10)));
7089
7489
  }
7090
7490
  const gitignoreContent = readFileSync7(gitignorePath, "utf-8");
7091
7491
  const ig = ignore().add(gitignoreContent);
7092
- return (path9) => {
7093
- const normalizedPath = normalizePath(path9);
7492
+ return (path10) => {
7493
+ const normalizedPath = normalizePath(path10);
7094
7494
  if (DEFAULT_WATCHER_IGNORED_PATTERNS.some((p) => p.test(normalizedPath))) {
7095
7495
  return true;
7096
7496
  }
7097
- const relativePath = relativeToRoot(rootPath, path9);
7497
+ const relativePath = relativeToRoot(rootPath, path10);
7098
7498
  return isIgnoredByGitignore(ig, relativePath);
7099
7499
  };
7100
7500
  }
@@ -7214,6 +7614,11 @@ var init_manager4 = __esm({
7214
7614
  new WorkspaceWatcher(workspaceId, rootPath, this.deps.broadcaster)
7215
7615
  );
7216
7616
  }
7617
+ hydrateWatchers() {
7618
+ for (const workspace of this.list()) {
7619
+ this.startWatcher(workspace.id, workspace.path);
7620
+ }
7621
+ }
7217
7622
  updateUiState(workspaceId, uiState) {
7218
7623
  const workspace = this.get(workspaceId);
7219
7624
  if (!workspace) {
@@ -7248,6 +7653,7 @@ var init_manager4 = __esm({
7248
7653
  workspaceId: existing.id,
7249
7654
  patch: { lastActiveAt: Date.now() }
7250
7655
  });
7656
+ this.deps.autoFetch?.triggerOpenTimeFetch(existing.id);
7251
7657
  return existing;
7252
7658
  }
7253
7659
  const workspace = {
@@ -7285,6 +7691,7 @@ var init_manager4 = __esm({
7285
7691
  patch: workspace
7286
7692
  });
7287
7693
  this.startWatcher(workspace.id, workspace.path);
7694
+ this.deps.autoFetch?.triggerOpenTimeFetch(workspace.id);
7288
7695
  return workspace;
7289
7696
  }
7290
7697
  /**
@@ -7369,12 +7776,12 @@ var init_manager4 = __esm({
7369
7776
  * @param path - Workspace path
7370
7777
  * @returns Workspace or undefined
7371
7778
  */
7372
- getByPath(path9) {
7779
+ getByPath(path10) {
7373
7780
  const row = this.deps.db.prepare(
7374
7781
  `SELECT id, path, target_runtime, wsl_distro, opened_at, last_active_at, ui_state
7375
7782
  FROM workspaces
7376
7783
  WHERE path = ?`
7377
- ).get(path9);
7784
+ ).get(path10);
7378
7785
  if (!row) return void 0;
7379
7786
  return {
7380
7787
  id: row.id,
@@ -7395,13 +7802,87 @@ var init_manager4 = __esm({
7395
7802
  const now = Date.now();
7396
7803
  this.deps.db.prepare("UPDATE workspaces SET last_active_at = ? WHERE id = ?").run(now, workspaceId);
7397
7804
  }
7805
+ recordFetch(workspaceId) {
7806
+ this.deps.autoFetch?.recordSuccess(workspaceId);
7807
+ }
7398
7808
  };
7399
7809
  }
7400
7810
  });
7401
7811
 
7402
- // packages/server/src/ws/fencing.ts
7403
- var DEFAULT_OPTIONS, FencingManager;
7404
- var init_fencing = __esm({
7812
+ // packages/server/src/ws/dispatch.ts
7813
+ function registerCommand(op, schema, handler) {
7814
+ handlers.set(op, handler);
7815
+ schemas.set(op, schema);
7816
+ }
7817
+ async function dispatch(msg, ctx, clientId) {
7818
+ const handler = handlers.get(msg.op);
7819
+ if (!handler) {
7820
+ return {
7821
+ kind: "result",
7822
+ id: msg.id,
7823
+ ok: false,
7824
+ error: {
7825
+ code: "unknown_op",
7826
+ message: `Unknown operation: ${msg.op}`
7827
+ }
7828
+ };
7829
+ }
7830
+ try {
7831
+ const schema = schemas.get(msg.op);
7832
+ let args = msg.args;
7833
+ if (schema) {
7834
+ args = schema.parse(msg.args);
7835
+ }
7836
+ const data = await handler(args, ctx, clientId);
7837
+ return {
7838
+ kind: "result",
7839
+ id: msg.id,
7840
+ ok: true,
7841
+ data
7842
+ };
7843
+ } catch (error) {
7844
+ const normalizedError = normalizeError(error);
7845
+ return {
7846
+ kind: "result",
7847
+ id: msg.id,
7848
+ ok: false,
7849
+ error: normalizedError
7850
+ };
7851
+ }
7852
+ }
7853
+ function normalizeError(error) {
7854
+ const candidate = error;
7855
+ if (candidate.name === "ZodError") {
7856
+ return {
7857
+ code: "validation_error",
7858
+ message: "Invalid arguments",
7859
+ details: candidate.errors
7860
+ };
7861
+ }
7862
+ if (candidate.code) {
7863
+ return {
7864
+ code: candidate.code,
7865
+ message: candidate.message ?? String(candidate.code),
7866
+ details: candidate.details
7867
+ };
7868
+ }
7869
+ return {
7870
+ code: "internal_error",
7871
+ message: candidate.message || "An internal error occurred"
7872
+ };
7873
+ }
7874
+ var handlers, schemas;
7875
+ var init_dispatch = __esm({
7876
+ "packages/server/src/ws/dispatch.ts"() {
7877
+ "use strict";
7878
+ handlers = /* @__PURE__ */ new Map();
7879
+ schemas = /* @__PURE__ */ new Map();
7880
+ }
7881
+ });
7882
+
7883
+ // packages/server/src/ws/fencing.ts
7884
+ var DEFAULT_OPTIONS, FencingManager;
7885
+ var init_fencing = __esm({
7405
7886
  "packages/server/src/ws/fencing.ts"() {
7406
7887
  "use strict";
7407
7888
  DEFAULT_OPTIONS = {
@@ -7571,127 +8052,6 @@ var init_fencing = __esm({
7571
8052
  }
7572
8053
  });
7573
8054
 
7574
- // packages/server/src/ws/dispatch.ts
7575
- async function debounce(key, op, windowMs) {
7576
- let entry = debounceMap.get(key);
7577
- if (entry) {
7578
- clearTimeout(entry.timer);
7579
- entry.op = op;
7580
- } else {
7581
- let resolve4;
7582
- let reject;
7583
- const promise = new Promise((res, rej) => {
7584
- resolve4 = res;
7585
- reject = rej;
7586
- });
7587
- entry = {
7588
- timer: void 0,
7589
- promise,
7590
- resolve: resolve4,
7591
- reject,
7592
- op
7593
- };
7594
- debounceMap.set(key, entry);
7595
- }
7596
- entry.timer = setTimeout(async () => {
7597
- debounceMap.delete(key);
7598
- try {
7599
- const result = await entry.op();
7600
- entry.resolve(result);
7601
- } catch (err) {
7602
- entry.reject(err);
7603
- }
7604
- }, windowMs);
7605
- return entry.promise;
7606
- }
7607
- function registerCommand(op, schema, handler) {
7608
- handlers.set(op, handler);
7609
- schemas.set(op, schema);
7610
- }
7611
- async function dispatch(msg, ctx, clientId) {
7612
- const handler = handlers.get(msg.op);
7613
- if (!handler) {
7614
- return {
7615
- kind: "result",
7616
- id: msg.id,
7617
- ok: false,
7618
- error: {
7619
- code: "unknown_op",
7620
- message: `Unknown operation: ${msg.op}`
7621
- }
7622
- };
7623
- }
7624
- try {
7625
- const schema = schemas.get(msg.op);
7626
- let args = msg.args;
7627
- if (schema) {
7628
- args = schema.parse(msg.args);
7629
- }
7630
- const data = await executeWithDebounce(msg.op, args, ctx, clientId);
7631
- return {
7632
- kind: "result",
7633
- id: msg.id,
7634
- ok: true,
7635
- data
7636
- };
7637
- } catch (error) {
7638
- const normalizedError = normalizeError(error);
7639
- return {
7640
- kind: "result",
7641
- id: msg.id,
7642
- ok: false,
7643
- error: normalizedError
7644
- };
7645
- }
7646
- }
7647
- async function executeWithDebounce(op, args, ctx, clientId) {
7648
- const handler = handlers.get(op);
7649
- if (op === "git.status") {
7650
- const workspaceId = getWorkspaceId(args);
7651
- const key = workspaceId ? `git.status:${workspaceId}` : op;
7652
- return debounce(key, () => handler(args, ctx, clientId), DEBOUNCE_GIT_STATUS_MS);
7653
- }
7654
- return handler(args, ctx, clientId);
7655
- }
7656
- function getWorkspaceId(args) {
7657
- if (typeof args !== "object" || args === null || !("workspaceId" in args)) {
7658
- return void 0;
7659
- }
7660
- const workspaceId = args.workspaceId;
7661
- return typeof workspaceId === "string" ? workspaceId : void 0;
7662
- }
7663
- function normalizeError(error) {
7664
- const candidate = error;
7665
- if (candidate.name === "ZodError") {
7666
- return {
7667
- code: "validation_error",
7668
- message: "Invalid arguments",
7669
- details: candidate.errors
7670
- };
7671
- }
7672
- if (candidate.code) {
7673
- return {
7674
- code: candidate.code,
7675
- message: candidate.message ?? String(candidate.code),
7676
- details: candidate.details
7677
- };
7678
- }
7679
- return {
7680
- code: "internal_error",
7681
- message: candidate.message || "An internal error occurred"
7682
- };
7683
- }
7684
- var handlers, schemas, debounceMap, DEBOUNCE_GIT_STATUS_MS;
7685
- var init_dispatch = __esm({
7686
- "packages/server/src/ws/dispatch.ts"() {
7687
- "use strict";
7688
- handlers = /* @__PURE__ */ new Map();
7689
- schemas = /* @__PURE__ */ new Map();
7690
- debounceMap = /* @__PURE__ */ new Map();
7691
- DEBOUNCE_GIT_STATUS_MS = 500;
7692
- }
7693
- });
7694
-
7695
8055
  // packages/server/src/commands/terminal.ts
7696
8056
  import { basename as basename2 } from "node:path";
7697
8057
  import { z as z5 } from "zod";
@@ -8540,6 +8900,7 @@ var init_hub = __esm({
8540
8900
  handleClose(client) {
8541
8901
  this.clients.delete(client.id);
8542
8902
  this.discardPendingBinaryWaiters(client.id);
8903
+ this.deps.commandContext?.autoFetch.unregisterViewer(client.id);
8543
8904
  }
8544
8905
  /**
8545
8906
  * Takeover: Force close existing writer and accept new one
@@ -8732,7 +9093,7 @@ var init_hub = __esm({
8732
9093
 
8733
9094
  // packages/server/src/commands/workspace.ts
8734
9095
  import { readdir as readdir2 } from "node:fs/promises";
8735
- import { homedir as homedir4 } from "node:os";
9096
+ import { homedir as homedir3 } from "node:os";
8736
9097
  import { join as join6 } from "node:path";
8737
9098
  import { z as z6 } from "zod";
8738
9099
  var init_workspace = __esm({
@@ -8748,7 +9109,7 @@ var init_workspace = __esm({
8748
9109
  path: z6.string().optional()
8749
9110
  }),
8750
9111
  async (args) => {
8751
- const basePath = args.path || homedir4();
9112
+ const basePath = args.path || homedir3();
8752
9113
  const entries = await readdir2(basePath, { withFileTypes: true });
8753
9114
  const directories = entries.filter((entry) => entry.isDirectory()).map((entry) => ({
8754
9115
  name: entry.name,
@@ -8821,6 +9182,35 @@ var init_workspace = __esm({
8821
9182
  }
8822
9183
  });
8823
9184
 
9185
+ // packages/server/src/commands/workspace-activity.ts
9186
+ import { z as z7 } from "zod";
9187
+ var init_workspace_activity = __esm({
9188
+ "packages/server/src/commands/workspace-activity.ts"() {
9189
+ "use strict";
9190
+ init_dispatch();
9191
+ registerCommand(
9192
+ "workspace.activate",
9193
+ z7.object({
9194
+ workspaceId: z7.string()
9195
+ }),
9196
+ async (args, ctx, clientId) => {
9197
+ if (!clientId) {
9198
+ return {};
9199
+ }
9200
+ ctx.autoFetch.registerViewer(clientId, args.workspaceId);
9201
+ return {};
9202
+ }
9203
+ );
9204
+ registerCommand("workspace.deactivate", z7.object({}), async (_args, ctx, clientId) => {
9205
+ if (!clientId) {
9206
+ return {};
9207
+ }
9208
+ ctx.autoFetch.unregisterViewer(clientId);
9209
+ return {};
9210
+ });
9211
+ }
9212
+ });
9213
+
8824
9214
  // packages/server/src/provider-runtime/runtime-status.ts
8825
9215
  function canAutoInstall(provider, platform, missingCommands, missingPrerequisites, availableCommands) {
8826
9216
  const strategies = provider.install.strategies[platform] ?? [];
@@ -8915,7 +9305,7 @@ var init_runtime_status = __esm({
8915
9305
  });
8916
9306
 
8917
9307
  // packages/server/src/commands/session.ts
8918
- import { z as z7 } from "zod";
9308
+ import { z as z8 } from "zod";
8919
9309
  function getProviderFromRegistry(providerId, registry) {
8920
9310
  return registry.find((provider) => provider.id === providerId);
8921
9311
  }
@@ -8926,8 +9316,8 @@ var init_session = __esm({
8926
9316
  init_dispatch();
8927
9317
  registerCommand(
8928
9318
  "session.list",
8929
- z7.object({
8930
- workspaceId: z7.string()
9319
+ z8.object({
9320
+ workspaceId: z8.string()
8931
9321
  }),
8932
9322
  async (args, ctx) => {
8933
9323
  return ctx.sessionMgr.getForWorkspace(args.workspaceId);
@@ -8935,10 +9325,10 @@ var init_session = __esm({
8935
9325
  );
8936
9326
  registerCommand(
8937
9327
  "session.create",
8938
- z7.object({
8939
- workspaceId: z7.string(),
8940
- providerId: z7.string(),
8941
- draft: z7.string().optional()
9328
+ z8.object({
9329
+ workspaceId: z8.string(),
9330
+ providerId: z8.string(),
9331
+ draft: z8.string().optional()
8942
9332
  }),
8943
9333
  async (args, ctx) => {
8944
9334
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
@@ -8972,8 +9362,8 @@ var init_session = __esm({
8972
9362
  );
8973
9363
  registerCommand(
8974
9364
  "session.stop",
8975
- z7.object({
8976
- sessionId: z7.string()
9365
+ z8.object({
9366
+ sessionId: z8.string()
8977
9367
  }),
8978
9368
  async (args, ctx) => {
8979
9369
  await ctx.sessionMgr.stop(args.sessionId);
@@ -8981,8 +9371,8 @@ var init_session = __esm({
8981
9371
  );
8982
9372
  registerCommand(
8983
9373
  "session.remove",
8984
- z7.object({
8985
- sessionId: z7.string()
9374
+ z8.object({
9375
+ sessionId: z8.string()
8986
9376
  }),
8987
9377
  async (args, ctx) => {
8988
9378
  const session = ctx.sessionMgr.get(args.sessionId);
@@ -9000,7 +9390,7 @@ var init_session = __esm({
9000
9390
 
9001
9391
  // packages/server/src/fs/tree.ts
9002
9392
  import { readdir as readdir3, stat as stat6 } from "fs/promises";
9003
- import { join as join7, relative as relative2 } from "path";
9393
+ import { join as join7, relative as relative4 } from "path";
9004
9394
  async function readTree(rootPath, subdir) {
9005
9395
  const targetPath = subdir ? join7(rootPath, subdir) : rootPath;
9006
9396
  const filter = createGitignoreFilter(rootPath, targetPath);
@@ -9011,7 +9401,7 @@ async function readTree(rootPath, subdir) {
9011
9401
  continue;
9012
9402
  }
9013
9403
  const fullPath = join7(targetPath, entry.name);
9014
- const relPath = relative2(rootPath, fullPath);
9404
+ const relPath = relative4(rootPath, fullPath);
9015
9405
  if (entry.isDirectory()) {
9016
9406
  nodes.push({
9017
9407
  name: entry.name,
@@ -9055,7 +9445,7 @@ async function searchFiles(rootPath, query, limit = 10) {
9055
9445
  filteredEntries.sort((a, b) => a.name.localeCompare(b.name));
9056
9446
  for (const entry of filteredEntries) {
9057
9447
  const fullPath = join7(dirPath, entry.name);
9058
- const relPath = relative2(rootPath, fullPath);
9448
+ const relPath = relative4(rootPath, fullPath);
9059
9449
  if (entry.isDirectory()) {
9060
9450
  await walk(fullPath);
9061
9451
  continue;
@@ -9147,7 +9537,7 @@ var init_tree = __esm({
9147
9537
  });
9148
9538
 
9149
9539
  // packages/server/src/commands/file.ts
9150
- import { z as z8 } from "zod";
9540
+ import { z as z9 } from "zod";
9151
9541
  var init_file = __esm({
9152
9542
  "packages/server/src/commands/file.ts"() {
9153
9543
  "use strict";
@@ -9156,9 +9546,9 @@ var init_file = __esm({
9156
9546
  init_dispatch();
9157
9547
  registerCommand(
9158
9548
  "file.readTree",
9159
- z8.object({
9160
- workspaceId: z8.string(),
9161
- subPath: z8.string().optional()
9549
+ z9.object({
9550
+ workspaceId: z9.string(),
9551
+ subPath: z9.string().optional()
9162
9552
  }),
9163
9553
  async (args, ctx) => {
9164
9554
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
@@ -9170,10 +9560,10 @@ var init_file = __esm({
9170
9560
  );
9171
9561
  registerCommand(
9172
9562
  "file.search",
9173
- z8.object({
9174
- workspaceId: z8.string(),
9175
- query: z8.string(),
9176
- limit: z8.number().int().positive().max(50).optional()
9563
+ z9.object({
9564
+ workspaceId: z9.string(),
9565
+ query: z9.string(),
9566
+ limit: z9.number().int().positive().max(50).optional()
9177
9567
  }),
9178
9568
  async (args, ctx) => {
9179
9569
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
@@ -9185,9 +9575,9 @@ var init_file = __esm({
9185
9575
  );
9186
9576
  registerCommand(
9187
9577
  "file.read",
9188
- z8.object({
9189
- workspaceId: z8.string(),
9190
- path: z8.string()
9578
+ z9.object({
9579
+ workspaceId: z9.string(),
9580
+ path: z9.string()
9191
9581
  }),
9192
9582
  async (args, ctx) => {
9193
9583
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
@@ -9199,9 +9589,9 @@ var init_file = __esm({
9199
9589
  );
9200
9590
  registerCommand(
9201
9591
  "file.create",
9202
- z8.object({
9203
- workspaceId: z8.string(),
9204
- path: z8.string()
9592
+ z9.object({
9593
+ workspaceId: z9.string(),
9594
+ path: z9.string()
9205
9595
  }),
9206
9596
  async (args, ctx) => {
9207
9597
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
@@ -9219,9 +9609,9 @@ var init_file = __esm({
9219
9609
  );
9220
9610
  registerCommand(
9221
9611
  "file.mkdir",
9222
- z8.object({
9223
- workspaceId: z8.string(),
9224
- path: z8.string()
9612
+ z9.object({
9613
+ workspaceId: z9.string(),
9614
+ path: z9.string()
9225
9615
  }),
9226
9616
  async (args, ctx) => {
9227
9617
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
@@ -9239,9 +9629,9 @@ var init_file = __esm({
9239
9629
  );
9240
9630
  registerCommand(
9241
9631
  "file.delete",
9242
- z8.object({
9243
- workspaceId: z8.string(),
9244
- path: z8.string()
9632
+ z9.object({
9633
+ workspaceId: z9.string(),
9634
+ path: z9.string()
9245
9635
  }),
9246
9636
  async (args, ctx) => {
9247
9637
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
@@ -9259,11 +9649,11 @@ var init_file = __esm({
9259
9649
  );
9260
9650
  registerCommand(
9261
9651
  "file.write",
9262
- z8.object({
9263
- workspaceId: z8.string(),
9264
- path: z8.string(),
9265
- content: z8.string(),
9266
- baseHash: z8.string().optional()
9652
+ z9.object({
9653
+ workspaceId: z9.string(),
9654
+ path: z9.string(),
9655
+ content: z9.string(),
9656
+ baseHash: z9.string().optional()
9267
9657
  // For conflict detection
9268
9658
  }),
9269
9659
  async (args, ctx) => {
@@ -9322,11 +9712,11 @@ async function getUntrackedFileDiff(cwd, filePath) {
9322
9712
  await rm5(tempDir, { recursive: true, force: true });
9323
9713
  }
9324
9714
  }
9325
- async function getFileDiff(cwd, path9, staged = false) {
9326
- if (!staged && !await isTrackedPath(cwd, path9)) {
9327
- return getUntrackedFileDiff(cwd, path9);
9715
+ async function getFileDiff(cwd, path10, staged = false) {
9716
+ if (!staged && !await isTrackedPath(cwd, path10)) {
9717
+ return getUntrackedFileDiff(cwd, path10);
9328
9718
  }
9329
- const args = staged ? ["diff", "--staged", "--", path9] : ["diff", "--", path9];
9719
+ const args = staged ? ["diff", "--staged", "--", path10] : ["diff", "--", path10];
9330
9720
  const result = await runGit(cwd, args);
9331
9721
  return result.stdout;
9332
9722
  }
@@ -9337,8 +9727,7 @@ var init_diff = __esm({
9337
9727
  }
9338
9728
  });
9339
9729
 
9340
- // packages/server/src/commands/git.ts
9341
- import { z as z9 } from "zod";
9730
+ // packages/server/src/commands/git-events.ts
9342
9731
  function emitGitStateChanged(ctx, workspaceId, options) {
9343
9732
  ctx.eventBus.emit({
9344
9733
  type: "git.state.changed",
@@ -9348,21 +9737,38 @@ function emitGitStateChanged(ctx, workspaceId, options) {
9348
9737
  worktreeChanged: options?.worktreeChanged
9349
9738
  });
9350
9739
  }
9351
- var gitHttpAuthSchema;
9740
+ var init_git_events = __esm({
9741
+ "packages/server/src/commands/git-events.ts"() {
9742
+ "use strict";
9743
+ }
9744
+ });
9745
+
9746
+ // packages/server/src/commands/git.ts
9747
+ import { z as z10 } from "zod";
9748
+ async function runGitNetworkOperation(ctx, workspaceId, op) {
9749
+ if (!ctx.autoFetch?.runExclusive) {
9750
+ return op();
9751
+ }
9752
+ return ctx.autoFetch.runExclusive(workspaceId, op);
9753
+ }
9754
+ var gitHttpAuthSchema, gitCommitRevisionSchema, GIT_BACKGROUND_FETCH_TIMEOUT_MS;
9352
9755
  var init_git2 = __esm({
9353
9756
  "packages/server/src/commands/git.ts"() {
9354
9757
  "use strict";
9355
9758
  init_cli();
9356
9759
  init_diff();
9357
9760
  init_dispatch();
9358
- gitHttpAuthSchema = z9.object({
9359
- username: z9.string(),
9360
- password: z9.string()
9761
+ init_git_events();
9762
+ gitHttpAuthSchema = z10.object({
9763
+ username: z10.string(),
9764
+ password: z10.string()
9361
9765
  });
9766
+ gitCommitRevisionSchema = z10.string().regex(/^[0-9a-fA-F]{7,64}$/, "Invalid git commit revision");
9767
+ GIT_BACKGROUND_FETCH_TIMEOUT_MS = 30 * 1e3;
9362
9768
  registerCommand(
9363
9769
  "git.status",
9364
- z9.object({
9365
- workspaceId: z9.string()
9770
+ z10.object({
9771
+ workspaceId: z10.string()
9366
9772
  }),
9367
9773
  async (args, ctx) => {
9368
9774
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
@@ -9374,9 +9780,9 @@ var init_git2 = __esm({
9374
9780
  );
9375
9781
  registerCommand(
9376
9782
  "git.stage",
9377
- z9.object({
9378
- workspaceId: z9.string(),
9379
- paths: z9.array(z9.string())
9783
+ z10.object({
9784
+ workspaceId: z10.string(),
9785
+ paths: z10.array(z10.string())
9380
9786
  }),
9381
9787
  async (args, ctx) => {
9382
9788
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
@@ -9390,10 +9796,10 @@ var init_git2 = __esm({
9390
9796
  );
9391
9797
  registerCommand(
9392
9798
  "git.diff",
9393
- z9.object({
9394
- workspaceId: z9.string(),
9395
- path: z9.string(),
9396
- staged: z9.boolean().optional()
9799
+ z10.object({
9800
+ workspaceId: z10.string(),
9801
+ path: z10.string(),
9802
+ staged: z10.boolean().optional()
9397
9803
  }),
9398
9804
  async (args, ctx) => {
9399
9805
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
@@ -9405,11 +9811,43 @@ var init_git2 = __esm({
9405
9811
  };
9406
9812
  }
9407
9813
  );
9814
+ registerCommand(
9815
+ "git.log",
9816
+ z10.object({
9817
+ workspaceId: z10.string(),
9818
+ limit: z10.number().int().min(1).max(50).optional()
9819
+ }),
9820
+ async (args, ctx) => {
9821
+ const workspace = ctx.workspaceMgr.get(args.workspaceId);
9822
+ if (!workspace) {
9823
+ throw { code: "workspace_not_found", message: `Workspace not found: ${args.workspaceId}` };
9824
+ }
9825
+ return {
9826
+ entries: await getGitHistory(workspace.path, args.limit ?? 5)
9827
+ };
9828
+ }
9829
+ );
9830
+ registerCommand(
9831
+ "git.show",
9832
+ z10.object({
9833
+ workspaceId: z10.string(),
9834
+ sha: gitCommitRevisionSchema
9835
+ }),
9836
+ async (args, ctx) => {
9837
+ const workspace = ctx.workspaceMgr.get(args.workspaceId);
9838
+ if (!workspace) {
9839
+ throw { code: "workspace_not_found", message: `Workspace not found: ${args.workspaceId}` };
9840
+ }
9841
+ return {
9842
+ diff: await getGitCommitDiff(workspace.path, args.sha)
9843
+ };
9844
+ }
9845
+ );
9408
9846
  registerCommand(
9409
9847
  "git.unstage",
9410
- z9.object({
9411
- workspaceId: z9.string(),
9412
- paths: z9.array(z9.string())
9848
+ z10.object({
9849
+ workspaceId: z10.string(),
9850
+ paths: z10.array(z10.string())
9413
9851
  }),
9414
9852
  async (args, ctx) => {
9415
9853
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
@@ -9423,9 +9861,9 @@ var init_git2 = __esm({
9423
9861
  );
9424
9862
  registerCommand(
9425
9863
  "git.discard",
9426
- z9.object({
9427
- workspaceId: z9.string(),
9428
- paths: z9.array(z9.string())
9864
+ z10.object({
9865
+ workspaceId: z10.string(),
9866
+ paths: z10.array(z10.string())
9429
9867
  }),
9430
9868
  async (args, ctx) => {
9431
9869
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
@@ -9441,9 +9879,9 @@ var init_git2 = __esm({
9441
9879
  );
9442
9880
  registerCommand(
9443
9881
  "git.commit",
9444
- z9.object({
9445
- workspaceId: z9.string(),
9446
- message: z9.string()
9882
+ z10.object({
9883
+ workspaceId: z10.string(),
9884
+ message: z10.string()
9447
9885
  }),
9448
9886
  async (args, ctx) => {
9449
9887
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
@@ -9460,11 +9898,11 @@ var init_git2 = __esm({
9460
9898
  );
9461
9899
  registerCommand(
9462
9900
  "git.push",
9463
- z9.object({
9464
- workspaceId: z9.string(),
9465
- remote: z9.string().optional(),
9466
- branch: z9.string().optional(),
9467
- force: z9.boolean().optional(),
9901
+ z10.object({
9902
+ workspaceId: z10.string(),
9903
+ remote: z10.string().optional(),
9904
+ branch: z10.string().optional(),
9905
+ force: z10.boolean().optional(),
9468
9906
  auth: gitHttpAuthSchema.optional()
9469
9907
  }),
9470
9908
  async (args, ctx) => {
@@ -9472,12 +9910,16 @@ var init_git2 = __esm({
9472
9910
  if (!workspace) {
9473
9911
  throw { code: "workspace_not_found", message: `Workspace not found: ${args.workspaceId}` };
9474
9912
  }
9475
- const result = await runGitPush(workspace.path, {
9476
- remote: args.remote,
9477
- branch: args.branch,
9478
- force: args.force,
9479
- auth: args.auth
9480
- });
9913
+ const result = await runGitNetworkOperation(
9914
+ ctx,
9915
+ args.workspaceId,
9916
+ () => runGitPush(workspace.path, {
9917
+ remote: args.remote,
9918
+ branch: args.branch,
9919
+ force: args.force,
9920
+ auth: args.auth
9921
+ })
9922
+ );
9481
9923
  emitGitStateChanged(ctx, args.workspaceId, {
9482
9924
  branchChanged: true,
9483
9925
  worktreeChanged: true
@@ -9487,10 +9929,10 @@ var init_git2 = __esm({
9487
9929
  );
9488
9930
  registerCommand(
9489
9931
  "git.pull",
9490
- z9.object({
9491
- workspaceId: z9.string(),
9492
- remote: z9.string().optional(),
9493
- branch: z9.string().optional(),
9932
+ z10.object({
9933
+ workspaceId: z10.string(),
9934
+ remote: z10.string().optional(),
9935
+ branch: z10.string().optional(),
9494
9936
  auth: gitHttpAuthSchema.optional()
9495
9937
  }),
9496
9938
  async (args, ctx) => {
@@ -9498,11 +9940,16 @@ var init_git2 = __esm({
9498
9940
  if (!workspace) {
9499
9941
  throw { code: "workspace_not_found", message: `Workspace not found: ${args.workspaceId}` };
9500
9942
  }
9501
- const result = await runGitPull(workspace.path, {
9502
- remote: args.remote,
9503
- branch: args.branch,
9504
- auth: args.auth
9505
- });
9943
+ const result = await runGitNetworkOperation(
9944
+ ctx,
9945
+ args.workspaceId,
9946
+ () => runGitPull(workspace.path, {
9947
+ remote: args.remote,
9948
+ branch: args.branch,
9949
+ auth: args.auth
9950
+ })
9951
+ );
9952
+ ctx.workspaceMgr.recordFetch(args.workspaceId);
9506
9953
  emitGitStateChanged(ctx, args.workspaceId, {
9507
9954
  treeChanged: true,
9508
9955
  branchChanged: true,
@@ -9511,12 +9958,46 @@ var init_git2 = __esm({
9511
9958
  return result;
9512
9959
  }
9513
9960
  );
9961
+ registerCommand(
9962
+ "git.fetch",
9963
+ z10.object({
9964
+ workspaceId: z10.string(),
9965
+ remote: z10.string().optional(),
9966
+ prune: z10.boolean().optional(),
9967
+ auth: gitHttpAuthSchema.optional(),
9968
+ background: z10.boolean().optional()
9969
+ }),
9970
+ async (args, ctx, clientId) => {
9971
+ const workspace = ctx.workspaceMgr.get(args.workspaceId);
9972
+ if (!workspace) {
9973
+ throw { code: "workspace_not_found", message: `Workspace not found: ${args.workspaceId}` };
9974
+ }
9975
+ try {
9976
+ const isInternalBackgroundFetch = args.background === true && !clientId;
9977
+ const runFetch = () => runGitFetch(workspace.path, {
9978
+ remote: args.remote,
9979
+ prune: args.prune,
9980
+ auth: args.auth,
9981
+ timeoutMs: args.background ? GIT_BACKGROUND_FETCH_TIMEOUT_MS : void 0
9982
+ });
9983
+ const result = isInternalBackgroundFetch ? await runFetch() : await runGitNetworkOperation(ctx, args.workspaceId, runFetch);
9984
+ ctx.workspaceMgr.recordFetch(args.workspaceId);
9985
+ emitGitStateChanged(ctx, args.workspaceId, { branchChanged: true });
9986
+ return result;
9987
+ } catch (err) {
9988
+ if (args.background && err instanceof GitAuthError) {
9989
+ return { success: false, message: err.message, updatedRefs: [] };
9990
+ }
9991
+ throw err;
9992
+ }
9993
+ }
9994
+ );
9514
9995
  registerCommand(
9515
9996
  "git.checkout",
9516
- z9.object({
9517
- workspaceId: z9.string(),
9518
- ref: z9.string(),
9519
- createBranch: z9.boolean().optional()
9997
+ z10.object({
9998
+ workspaceId: z10.string(),
9999
+ ref: z10.string(),
10000
+ createBranch: z10.boolean().optional()
9520
10001
  }),
9521
10002
  async (args, ctx) => {
9522
10003
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
@@ -9538,10 +10019,10 @@ var init_git2 = __esm({
9538
10019
  );
9539
10020
  registerCommand(
9540
10021
  "git.branch",
9541
- z9.object({
9542
- workspaceId: z9.string(),
9543
- name: z9.string(),
9544
- startPoint: z9.string().optional()
10022
+ z10.object({
10023
+ workspaceId: z10.string(),
10024
+ name: z10.string(),
10025
+ startPoint: z10.string().optional()
9545
10026
  }),
9546
10027
  async (args, ctx) => {
9547
10028
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
@@ -9560,8 +10041,8 @@ var init_git2 = __esm({
9560
10041
  );
9561
10042
  registerCommand(
9562
10043
  "git.branches",
9563
- z9.object({
9564
- workspaceId: z9.string()
10044
+ z10.object({
10045
+ workspaceId: z10.string()
9565
10046
  }),
9566
10047
  async (args, ctx) => {
9567
10048
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
@@ -9575,8 +10056,8 @@ var init_git2 = __esm({
9575
10056
  });
9576
10057
 
9577
10058
  // packages/server/src/config/config-io.ts
9578
- import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync8, renameSync as renameSync2, writeFileSync as writeFileSync4 } from "node:fs";
9579
- import { homedir as homedir5 } from "node:os";
10059
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync8, renameSync, writeFileSync as writeFileSync4 } from "node:fs";
10060
+ import { homedir as homedir4 } from "node:os";
9580
10061
  import { basename as basename3, dirname as dirname5, join as join8 } from "node:path";
9581
10062
  function resolveConfigPath(configType) {
9582
10063
  if (configType === "codex") {
@@ -9584,14 +10065,18 @@ function resolveConfigPath(configType) {
9584
10065
  if (testHome && testHome.trim()) {
9585
10066
  return join8(testHome, "config.toml");
9586
10067
  }
9587
- return resolveCodexConfigPath();
10068
+ const codexHome = process.env.CODEX_HOME;
10069
+ if (codexHome && codexHome.trim()) {
10070
+ return join8(codexHome, "config.toml");
10071
+ }
10072
+ return join8(homedir4(), ".codex", "config.toml");
9588
10073
  }
9589
10074
  if (configType === "claude") {
9590
10075
  const testHome = process.env.CODER_STUDIO_CLAUDE_HOME;
9591
10076
  if (testHome && testHome.trim()) {
9592
10077
  return join8(testHome, "settings.json");
9593
10078
  }
9594
- return join8(homedir5(), ".claude", "settings.json");
10079
+ return join8(homedir4(), ".claude", "settings.json");
9595
10080
  }
9596
10081
  throw new Error(`Unknown config type: ${configType}`);
9597
10082
  }
@@ -9620,7 +10105,7 @@ function writeConfigFile(configType, content) {
9620
10105
  }
9621
10106
  const tempPath = `${configPath}.tmp`;
9622
10107
  writeFileSync4(tempPath, content, "utf-8");
9623
- renameSync2(tempPath, configPath);
10108
+ renameSync(tempPath, configPath);
9624
10109
  return { success: true, backupPath };
9625
10110
  } catch (error) {
9626
10111
  return {
@@ -9635,24 +10120,23 @@ function createBackup(filePath) {
9635
10120
  const ext = filePath.split(".").pop() ?? "";
9636
10121
  const base = basename3(filePath, `.${ext}`);
9637
10122
  const dir = dirname5(filePath);
9638
- const ts = formatTimestamp2(/* @__PURE__ */ new Date());
10123
+ const ts = formatTimestamp(/* @__PURE__ */ new Date());
9639
10124
  const backupPath = join8(dir, `${base}.bak.${ts}.${ext}`);
9640
10125
  writeFileSync4(backupPath, original, "utf-8");
9641
10126
  return backupPath;
9642
10127
  }
9643
- function formatTimestamp2(d) {
10128
+ function formatTimestamp(d) {
9644
10129
  const pad = (n) => String(n).padStart(2, "0");
9645
10130
  return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
9646
10131
  }
9647
10132
  var init_config_io = __esm({
9648
10133
  "packages/server/src/config/config-io.ts"() {
9649
10134
  "use strict";
9650
- init_codex_config_audit();
9651
10135
  }
9652
10136
  });
9653
10137
 
9654
10138
  // packages/server/src/commands/settings.ts
9655
- import { z as z10 } from "zod";
10139
+ import { z as z11 } from "zod";
9656
10140
  function flattenSettings(obj, prefix = "") {
9657
10141
  const result = {};
9658
10142
  for (const [key, value] of Object.entries(obj)) {
@@ -9665,7 +10149,7 @@ function flattenSettings(obj, prefix = "") {
9665
10149
  }
9666
10150
  return result;
9667
10151
  }
9668
- var EMPTY_CODEX_AUDIT, SettingsSchema;
10152
+ var SettingsSchema;
9669
10153
  var init_settings2 = __esm({
9670
10154
  "packages/server/src/commands/settings.ts"() {
9671
10155
  "use strict";
@@ -9675,34 +10159,27 @@ var init_settings2 = __esm({
9675
10159
  init_provider_config_repo();
9676
10160
  init_settings();
9677
10161
  init_dispatch();
9678
- EMPTY_CODEX_AUDIT = {
9679
- codex: {
9680
- configPath: "",
9681
- exists: false,
9682
- findings: []
9683
- }
9684
- };
9685
- SettingsSchema = z10.object({
9686
- defaultProviderId: z10.string().optional(),
9687
- notifications: z10.object({
9688
- enabled: z10.boolean().optional(),
9689
- soundEnabled: z10.boolean().optional(),
10162
+ SettingsSchema = z11.object({
10163
+ defaultProviderId: z11.string().optional(),
10164
+ notifications: z11.object({
10165
+ enabled: z11.boolean().optional(),
10166
+ soundEnabled: z11.boolean().optional(),
9690
10167
  // Legacy field — accepted for backward compat with older clients but
9691
10168
  // no longer surfaced in the UI. The web client now picks the channel
9692
10169
  // automatically based on workspace focus + page visibility.
9693
- onlyWhenBackgrounded: z10.boolean().optional()
10170
+ onlyWhenBackgrounded: z11.boolean().optional()
9694
10171
  }).optional(),
9695
- supervisor: z10.object({
9696
- evaluationTimeoutSec: z10.number().int().min(1).max(MAX_SUPERVISOR_EVALUATION_TIMEOUT_SEC).default(DEFAULT_SUPERVISOR_EVALUATION_TIMEOUT_SEC).optional()
10172
+ supervisor: z11.object({
10173
+ evaluationTimeoutSec: z11.number().int().min(1).max(MAX_SUPERVISOR_EVALUATION_TIMEOUT_SEC).default(DEFAULT_SUPERVISOR_EVALUATION_TIMEOUT_SEC).optional()
9697
10174
  }).optional(),
9698
- appearance: z10.object({
9699
- theme: z10.enum(["dark"]).optional(),
9700
- terminalRenderer: z10.enum(["standard", "compatibility"]).optional(),
9701
- locale: z10.enum(["zh", "en"]).optional()
10175
+ appearance: z11.object({
10176
+ theme: z11.enum(["dark"]).optional(),
10177
+ terminalRenderer: z11.enum(["standard", "compatibility"]).optional(),
10178
+ locale: z11.enum(["zh", "en"]).optional()
9702
10179
  }).optional(),
9703
10180
  providers: ProviderSettingsSchema.optional()
9704
10181
  });
9705
- registerCommand("settings.get", z10.object({}), async (_args, ctx) => {
10182
+ registerCommand("settings.get", z11.object({}), async (_args, ctx) => {
9706
10183
  const row = ctx.db.prepare("SELECT key, value FROM user_settings").all();
9707
10184
  const settings = {};
9708
10185
  for (const { key, value } of row) {
@@ -9726,11 +10203,6 @@ var init_settings2 = __esm({
9726
10203
  flattenSettings(sanitizeProviderLaunchConfig(config), `providers.${providerId}`)
9727
10204
  );
9728
10205
  }
9729
- try {
9730
- settings.externalConfigAudit = ctx.codexConfigAudit?.audit() ?? EMPTY_CODEX_AUDIT;
9731
- } catch {
9732
- settings.externalConfigAudit = null;
9733
- }
9734
10206
  if (Object.prototype.hasOwnProperty.call(settings, SUPERVISOR_EVALUATION_TIMEOUT_SETTING_KEY)) {
9735
10207
  settings[SUPERVISOR_EVALUATION_TIMEOUT_SETTING_KEY] = resolveSupervisorEvaluationTimeoutSec(
9736
10208
  settings[SUPERVISOR_EVALUATION_TIMEOUT_SETTING_KEY]
@@ -9740,7 +10212,7 @@ var init_settings2 = __esm({
9740
10212
  });
9741
10213
  registerCommand(
9742
10214
  "settings.update",
9743
- z10.object({
10215
+ z11.object({
9744
10216
  settings: SettingsSchema
9745
10217
  }),
9746
10218
  async (args, ctx) => {
@@ -9770,31 +10242,12 @@ var init_settings2 = __esm({
9770
10242
  };
9771
10243
  }
9772
10244
  );
9773
- registerCommand(
9774
- "settings.cleanupCodexConfig",
9775
- z10.object({
9776
- removeIds: z10.array(z10.enum(["toml_notify", "toml_codex_hooks"])).min(1)
9777
- }),
9778
- async (args, ctx) => {
9779
- const result = ctx.codexConfigAudit?.cleanup(args.removeIds) ?? {
9780
- removed: [],
9781
- backupPath: null,
9782
- noop: true
9783
- };
9784
- return {
9785
- removed: result.removed,
9786
- backupPath: result.backupPath,
9787
- noop: result.noop,
9788
- audit: ctx.codexConfigAudit?.audit() ?? EMPTY_CODEX_AUDIT
9789
- };
9790
- }
9791
- );
9792
10245
  registerCommand(
9793
10246
  "settings.previewCommand",
9794
- z10.object({
9795
- providerId: z10.string(),
10247
+ z11.object({
10248
+ providerId: z11.string(),
9796
10249
  config: ProviderLaunchConfigInputSchema,
9797
- workspacePath: z10.string().optional()
10250
+ workspacePath: z11.string().optional()
9798
10251
  }),
9799
10252
  async (args, ctx) => {
9800
10253
  const provider = ctx.providerRegistry.find((item) => item.id === args.providerId);
@@ -9815,8 +10268,8 @@ var init_settings2 = __esm({
9815
10268
  );
9816
10269
  registerCommand(
9817
10270
  "settings.readConfigFile",
9818
- z10.object({
9819
- configType: z10.enum(["codex", "claude"])
10271
+ z11.object({
10272
+ configType: z11.enum(["codex", "claude"])
9820
10273
  }),
9821
10274
  async (args) => {
9822
10275
  const result = readConfigFile(args.configType);
@@ -9825,9 +10278,9 @@ var init_settings2 = __esm({
9825
10278
  );
9826
10279
  registerCommand(
9827
10280
  "settings.writeConfigFile",
9828
- z10.object({
9829
- configType: z10.enum(["codex", "claude"]),
9830
- content: z10.string()
10281
+ z11.object({
10282
+ configType: z11.enum(["codex", "claude"]),
10283
+ content: z11.string()
9831
10284
  }),
9832
10285
  async (args) => {
9833
10286
  const result = writeConfigFile(args.configType, args.content);
@@ -9838,19 +10291,19 @@ var init_settings2 = __esm({
9838
10291
  });
9839
10292
 
9840
10293
  // packages/server/src/commands/provider.ts
9841
- import { z as z11 } from "zod";
10294
+ import { z as z12 } from "zod";
9842
10295
  var init_provider = __esm({
9843
10296
  "packages/server/src/commands/provider.ts"() {
9844
10297
  "use strict";
9845
10298
  init_runtime_status();
9846
10299
  init_dispatch();
9847
- registerCommand("provider.runtimeStatus", z11.object({}), async (_args, ctx) => {
10300
+ registerCommand("provider.runtimeStatus", z12.object({}), async (_args, ctx) => {
9848
10301
  return buildProviderRuntimeStatus(ctx.providerRegistry, ctx.providerRuntimeDeps);
9849
10302
  });
9850
10303
  registerCommand(
9851
10304
  "provider.install.start",
9852
- z11.object({
9853
- providerId: z11.string()
10305
+ z12.object({
10306
+ providerId: z12.string()
9854
10307
  }),
9855
10308
  async (args, ctx) => {
9856
10309
  if (!ctx.providerInstallMgr) {
@@ -9864,8 +10317,8 @@ var init_provider = __esm({
9864
10317
  );
9865
10318
  registerCommand(
9866
10319
  "provider.install.get",
9867
- z11.object({
9868
- jobId: z11.string()
10320
+ z12.object({
10321
+ jobId: z12.string()
9869
10322
  }),
9870
10323
  async (args, ctx) => {
9871
10324
  if (!ctx.providerInstallMgr) {
@@ -9888,29 +10341,29 @@ var init_provider = __esm({
9888
10341
  });
9889
10342
 
9890
10343
  // packages/server/src/commands/supervisor.ts
9891
- import { z as z12 } from "zod";
10344
+ import { z as z13 } from "zod";
9892
10345
  var supervisorObjectiveSchema, createSupervisorSchema, updateSupervisorSchema, sessionIdSchema, supervisorIdSchema;
9893
10346
  var init_supervisor2 = __esm({
9894
10347
  "packages/server/src/commands/supervisor.ts"() {
9895
10348
  "use strict";
9896
10349
  init_dispatch();
9897
- supervisorObjectiveSchema = z12.string().trim().min(1).max(4e3);
9898
- createSupervisorSchema = z12.object({
9899
- sessionId: z12.string(),
9900
- workspaceId: z12.string(),
10350
+ supervisorObjectiveSchema = z13.string().trim().min(1).max(4e3);
10351
+ createSupervisorSchema = z13.object({
10352
+ sessionId: z13.string(),
10353
+ workspaceId: z13.string(),
9901
10354
  objective: supervisorObjectiveSchema,
9902
- evaluatorProviderId: z12.string()
10355
+ evaluatorProviderId: z13.string()
9903
10356
  }).strict();
9904
- updateSupervisorSchema = z12.object({
9905
- id: z12.string(),
10357
+ updateSupervisorSchema = z13.object({
10358
+ id: z13.string(),
9906
10359
  objective: supervisorObjectiveSchema.optional(),
9907
- evaluatorProviderId: z12.string().optional()
10360
+ evaluatorProviderId: z13.string().optional()
9908
10361
  }).strict().refine(
9909
10362
  (input) => input.objective !== void 0 || input.evaluatorProviderId !== void 0,
9910
10363
  "objective or evaluatorProviderId is required"
9911
10364
  );
9912
- sessionIdSchema = z12.object({ sessionId: z12.string() });
9913
- supervisorIdSchema = z12.object({ id: z12.string() });
10365
+ sessionIdSchema = z13.object({ sessionId: z13.string() });
10366
+ supervisorIdSchema = z13.object({ id: z13.string() });
9914
10367
  registerCommand("supervisor.create", createSupervisorSchema, async (args, ctx) => {
9915
10368
  return {
9916
10369
  supervisor: await ctx.supervisorMgr.create({
@@ -9953,6 +10406,10 @@ import path8 from "node:path";
9953
10406
  function normalizeWorktreePath(worktreePath) {
9954
10407
  return path8.resolve(worktreePath);
9955
10408
  }
10409
+ async function getGitCommonDirPath(repoPath) {
10410
+ const { stdout } = await runGit(repoPath, ["rev-parse", "--git-common-dir"]);
10411
+ return normalizeWorktreePath(path8.resolve(repoPath, stdout.trim()));
10412
+ }
9956
10413
  async function resolveWorktreePath(repoPath, worktreePath) {
9957
10414
  const normalizedRequested = normalizeWorktreePath(worktreePath);
9958
10415
  const worktrees = await listWorktrees(repoPath);
@@ -10038,19 +10495,30 @@ async function getWorktreeTree(worktreePath) {
10038
10495
  for (const line of lines) {
10039
10496
  const isDir = line.endsWith("/");
10040
10497
  const name = isDir ? line.slice(0, -1) : line;
10041
- const path9 = `${worktreePath}/${name}`;
10498
+ const path10 = `${worktreePath}/${name}`;
10042
10499
  nodes.push({
10043
10500
  name,
10044
- path: path9,
10501
+ path: path10,
10045
10502
  kind: isDir ? "dir" : "file"
10046
10503
  });
10047
10504
  }
10048
10505
  return nodes;
10049
10506
  }
10050
- async function createWorktree(repoPath, branch, path9) {
10051
- await runGit(repoPath, ["worktree", "add", path9, branch]);
10507
+ async function createWorktree(repoPath, branch, worktreePath) {
10508
+ let createArgs = ["worktree", "add", worktreePath, branch];
10509
+ try {
10510
+ await runGit(repoPath, ["rev-parse", "--verify", "--quiet", `${branch}^{commit}`]);
10511
+ } catch (error) {
10512
+ if (error instanceof GitError) {
10513
+ createArgs = ["worktree", "add", "-b", branch, worktreePath];
10514
+ } else {
10515
+ throw error;
10516
+ }
10517
+ }
10518
+ await runGit(repoPath, createArgs);
10052
10519
  const worktrees = await listWorktrees(repoPath);
10053
- const created = worktrees.find((wt) => wt.path === path9);
10520
+ const normalizedRequested = normalizeWorktreePath(worktreePath);
10521
+ const created = worktrees.find((wt) => normalizeWorktreePath(wt.path) === normalizedRequested);
10054
10522
  if (!created) {
10055
10523
  throw new Error("Failed to find created worktree");
10056
10524
  }
@@ -10072,13 +10540,41 @@ var init_worktree = __esm({
10072
10540
  });
10073
10541
 
10074
10542
  // packages/server/src/commands/worktree.ts
10075
- import { z as z13 } from "zod";
10543
+ import path9 from "node:path";
10544
+ import { z as z14 } from "zod";
10545
+ async function findRelatedWorkspaceIds(ctx, workspacePath) {
10546
+ const targetCommonDir = await getGitCommonDirPath(workspacePath);
10547
+ const relatedWorkspaceIds = await Promise.all(
10548
+ ctx.workspaceMgr.list().map(async (workspace) => {
10549
+ try {
10550
+ const commonDir = await getGitCommonDirPath(workspace.path);
10551
+ return commonDir === targetCommonDir ? workspace.id : null;
10552
+ } catch {
10553
+ return null;
10554
+ }
10555
+ })
10556
+ );
10557
+ return relatedWorkspaceIds.filter((workspaceId) => Boolean(workspaceId));
10558
+ }
10559
+ function emitWorktreeChangedForWorkspaceIds(ctx, workspaceIds) {
10560
+ for (const workspaceId of workspaceIds) {
10561
+ if (!ctx.workspaceMgr.get(workspaceId)) {
10562
+ continue;
10563
+ }
10564
+ emitGitStateChanged(ctx, workspaceId, { worktreeChanged: true });
10565
+ }
10566
+ }
10567
+ function isWorkspaceOpenForPath(ctx, workspacePath) {
10568
+ const targetPath = path9.resolve(workspacePath);
10569
+ return ctx.workspaceMgr.list().some((openWorkspace) => path9.resolve(openWorkspace.path) === targetPath);
10570
+ }
10076
10571
  var init_worktree2 = __esm({
10077
10572
  "packages/server/src/commands/worktree.ts"() {
10078
10573
  "use strict";
10079
10574
  init_worktree();
10080
10575
  init_dispatch();
10081
- registerCommand("worktree.list", z13.object({ workspaceId: z13.string() }), async (args, ctx) => {
10576
+ init_git_events();
10577
+ registerCommand("worktree.list", z14.object({ workspaceId: z14.string() }), async (args, ctx) => {
10082
10578
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
10083
10579
  if (!workspace) {
10084
10580
  throw { code: "workspace_not_found", message: `Workspace not found: ${args.workspaceId}` };
@@ -10087,7 +10583,7 @@ var init_worktree2 = __esm({
10087
10583
  });
10088
10584
  registerCommand(
10089
10585
  "worktree.status",
10090
- z13.object({ workspaceId: z13.string(), worktreePath: z13.string() }),
10586
+ z14.object({ workspaceId: z14.string(), worktreePath: z14.string() }),
10091
10587
  async (args, ctx) => {
10092
10588
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
10093
10589
  if (!workspace) {
@@ -10099,10 +10595,10 @@ var init_worktree2 = __esm({
10099
10595
  );
10100
10596
  registerCommand(
10101
10597
  "worktree.diff",
10102
- z13.object({
10103
- workspaceId: z13.string(),
10104
- worktreePath: z13.string(),
10105
- staged: z13.boolean().optional().default(false)
10598
+ z14.object({
10599
+ workspaceId: z14.string(),
10600
+ worktreePath: z14.string(),
10601
+ staged: z14.boolean().optional().default(false)
10106
10602
  }),
10107
10603
  async (args, ctx) => {
10108
10604
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
@@ -10115,7 +10611,7 @@ var init_worktree2 = __esm({
10115
10611
  );
10116
10612
  registerCommand(
10117
10613
  "worktree.tree",
10118
- z13.object({ workspaceId: z13.string(), worktreePath: z13.string() }),
10614
+ z14.object({ workspaceId: z14.string(), worktreePath: z14.string() }),
10119
10615
  async (args, ctx) => {
10120
10616
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
10121
10617
  if (!workspace) {
@@ -10127,33 +10623,44 @@ var init_worktree2 = __esm({
10127
10623
  );
10128
10624
  registerCommand(
10129
10625
  "worktree.create",
10130
- z13.object({
10131
- workspaceId: z13.string(),
10132
- branch: z13.string(),
10133
- path: z13.string()
10626
+ z14.object({
10627
+ workspaceId: z14.string(),
10628
+ branch: z14.string(),
10629
+ path: z14.string()
10134
10630
  }),
10135
10631
  async (args, ctx) => {
10136
10632
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
10137
10633
  if (!workspace) {
10138
10634
  throw { code: "workspace_not_found", message: `Workspace not found: ${args.workspaceId}` };
10139
10635
  }
10140
- return { worktree: await createWorktree(workspace.path, args.branch, args.path) };
10636
+ const relatedWorkspaceIds = await findRelatedWorkspaceIds(ctx, workspace.path);
10637
+ const worktree = await createWorktree(workspace.path, args.branch, args.path);
10638
+ emitWorktreeChangedForWorkspaceIds(ctx, relatedWorkspaceIds);
10639
+ return { worktree };
10141
10640
  }
10142
10641
  );
10143
10642
  registerCommand(
10144
10643
  "worktree.remove",
10145
- z13.object({
10146
- workspaceId: z13.string(),
10147
- worktreePath: z13.string(),
10148
- force: z13.boolean().optional().default(false)
10644
+ z14.object({
10645
+ workspaceId: z14.string(),
10646
+ worktreePath: z14.string(),
10647
+ force: z14.boolean().optional().default(false)
10149
10648
  }),
10150
10649
  async (args, ctx) => {
10151
10650
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
10152
10651
  if (!workspace) {
10153
10652
  throw { code: "workspace_not_found", message: `Workspace not found: ${args.workspaceId}` };
10154
10653
  }
10654
+ const relatedWorkspaceIds = await findRelatedWorkspaceIds(ctx, workspace.path);
10155
10655
  const worktreePath = await resolveWorktreePath(workspace.path, args.worktreePath);
10656
+ if (isWorkspaceOpenForPath(ctx, worktreePath)) {
10657
+ throw {
10658
+ code: "worktree_in_use",
10659
+ message: `Cannot remove an open worktree workspace: ${worktreePath}`
10660
+ };
10661
+ }
10156
10662
  await removeWorktree(workspace.path, worktreePath, args.force);
10663
+ emitWorktreeChangedForWorkspaceIds(ctx, relatedWorkspaceIds);
10157
10664
  return {};
10158
10665
  }
10159
10666
  );
@@ -10161,7 +10668,7 @@ var init_worktree2 = __esm({
10161
10668
  });
10162
10669
 
10163
10670
  // packages/server/src/commands/fencing.ts
10164
- import { z as z14 } from "zod";
10671
+ import { z as z15 } from "zod";
10165
10672
  function createMockFencingRequest() {
10166
10673
  return {
10167
10674
  ip: "127.0.0.1",
@@ -10174,9 +10681,9 @@ var init_fencing2 = __esm({
10174
10681
  init_dispatch();
10175
10682
  registerCommand(
10176
10683
  "fencing.request",
10177
- z14.object({
10178
- workspaceId: z14.string(),
10179
- tabId: z14.string()
10684
+ z15.object({
10685
+ workspaceId: z15.string(),
10686
+ tabId: z15.string()
10180
10687
  }),
10181
10688
  async (args, ctx, clientId) => {
10182
10689
  return ctx.fencingMgr.requestControl(
@@ -10189,7 +10696,7 @@ var init_fencing2 = __esm({
10189
10696
  );
10190
10697
  registerCommand(
10191
10698
  "fencing.heartbeat",
10192
- z14.object({ workspaceId: z14.string() }),
10699
+ z15.object({ workspaceId: z15.string() }),
10193
10700
  async (args, ctx, clientId) => {
10194
10701
  const success = ctx.fencingMgr.heartbeat(args.workspaceId, clientId);
10195
10702
  return { success };
@@ -10197,13 +10704,13 @@ var init_fencing2 = __esm({
10197
10704
  );
10198
10705
  registerCommand(
10199
10706
  "fencing.release",
10200
- z14.object({ workspaceId: z14.string() }),
10707
+ z15.object({ workspaceId: z15.string() }),
10201
10708
  async (args, ctx, clientId) => {
10202
10709
  ctx.fencingMgr.release(args.workspaceId, clientId);
10203
10710
  return {};
10204
10711
  }
10205
10712
  );
10206
- registerCommand("fencing.status", z14.object({ workspaceId: z14.string() }), async (args, ctx) => {
10713
+ registerCommand("fencing.status", z15.object({ workspaceId: z15.string() }), async (args, ctx) => {
10207
10714
  const controller = ctx.fencingMgr.getController(args.workspaceId);
10208
10715
  const isUnresponsive = ctx.fencingMgr.isControllerUnresponsive(args.workspaceId);
10209
10716
  return {
@@ -10214,9 +10721,9 @@ var init_fencing2 = __esm({
10214
10721
  });
10215
10722
  registerCommand(
10216
10723
  "fencing.takeover",
10217
- z14.object({
10218
- workspaceId: z14.string(),
10219
- tabId: z14.string()
10724
+ z15.object({
10725
+ workspaceId: z15.string(),
10726
+ tabId: z15.string()
10220
10727
  }),
10221
10728
  async (args, ctx, clientId) => {
10222
10729
  return ctx.fencingMgr.forceTakeover(
@@ -10235,6 +10742,7 @@ var init_commands = __esm({
10235
10742
  "packages/server/src/commands/index.ts"() {
10236
10743
  "use strict";
10237
10744
  init_workspace();
10745
+ init_workspace_activity();
10238
10746
  init_session();
10239
10747
  init_terminal();
10240
10748
  init_file();
@@ -10248,32 +10756,6 @@ var init_commands = __esm({
10248
10756
  });
10249
10757
 
10250
10758
  // packages/server/src/server.ts
10251
- function createCodexConfigAuditApi() {
10252
- return {
10253
- audit: () => ({ codex: auditCodexConfigToml() }),
10254
- cleanup: (removeIds) => {
10255
- const audit = auditCodexConfigToml();
10256
- return cleanupCodexConfigToml(audit.configPath, { removeIds });
10257
- }
10258
- };
10259
- }
10260
- async function logCodexConfigFindings(auditApi, logger) {
10261
- try {
10262
- const audit = auditApi.audit();
10263
- for (const finding of audit.codex.findings) {
10264
- logger.warn(
10265
- {
10266
- configPath: audit.codex.configPath,
10267
- startLine: finding.startLine,
10268
- findingMessage: finding.message
10269
- },
10270
- "Codex config finding"
10271
- );
10272
- }
10273
- } catch (err) {
10274
- logger.warn({ err }, "Codex config audit failed (non-fatal)");
10275
- }
10276
- }
10277
10759
  async function createServer(configOverrides) {
10278
10760
  const config = parseServerConfig(configOverrides);
10279
10761
  ensureDataDir(config);
@@ -10281,14 +10763,45 @@ async function createServer(configOverrides) {
10281
10763
  const eventBus = new EventBus();
10282
10764
  const fencingMgr = new FencingManager();
10283
10765
  const wsHub = new WsHub({ eventBus, commandContext: null, config, fencingMgr });
10766
+ let workspaceMgr;
10767
+ let commandContext;
10284
10768
  const terminalMgr = new TerminalManager({
10285
10769
  ptyHost: createPtyHost(),
10286
10770
  eventBus,
10287
10771
  db: createTerminalDatabase(db)
10288
10772
  });
10773
+ const settingsRepo = new SettingsRepo(db);
10774
+ const autoFetch = new AutoFetchScheduler({
10775
+ workspaceMgr: { get: (workspaceId) => workspaceMgr.get(workspaceId) },
10776
+ eventBus,
10777
+ settingsRepo,
10778
+ runFetch: async (workspaceId) => {
10779
+ if (!workspaceMgr.get(workspaceId)) {
10780
+ return;
10781
+ }
10782
+ const result = await dispatch(
10783
+ {
10784
+ kind: "command",
10785
+ id: `auto-fetch:${workspaceId}:${Date.now()}`,
10786
+ op: "git.fetch",
10787
+ args: {
10788
+ workspaceId,
10789
+ background: true
10790
+ }
10791
+ },
10792
+ commandContext
10793
+ );
10794
+ if (!result.ok) {
10795
+ throw new Error(result.error?.message ?? "Background fetch failed");
10796
+ }
10797
+ const data = result.data;
10798
+ if (data.success === false) {
10799
+ throw new Error(data.message ?? "Background fetch failed");
10800
+ }
10801
+ }
10802
+ });
10289
10803
  const sessionDb = createSessionDatabase(db);
10290
10804
  const providerConfigRepo = new ProviderConfigRepo(db);
10291
- const settingsRepo = new SettingsRepo(db);
10292
10805
  const sessionMgr = new SessionManager({
10293
10806
  terminalMgr,
10294
10807
  eventBus,
@@ -10298,10 +10811,11 @@ async function createServer(configOverrides) {
10298
10811
  providerConfigRepo
10299
10812
  });
10300
10813
  let supervisorMgr;
10301
- const workspaceMgr = new WorkspaceManager({
10814
+ workspaceMgr = new WorkspaceManager({
10302
10815
  db,
10303
10816
  eventBus,
10304
10817
  broadcaster: wsHub,
10818
+ autoFetch,
10305
10819
  teardown: async (workspaceId) => {
10306
10820
  await supervisorMgr?.deleteForWorkspace(workspaceId);
10307
10821
  await sessionMgr.stopForWorkspace(workspaceId);
@@ -10312,9 +10826,9 @@ async function createServer(configOverrides) {
10312
10826
  (err) => console.warn("[uploads] cascade cleanup failed", { wsId: workspaceId, err })
10313
10827
  )
10314
10828
  });
10829
+ workspaceMgr.hydrateWatchers();
10315
10830
  const authSessionRepo = new AuthSessionRepo(db);
10316
10831
  const authLoginBlockRepo = new AuthLoginBlockRepo(db);
10317
- const codexConfigAudit = createCodexConfigAuditApi();
10318
10832
  const app = await buildFastifyApp({
10319
10833
  wsHub,
10320
10834
  db,
@@ -10335,7 +10849,6 @@ async function createServer(configOverrides) {
10335
10849
  }
10336
10850
  });
10337
10851
  wsHub.setLogger(app.log);
10338
- await logCodexConfigFindings(codexConfigAudit, app.log);
10339
10852
  const supervisorRepo = new SupervisorRepo(db);
10340
10853
  const cycleRepo = new SupervisorCycleRepo(db);
10341
10854
  supervisorMgr = new SupervisorManager({
@@ -10353,12 +10866,15 @@ async function createServer(configOverrides) {
10353
10866
  });
10354
10867
  await sessionMgr.hydrate();
10355
10868
  await supervisorMgr.hydrate();
10356
- const providerRuntimeDeps = {};
10869
+ const providerMockOverrides = createE2EProviderMockOverrides();
10870
+ const providerRuntimeDeps = providerMockOverrides ? {
10871
+ commandExists: providerMockOverrides.commandExists
10872
+ } : {};
10357
10873
  const providerInstallMgr = new ProviderInstallManager(providerRegistry, {
10358
10874
  ...providerRuntimeDeps,
10359
- runCommand: runCommandAsString
10875
+ runCommand: providerMockOverrides?.runCommand ?? runCommandAsString
10360
10876
  });
10361
- const commandContext = {
10877
+ commandContext = {
10362
10878
  workspaceMgr,
10363
10879
  sessionMgr,
10364
10880
  terminalMgr,
@@ -10368,9 +10884,9 @@ async function createServer(configOverrides) {
10368
10884
  providerRegistry,
10369
10885
  fencingMgr,
10370
10886
  supervisorMgr,
10887
+ autoFetch,
10371
10888
  providerRuntimeDeps,
10372
- providerInstallMgr,
10373
- codexConfigAudit
10889
+ providerInstallMgr
10374
10890
  };
10375
10891
  wsHub.setCommandContext(commandContext);
10376
10892
  await app.listen({
@@ -10401,6 +10917,7 @@ async function createServer(configOverrides) {
10401
10917
  stopped = true;
10402
10918
  clearTimeout(gcTimer);
10403
10919
  await app.close();
10920
+ autoFetch.stop();
10404
10921
  supervisorMgr.stop();
10405
10922
  terminalMgr.shutdown();
10406
10923
  wsHub.destroy();
@@ -10522,9 +11039,10 @@ var init_server = __esm({
10522
11039
  init_src2();
10523
11040
  init_app();
10524
11041
  init_event_bus();
10525
- init_codex_config_audit();
10526
11042
  init_config();
11043
+ init_auto_fetch();
10527
11044
  init_command_runner();
11045
+ init_e2e_provider_mock();
10528
11046
  init_install_manager();
10529
11047
  init_manager();
10530
11048
  init_db();
@@ -10541,6 +11059,7 @@ var init_server = __esm({
10541
11059
  init_cleanup();
10542
11060
  init_constants();
10543
11061
  init_manager4();
11062
+ init_dispatch();
10544
11063
  init_fencing();
10545
11064
  init_hub();
10546
11065
  init_commands();
@@ -10694,8 +11213,8 @@ var init_workspace_repo = __esm({
10694
11213
  /**
10695
11214
  * Finds a workspace by path
10696
11215
  */
10697
- findByPath(path9) {
10698
- const row = this.db.prepare("SELECT * FROM workspaces WHERE path = ?").get(path9);
11216
+ findByPath(path10) {
11217
+ const row = this.db.prepare("SELECT * FROM workspaces WHERE path = ?").get(path10);
10699
11218
  return row ? this.rowToWorkspace(row) : void 0;
10700
11219
  }
10701
11220
  /**
@@ -10840,12 +11359,12 @@ function getCliConfigPath() {
10840
11359
  return join(homedir(), ".coder-studio", "config.json");
10841
11360
  }
10842
11361
  function readCliConfig() {
10843
- const path9 = getCliConfigPath();
10844
- if (!existsSync(path9)) {
11362
+ const path10 = getCliConfigPath();
11363
+ if (!existsSync(path10)) {
10845
11364
  return null;
10846
11365
  }
10847
11366
  try {
10848
- const parsed = JSON.parse(readFileSync(path9, "utf-8"));
11367
+ const parsed = JSON.parse(readFileSync(path10, "utf-8"));
10849
11368
  if (parsed.host !== void 0 && typeof parsed.host !== "string" || parsed.port !== void 0 && typeof parsed.port !== "number" || parsed.dataDir !== void 0 && typeof parsed.dataDir !== "string" || parsed.password !== void 0 && typeof parsed.password !== "string") {
10850
11369
  return null;
10851
11370
  }