@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.
package/dist/esm/bin.mjs CHANGED
@@ -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) => {
@@ -809,11 +809,11 @@ var init_src = __esm({
809
809
 
810
810
  // packages/utils/src/direct-execution.ts
811
811
  import { posix, resolve } from "node:path";
812
- function isWindowsDrivePath(path9) {
813
- return /^[A-Za-z]:\//.test(path9);
812
+ function isWindowsDrivePath(path10) {
813
+ return /^[A-Za-z]:\//.test(path10);
814
814
  }
815
- function normalizeComparablePath(path9) {
816
- let normalized = path9.replace(/\\/g, "/");
815
+ function normalizeComparablePath(path10) {
816
+ let normalized = path10.replace(/\\/g, "/");
817
817
  if (/^\/[A-Za-z]:\//.test(normalized)) {
818
818
  normalized = normalized.slice(1);
819
819
  }
@@ -837,8 +837,8 @@ function normalizeModuleUrlPath(moduleUrl) {
837
837
  if (url.protocol !== "file:") {
838
838
  return null;
839
839
  }
840
- const path9 = `${url.host ? `//${url.host}` : ""}${decodeURIComponent(url.pathname)}`;
841
- return normalizeComparablePath(path9);
840
+ const path10 = `${url.host ? `//${url.host}` : ""}${decodeURIComponent(url.pathname)}`;
841
+ return normalizeComparablePath(path10);
842
842
  }
843
843
  function normalizeArgvPath(argv1) {
844
844
  const isAbsoluteWindowsPath = /^[A-Za-z]:[\\/]/.test(argv1) || /^\\\\/.test(argv1);
@@ -1017,10 +1017,10 @@ var init_image = __esm({
1017
1017
  // packages/server/src/fs/file-io.ts
1018
1018
  import { createHash } from "crypto";
1019
1019
  import { readFile as fsReadFile, writeFile as fsWriteFile, mkdir, rm, stat } from "fs/promises";
1020
- import { dirname as dirname2, resolve as resolve2 } from "path";
1021
- async function statSafe(path9) {
1020
+ import { dirname as dirname2, isAbsolute, relative, resolve as resolve2 } from "path";
1021
+ async function statSafe(path10) {
1022
1022
  try {
1023
- return await stat(path9);
1023
+ return await stat(path10);
1024
1024
  } catch {
1025
1025
  return null;
1026
1026
  }
@@ -1053,7 +1053,8 @@ async function deleteEntry(rootPath, relPath) {
1053
1053
  function resolveSafe(root, relPath) {
1054
1054
  const absRoot = resolve2(root);
1055
1055
  const abs = resolve2(absRoot, relPath);
1056
- if (!abs.startsWith(absRoot + "/") && abs !== absRoot) {
1056
+ const rel = relative(absRoot, abs);
1057
+ if (rel === ".." || rel.startsWith(`..${"/"}`) || isAbsolute(rel)) {
1057
1058
  throw { code: "path_escape", message: "Path escapes workspace root" };
1058
1059
  }
1059
1060
  return abs;
@@ -1115,8 +1116,10 @@ var init_file_io = __esm({
1115
1116
  // packages/server/src/routes/file-asset.ts
1116
1117
  import { createReadStream } from "fs";
1117
1118
  import { realpath, stat as stat2 } from "fs/promises";
1119
+ import { isAbsolute as isAbsolute2, relative as relative2 } from "path";
1118
1120
  function isPathInsideRoot(rootPath, targetPath) {
1119
- return targetPath === rootPath || targetPath.startsWith(`${rootPath}/`);
1121
+ const rel = relative2(rootPath, targetPath);
1122
+ return rel !== ".." && !rel.startsWith(`..${"/"}`) && !isAbsolute2(rel);
1120
1123
  }
1121
1124
  function registerFileAssetRoutes(app, deps) {
1122
1125
  app.get(
@@ -1241,12 +1244,12 @@ async function ensureSafeUploadDir(rootDir, targetDir) {
1241
1244
  await mkdir2(resolvedRoot, { recursive: true });
1242
1245
  await assertDirectorySegmentSafe(resolvedRoot);
1243
1246
  }
1244
- const relative3 = path3.relative(resolvedRoot, resolvedTarget);
1245
- if (!relative3) {
1247
+ const relative5 = path3.relative(resolvedRoot, resolvedTarget);
1248
+ if (!relative5) {
1246
1249
  return;
1247
1250
  }
1248
1251
  let current = resolvedRoot;
1249
- for (const segment of relative3.split(path3.sep)) {
1252
+ for (const segment of relative5.split(path3.sep)) {
1250
1253
  current = path3.join(current, segment);
1251
1254
  try {
1252
1255
  await assertDirectorySegmentSafe(current);
@@ -1795,189 +1798,265 @@ var init_app = __esm({
1795
1798
  }
1796
1799
  });
1797
1800
 
1798
- // packages/server/src/config/codex-config-audit.ts
1799
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, renameSync, writeFileSync as writeFileSync2 } from "node:fs";
1800
- import { homedir as homedir2 } from "node:os";
1801
- import { dirname as dirname3, join as join2 } from "node:path";
1802
- function resolveCodexConfigPath() {
1803
- const codexHome = process.env.CODEX_HOME;
1804
- if (codexHome && codexHome.trim()) {
1805
- return join2(codexHome, "config.toml");
1806
- }
1807
- return join2(homedir2(), ".codex", "config.toml");
1808
- }
1809
- function auditCodexConfigToml(configPath) {
1810
- const path9 = configPath ?? resolveCodexConfigPath();
1811
- if (!existsSync3(path9)) {
1812
- return { configPath: path9, exists: false, findings: [] };
1813
- }
1814
- let content;
1815
- try {
1816
- content = readFileSync3(path9, "utf-8");
1817
- } catch {
1818
- return { configPath: path9, exists: false, findings: [] };
1819
- }
1820
- const lines = content.split(/\r?\n/);
1821
- const findings = [];
1822
- const notifyFinding = detectTopLevelNotify(lines);
1823
- if (notifyFinding) findings.push(notifyFinding);
1824
- const codexHooksFinding = detectCodexHooksFlag(lines);
1825
- if (codexHooksFinding) findings.push(codexHooksFinding);
1826
- return { configPath: path9, exists: true, findings };
1827
- }
1828
- function cleanupCodexConfigToml(configPath, opts) {
1829
- if (opts.removeIds.length === 0) {
1830
- return { removed: [], backupPath: null, noop: true };
1831
- }
1832
- if (!existsSync3(configPath)) {
1833
- return { removed: [], backupPath: null, noop: true };
1834
- }
1835
- const audit = auditCodexConfigToml(configPath);
1836
- const selected = audit.findings.filter((f) => opts.removeIds.includes(f.id));
1837
- if (selected.length === 0) {
1838
- return { removed: [], backupPath: null, noop: true };
1839
- }
1840
- const original = readFileSync3(configPath, "utf-8");
1841
- const backupPath = writeBackup(configPath, original, opts.backupDir);
1842
- const linesToDrop = /* @__PURE__ */ new Set();
1843
- for (const finding of selected) {
1844
- for (let ln = finding.startLine; ln <= finding.endLine; ln++) {
1845
- linesToDrop.add(ln);
1846
- }
1847
- }
1848
- const originalLines = original.split(/\r?\n/);
1849
- const kept = [];
1850
- for (let i = 0; i < originalLines.length; i++) {
1851
- const ln = i + 1;
1852
- if (linesToDrop.has(ln)) continue;
1853
- kept.push(originalLines[i]);
1854
- }
1855
- const cleaned = collapseBlankRunsNearDeletions(kept);
1856
- const output2 = cleaned.join("\n");
1857
- atomicWrite(configPath, output2);
1858
- return {
1859
- removed: selected.map((f) => f.id),
1860
- backupPath,
1861
- noop: false
1862
- };
1863
- }
1864
- function detectTopLevelNotify(lines) {
1865
- const headerRegex = /^\s*\[/;
1866
- const notifyRegex = /^\s*notify\s*=\s*(.*)$/;
1867
- let inTopLevel = true;
1868
- for (let i = 0; i < lines.length; i++) {
1869
- const line = lines[i];
1870
- const trimmed = line.trim();
1871
- if (headerRegex.test(trimmed)) {
1872
- inTopLevel = false;
1873
- continue;
1874
- }
1875
- if (!inTopLevel) continue;
1876
- const m = trimmed.match(notifyRegex);
1877
- if (!m) continue;
1878
- const rhs = (m[1] ?? "").trim();
1879
- if (rhs.startsWith("[") && rhs.endsWith("]") && countBrackets(rhs) === 0) {
1880
- return makeNotifyFinding(lines, i, i);
1881
- }
1882
- if (rhs.startsWith("[")) {
1883
- let depth = countBrackets(rhs);
1884
- for (let j = i + 1; j < lines.length; j++) {
1885
- depth += countBrackets(lines[j]);
1886
- if (depth === 0) {
1887
- return makeNotifyFinding(lines, i, j);
1801
+ // packages/server/src/git/auto-fetch.ts
1802
+ var PERIOD_SETTING_KEY, DEFAULT_PERIOD_SEC, TICK_INTERVAL_MS, OPEN_TIME_COOLDOWN_MS, MAX_CONSECUTIVE_FAILURES, JITTER_RATIO, AutoFetchScheduler;
1803
+ var init_auto_fetch = __esm({
1804
+ "packages/server/src/git/auto-fetch.ts"() {
1805
+ "use strict";
1806
+ PERIOD_SETTING_KEY = "git.autofetchPeriodSec";
1807
+ DEFAULT_PERIOD_SEC = 180;
1808
+ TICK_INTERVAL_MS = 1e3;
1809
+ OPEN_TIME_COOLDOWN_MS = 5 * 60 * 1e3;
1810
+ MAX_CONSECUTIVE_FAILURES = 3;
1811
+ JITTER_RATIO = 0.1;
1812
+ AutoFetchScheduler = class {
1813
+ constructor(deps) {
1814
+ this.deps = deps;
1815
+ this.now = deps.now ?? Date.now;
1816
+ this.random = deps.random ?? (() => 0.5);
1817
+ this.setTimeoutFn = deps.setTimeout ?? globalThis.setTimeout;
1818
+ this.setIntervalFn = deps.setInterval ?? globalThis.setInterval;
1819
+ this.clearIntervalFn = deps.clearInterval ?? globalThis.clearInterval;
1820
+ this.clearTimeoutFn = deps.clearTimeout ?? globalThis.clearTimeout;
1821
+ this.start();
1822
+ }
1823
+ deps;
1824
+ clientWorkspaceMap = /* @__PURE__ */ new Map();
1825
+ workspaceStateMap = /* @__PURE__ */ new Map();
1826
+ now;
1827
+ random;
1828
+ setTimeoutFn;
1829
+ setIntervalFn;
1830
+ clearIntervalFn;
1831
+ clearTimeoutFn;
1832
+ pendingTimeouts = /* @__PURE__ */ new Set();
1833
+ tickTimer = null;
1834
+ stopped = false;
1835
+ registerViewer(clientId, workspaceId) {
1836
+ const previousWorkspaceId = this.clientWorkspaceMap.get(clientId);
1837
+ if (previousWorkspaceId === workspaceId) {
1838
+ return;
1888
1839
  }
1840
+ if (previousWorkspaceId) {
1841
+ this.unregisterViewer(clientId);
1842
+ }
1843
+ this.clientWorkspaceMap.set(clientId, workspaceId);
1844
+ const state = this.getOrCreateState(workspaceId);
1845
+ state.viewerCount += 1;
1846
+ if (state.viewerCount === 1) {
1847
+ state.blocked = false;
1848
+ state.consecutiveFailures = 0;
1849
+ }
1850
+ this.ensureNextPeriodicFetch(state, false);
1851
+ }
1852
+ unregisterViewer(clientId) {
1853
+ const workspaceId = this.clientWorkspaceMap.get(clientId);
1854
+ if (!workspaceId) {
1855
+ return;
1856
+ }
1857
+ this.clientWorkspaceMap.delete(clientId);
1858
+ const state = this.workspaceStateMap.get(workspaceId);
1859
+ if (!state) {
1860
+ return;
1861
+ }
1862
+ state.viewerCount = Math.max(0, state.viewerCount - 1);
1863
+ if (state.viewerCount === 0) {
1864
+ state.blocked = false;
1865
+ state.consecutiveFailures = 0;
1866
+ state.nextFetchAt = void 0;
1867
+ }
1868
+ }
1869
+ triggerOpenTimeFetch(workspaceId) {
1870
+ const state = this.getOrCreateState(workspaceId);
1871
+ const lastFetchAt = state.lastFetchAt;
1872
+ if (this.stopped || state.inFlight) {
1873
+ return;
1874
+ }
1875
+ if (lastFetchAt !== void 0 && this.now() - lastFetchAt < OPEN_TIME_COOLDOWN_MS) {
1876
+ return;
1877
+ }
1878
+ const timer = this.setTimeoutFn(() => {
1879
+ this.pendingTimeouts.delete(timer);
1880
+ if (this.stopped) {
1881
+ return;
1882
+ }
1883
+ void this.fetchWorkspace(workspaceId, "open");
1884
+ }, 0);
1885
+ this.pendingTimeouts.add(timer);
1886
+ }
1887
+ recordSuccess(workspaceId) {
1888
+ const state = this.getOrCreateState(workspaceId);
1889
+ state.lastFetchAt = this.now();
1890
+ state.consecutiveFailures = 0;
1891
+ state.blocked = false;
1892
+ state.nextFetchAt = void 0;
1893
+ this.ensureNextPeriodicFetch(state, true);
1894
+ }
1895
+ recordFailure(workspaceId) {
1896
+ const state = this.getOrCreateState(workspaceId);
1897
+ state.consecutiveFailures += 1;
1898
+ state.nextFetchAt = void 0;
1899
+ if (state.consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
1900
+ state.blocked = true;
1901
+ return;
1902
+ }
1903
+ this.ensureNextPeriodicFetch(state, true);
1904
+ }
1905
+ getLastFetchAt(workspaceId) {
1906
+ return this.workspaceStateMap.get(workspaceId)?.lastFetchAt;
1907
+ }
1908
+ async runExclusive(workspaceId, op) {
1909
+ const release = await this.acquireWorkspaceOperation(workspaceId);
1910
+ try {
1911
+ return await op();
1912
+ } finally {
1913
+ release();
1914
+ }
1915
+ }
1916
+ start() {
1917
+ if (this.tickTimer) {
1918
+ return;
1919
+ }
1920
+ this.stopped = false;
1921
+ this.tickTimer = this.setIntervalFn(() => {
1922
+ this.evaluateDueFetches();
1923
+ }, TICK_INTERVAL_MS);
1924
+ }
1925
+ stop() {
1926
+ if (this.tickTimer) {
1927
+ this.clearIntervalFn(this.tickTimer);
1928
+ this.tickTimer = null;
1929
+ }
1930
+ this.stopped = true;
1931
+ for (const timer of this.pendingTimeouts) {
1932
+ this.clearTimeoutFn(timer);
1933
+ }
1934
+ this.pendingTimeouts.clear();
1935
+ }
1936
+ evaluateDueFetches() {
1937
+ const now = this.now();
1938
+ for (const [workspaceId, state] of this.workspaceStateMap) {
1939
+ if (!this.shouldCheckWorkspace(state)) {
1940
+ continue;
1941
+ }
1942
+ if (state.nextFetchAt === void 0) {
1943
+ this.ensureNextPeriodicFetch(state, false);
1944
+ }
1945
+ if (state.nextFetchAt === void 0 || state.nextFetchAt > now) {
1946
+ continue;
1947
+ }
1948
+ void this.fetchWorkspace(workspaceId, "periodic", state.nextFetchAt);
1949
+ }
1950
+ }
1951
+ shouldCheckWorkspace(state) {
1952
+ return this.getPeriodMs() > 0 && state.viewerCount > 0 && !state.inFlight && !state.blocked;
1953
+ }
1954
+ ensureNextPeriodicFetch(state, resetSchedule) {
1955
+ const periodMs = this.getPeriodMs();
1956
+ if (periodMs <= 0 || state.viewerCount <= 0 || state.blocked || state.inFlight) {
1957
+ return;
1958
+ }
1959
+ if (!resetSchedule && state.nextFetchAt !== void 0) {
1960
+ return;
1961
+ }
1962
+ if (state.lastFetchAt === void 0) {
1963
+ state.nextFetchAt = this.now();
1964
+ return;
1965
+ }
1966
+ const nextFetchAt = state.lastFetchAt + this.getJitteredPeriodMs(periodMs);
1967
+ state.nextFetchAt = Math.max(this.now(), nextFetchAt);
1968
+ }
1969
+ getPeriodMs() {
1970
+ const configuredPeriodSec = this.deps.settingsRepo.get(PERIOD_SETTING_KEY);
1971
+ const periodSec = configuredPeriodSec ?? DEFAULT_PERIOD_SEC;
1972
+ return Math.max(0, periodSec) * 1e3;
1973
+ }
1974
+ getJitteredPeriodMs(periodMs) {
1975
+ const jitterScale = 1 + (this.random() - 0.5) * 2 * JITTER_RATIO;
1976
+ return Math.round(periodMs * jitterScale);
1977
+ }
1978
+ getOrCreateState(workspaceId) {
1979
+ const existingState = this.workspaceStateMap.get(workspaceId);
1980
+ if (existingState) {
1981
+ return existingState;
1982
+ }
1983
+ const state = {
1984
+ viewerCount: 0,
1985
+ consecutiveFailures: 0,
1986
+ inFlight: false,
1987
+ blocked: false,
1988
+ waiters: []
1989
+ };
1990
+ this.workspaceStateMap.set(workspaceId, state);
1991
+ return state;
1992
+ }
1993
+ async fetchWorkspace(workspaceId, mode, scheduledAt) {
1994
+ if (this.stopped) {
1995
+ return;
1996
+ }
1997
+ const state = this.getOrCreateState(workspaceId);
1998
+ if (state.inFlight || state.blocked) {
1999
+ return;
2000
+ }
2001
+ if (!this.deps.workspaceMgr.get(workspaceId)) {
2002
+ state.nextFetchAt = void 0;
2003
+ return;
2004
+ }
2005
+ await this.runExclusive(workspaceId, async () => {
2006
+ const currentState = this.getOrCreateState(workspaceId);
2007
+ if (this.stopped || currentState.blocked) {
2008
+ return;
2009
+ }
2010
+ if (!this.deps.workspaceMgr.get(workspaceId)) {
2011
+ currentState.nextFetchAt = void 0;
2012
+ return;
2013
+ }
2014
+ if (mode === "open") {
2015
+ const lastFetchAt = currentState.lastFetchAt;
2016
+ if (lastFetchAt !== void 0 && this.now() - lastFetchAt < OPEN_TIME_COOLDOWN_MS) {
2017
+ return;
2018
+ }
2019
+ } else if (scheduledAt !== void 0 && currentState.lastFetchAt !== void 0 && currentState.lastFetchAt >= scheduledAt) {
2020
+ return;
2021
+ }
2022
+ try {
2023
+ await this.deps.runFetch(workspaceId);
2024
+ this.recordSuccess(workspaceId);
2025
+ } catch {
2026
+ this.recordFailure(workspaceId);
2027
+ }
2028
+ });
2029
+ }
2030
+ acquireWorkspaceOperation(workspaceId) {
2031
+ const state = this.getOrCreateState(workspaceId);
2032
+ return new Promise((resolve4) => {
2033
+ const grant = () => {
2034
+ state.inFlight = true;
2035
+ state.nextFetchAt = void 0;
2036
+ let released = false;
2037
+ resolve4(() => {
2038
+ if (released) {
2039
+ return;
2040
+ }
2041
+ released = true;
2042
+ const next = state.waiters.shift();
2043
+ if (next) {
2044
+ next();
2045
+ return;
2046
+ }
2047
+ state.inFlight = false;
2048
+ this.ensureNextPeriodicFetch(state, true);
2049
+ });
2050
+ };
2051
+ if (state.inFlight) {
2052
+ state.waiters.push(grant);
2053
+ return;
2054
+ }
2055
+ grant();
2056
+ });
1889
2057
  }
1890
- return null;
1891
- }
1892
- return makeNotifyFinding(lines, i, i);
1893
- }
1894
- return null;
1895
- }
1896
- function makeNotifyFinding(lines, startIdx, endIdx) {
1897
- return {
1898
- id: "toml_notify",
1899
- type: "toml_notify",
1900
- severity: "warn",
1901
- startLine: startIdx + 1,
1902
- endLine: endIdx + 1,
1903
- snippet: lines.slice(startIdx, endIdx + 1).join("\n"),
1904
- 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"
1905
- };
1906
- }
1907
- function detectCodexHooksFlag(lines) {
1908
- const headerRegex = /^\s*\[([^\]]+)\]\s*$/;
1909
- const codexHooksRegex = /^\s*codex_hooks\s*=\s*true\b/;
1910
- let currentSection = null;
1911
- for (let i = 0; i < lines.length; i++) {
1912
- const line = lines[i];
1913
- const headerMatch = line.match(headerRegex);
1914
- if (headerMatch) {
1915
- currentSection = headerMatch[1].trim();
1916
- continue;
1917
- }
1918
- if (currentSection !== "features") continue;
1919
- if (!codexHooksRegex.test(line)) continue;
1920
- return {
1921
- id: "toml_codex_hooks",
1922
- type: "toml_codex_hooks",
1923
- severity: "info",
1924
- startLine: i + 1,
1925
- endLine: i + 1,
1926
- snippet: line,
1927
- 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"
1928
2058
  };
1929
2059
  }
1930
- return null;
1931
- }
1932
- function countBrackets(s) {
1933
- let n = 0;
1934
- for (const ch of s) {
1935
- if (ch === "[") n++;
1936
- else if (ch === "]") n--;
1937
- }
1938
- return n;
1939
- }
1940
- function writeBackup(configPath, original, backupDir) {
1941
- const dir = backupDir ?? dirname3(configPath);
1942
- if (!existsSync3(dir)) {
1943
- mkdirSync2(dir, { recursive: true });
1944
- }
1945
- const ts = formatTimestamp(/* @__PURE__ */ new Date());
1946
- const backupPath = join2(dir, `${basenameNoTomlExt(configPath)}.bak.${ts}.toml`);
1947
- writeFileSync2(backupPath, original, "utf-8");
1948
- return backupPath;
1949
- }
1950
- function atomicWrite(configPath, contents) {
1951
- const tempPath = `${configPath}.tmp`;
1952
- writeFileSync2(tempPath, contents, "utf-8");
1953
- renameSync(tempPath, configPath);
1954
- }
1955
- function basenameNoTomlExt(p) {
1956
- const base = p.split(/[\\/]/).pop() ?? "config.toml";
1957
- return base.replace(/\.toml$/i, "");
1958
- }
1959
- function formatTimestamp(d) {
1960
- const pad = (n) => String(n).padStart(2, "0");
1961
- return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
1962
- }
1963
- function collapseBlankRunsNearDeletions(lines) {
1964
- const out = [];
1965
- let blankRun = 0;
1966
- for (const line of lines) {
1967
- if (line.trim() === "") {
1968
- blankRun++;
1969
- if (blankRun <= 2) out.push(line);
1970
- } else {
1971
- blankRun = 0;
1972
- out.push(line);
1973
- }
1974
- }
1975
- return out;
1976
- }
1977
- var init_codex_config_audit = __esm({
1978
- "packages/server/src/config/codex-config-audit.ts"() {
1979
- "use strict";
1980
- }
1981
2060
  });
1982
2061
 
1983
2062
  // packages/server/src/provider-runtime/command-runner.ts
@@ -2050,6 +2129,157 @@ var init_command_check = __esm({
2050
2129
  }
2051
2130
  });
2052
2131
 
2132
+ // packages/server/src/provider-runtime/e2e-provider-mock.ts
2133
+ import { chmodSync, existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
2134
+ import { dirname as dirname3, join as join2 } from "node:path";
2135
+ function createE2EProviderMockOverrides(env = process.env) {
2136
+ const statePath = env.CODER_STUDIO_E2E_PROVIDER_STATE_PATH;
2137
+ if (!statePath) {
2138
+ return null;
2139
+ }
2140
+ const binDir = env.CODER_STUDIO_E2E_PROVIDER_BIN_DIR;
2141
+ const debugLogPath = env.CODER_STUDIO_E2E_PROVIDER_DEBUG_LOG_PATH;
2142
+ appendDebugLog(debugLogPath, `init statePath=${statePath} binDir=${binDir ?? ""}`);
2143
+ const commandExists = async (command) => {
2144
+ const state = readMockState(statePath);
2145
+ const override = state.commands?.[command];
2146
+ appendDebugLog(
2147
+ debugLogPath,
2148
+ `commandExists ${command} override=${String(override)} state=${JSON.stringify(state.commands ?? {})}`
2149
+ );
2150
+ if (typeof override === "boolean") {
2151
+ return override;
2152
+ }
2153
+ return checkCommandAvailable(command);
2154
+ };
2155
+ const runCommand2 = async (file, args, options) => {
2156
+ const providerId = getInstallProviderId(file, args);
2157
+ appendDebugLog(
2158
+ debugLogPath,
2159
+ `runCommand ${file} ${args.join(" ")} provider=${providerId ?? "none"}`
2160
+ );
2161
+ if (!providerId) {
2162
+ return runCommandAsString(file, args, options);
2163
+ }
2164
+ const state = readMockState(statePath);
2165
+ const behavior = state.installBehavior?.[providerId];
2166
+ appendDebugLog(
2167
+ debugLogPath,
2168
+ `behavior ${providerId} ${JSON.stringify(behavior)} state=${JSON.stringify(state)}`
2169
+ );
2170
+ if (!behavior) {
2171
+ return runCommandAsString(file, args, options);
2172
+ }
2173
+ if (behavior.result === "success") {
2174
+ writeMockState(statePath, (draft) => {
2175
+ draft.commands ??= {};
2176
+ draft.commands[providerId] = true;
2177
+ });
2178
+ if (binDir) {
2179
+ ensureProviderCommand(binDir, providerId);
2180
+ }
2181
+ appendDebugLog(debugLogPath, `install success ${providerId}`);
2182
+ return {
2183
+ stdout: `installed ${providerId}`,
2184
+ stderr: ""
2185
+ };
2186
+ }
2187
+ const message = behavior.message ?? (behavior.result === "permission_denied" ? "permission denied" : "command not found");
2188
+ throw Object.assign(new Error(message), {
2189
+ exitCode: 1,
2190
+ stdout: "",
2191
+ stderr: message
2192
+ });
2193
+ };
2194
+ return {
2195
+ commandExists,
2196
+ runCommand: runCommand2
2197
+ };
2198
+ }
2199
+ function getInstallProviderId(file, args) {
2200
+ if (file !== "npm" || args.length !== 3) {
2201
+ return null;
2202
+ }
2203
+ if (args[0] !== "install" || args[1] !== "-g") {
2204
+ return null;
2205
+ }
2206
+ const packageName = args[2];
2207
+ if (packageName === PROVIDER_INSTALL_PACKAGES.claude) {
2208
+ return "claude";
2209
+ }
2210
+ if (packageName === PROVIDER_INSTALL_PACKAGES.codex) {
2211
+ return "codex";
2212
+ }
2213
+ return null;
2214
+ }
2215
+ function readMockState(statePath) {
2216
+ if (!existsSync3(statePath)) {
2217
+ return {};
2218
+ }
2219
+ const raw = readFileSync3(statePath, "utf8");
2220
+ if (!raw.trim()) {
2221
+ return {};
2222
+ }
2223
+ try {
2224
+ return JSON.parse(raw);
2225
+ } catch (error) {
2226
+ throw new Error(
2227
+ `Invalid provider mock state at ${statePath}: ${error instanceof Error ? error.message : String(error)}`
2228
+ );
2229
+ }
2230
+ }
2231
+ function writeMockState(statePath, updater) {
2232
+ const nextState = readMockState(statePath);
2233
+ updater(nextState);
2234
+ mkdirSync2(dirname3(statePath), { recursive: true });
2235
+ writeFileSync2(statePath, JSON.stringify(nextState, null, 2));
2236
+ return nextState;
2237
+ }
2238
+ function ensureProviderCommand(binDir, providerId) {
2239
+ mkdirSync2(binDir, { recursive: true });
2240
+ const scriptPath = join2(binDir, providerId);
2241
+ writeFileSync2(scriptPath, PROVIDER_COMMAND_SCRIPTS[providerId], "utf8");
2242
+ chmodSync(scriptPath, 493);
2243
+ }
2244
+ function appendDebugLog(path10, line) {
2245
+ if (!path10) {
2246
+ return;
2247
+ }
2248
+ mkdirSync2(dirname3(path10), { recursive: true });
2249
+ writeFileSync2(path10, `${line}
2250
+ `, { flag: "a" });
2251
+ }
2252
+ var PROVIDER_INSTALL_PACKAGES, PROVIDER_COMMAND_SCRIPTS;
2253
+ var init_e2e_provider_mock = __esm({
2254
+ "packages/server/src/provider-runtime/e2e-provider-mock.ts"() {
2255
+ "use strict";
2256
+ init_command_check();
2257
+ init_command_runner();
2258
+ PROVIDER_INSTALL_PACKAGES = {
2259
+ claude: "@anthropic-ai/claude-code",
2260
+ codex: "@openai/codex"
2261
+ };
2262
+ PROVIDER_COMMAND_SCRIPTS = {
2263
+ claude: `#!/usr/bin/env bash
2264
+ set -euo pipefail
2265
+ trap 'exit 0' TERM INT
2266
+ printf 'Mock Claude ready\\n'
2267
+ while true; do
2268
+ sleep 1
2269
+ done
2270
+ `,
2271
+ codex: `#!/usr/bin/env bash
2272
+ set -euo pipefail
2273
+ trap 'exit 0' TERM INT
2274
+ printf 'Session ID: abcdef-123456\\n> '
2275
+ while true; do
2276
+ sleep 1
2277
+ done
2278
+ `
2279
+ };
2280
+ }
2281
+ });
2282
+
2053
2283
  // packages/server/src/provider-runtime/install-manager.ts
2054
2284
  import { randomUUID as randomUUID2 } from "node:crypto";
2055
2285
  function getErrorDetails(error) {
@@ -4210,7 +4440,7 @@ function parseStatus(porcelainV2) {
4210
4440
  continue;
4211
4441
  }
4212
4442
  if (record.startsWith("? ")) {
4213
- untracked.push({ path: record.substring(2) });
4443
+ untracked.push({ path: record.substring(2), status: "untracked" });
4214
4444
  }
4215
4445
  }
4216
4446
  return {
@@ -4231,11 +4461,11 @@ function parseOrdinaryChangedEntry(record, staged, modified, deleted) {
4231
4461
  if (!xy) {
4232
4462
  return;
4233
4463
  }
4234
- const path9 = parts.slice(8).join(" ");
4235
- if (!path9) {
4464
+ const path10 = parts.slice(8).join(" ");
4465
+ if (!path10) {
4236
4466
  return;
4237
4467
  }
4238
- pushChange({ path: path9 }, xy, staged, modified, deleted);
4468
+ pushChange({ path: path10 }, xy, staged, modified, deleted);
4239
4469
  }
4240
4470
  function parseRenamedEntry(record, oldPathRecord, staged, modified, deleted) {
4241
4471
  const parts = record.split(" ");
@@ -4247,27 +4477,52 @@ function parseRenamedEntry(record, oldPathRecord, staged, modified, deleted) {
4247
4477
  const pathAndMaybeOldPath = pathTokens.join(" ");
4248
4478
  const inlinePathParts = pathAndMaybeOldPath.split(" ");
4249
4479
  const fallbackPath = !oldPathRecord && inlinePathParts.length === 1 && pathTokens.length > 1 ? pathTokens.slice(0, -1).join(" ") : void 0;
4250
- const path9 = fallbackPath ?? inlinePathParts[0];
4251
- if (!path9) {
4480
+ const path10 = fallbackPath ?? inlinePathParts[0];
4481
+ if (!path10) {
4252
4482
  return;
4253
4483
  }
4254
4484
  const oldPath = (oldPathRecord && !oldPathRecord.startsWith("#") ? oldPathRecord : void 0) ?? inlinePathParts[1] ?? (pathTokens.length > 1 ? pathTokens[pathTokens.length - 1] : void 0);
4255
- pushChange({ path: path9, oldPath }, xy, staged, modified, deleted);
4485
+ pushChange({ path: path10, oldPath }, xy, staged, modified, deleted);
4256
4486
  }
4257
4487
  function pushChange(change, xy, staged, modified, deleted) {
4258
4488
  const indexStatus = xy[0];
4259
4489
  const worktreeStatus = xy[1];
4260
4490
  if (indexStatus && indexStatus !== "." && indexStatus !== " ") {
4261
- staged.push(change);
4491
+ staged.push({
4492
+ ...change,
4493
+ status: resolveGitChangeStatus(indexStatus, change.oldPath)
4494
+ });
4262
4495
  }
4263
4496
  if (!worktreeStatus || worktreeStatus === "." || worktreeStatus === " ") {
4264
4497
  return;
4265
4498
  }
4266
4499
  if (worktreeStatus === "D") {
4267
- deleted.push({ path: change.path });
4500
+ deleted.push({ path: change.path, status: "deleted" });
4268
4501
  return;
4269
4502
  }
4270
- modified.push({ path: change.path });
4503
+ modified.push({
4504
+ path: change.path,
4505
+ oldPath: change.oldPath,
4506
+ status: resolveGitChangeStatus(worktreeStatus, change.oldPath)
4507
+ });
4508
+ }
4509
+ function resolveGitChangeStatus(code, oldPath) {
4510
+ switch (code) {
4511
+ case "A":
4512
+ return "added";
4513
+ case "D":
4514
+ return "deleted";
4515
+ case "R":
4516
+ return "renamed";
4517
+ case "C":
4518
+ return oldPath ? "renamed" : "added";
4519
+ case "U":
4520
+ return "modified";
4521
+ case "M":
4522
+ case "T":
4523
+ default:
4524
+ return oldPath ? "renamed" : "modified";
4525
+ }
4271
4526
  }
4272
4527
  var init_status_parser = __esm({
4273
4528
  "packages/server/src/git/status-parser.ts"() {
@@ -4337,6 +4592,40 @@ async function getGitStatus(cwd) {
4337
4592
  headSubject: headSubjectOutput.trim()
4338
4593
  };
4339
4594
  }
4595
+ async function getGitHistory(cwd, limit = 5) {
4596
+ try {
4597
+ const { stdout } = await runGit(cwd, [
4598
+ "log",
4599
+ `--max-count=${Math.max(1, limit)}`,
4600
+ "--format=%H%x1f%h%x1f%s%x1f%an%x1f%at%x1e"
4601
+ ]);
4602
+ return stdout.split("").map((record) => record.trim()).filter((record) => record.length > 0).map((record) => {
4603
+ const [sha = "", shortSha = "", subject = "", authorName = "", authoredAt = "0"] = record.split("");
4604
+ return {
4605
+ sha,
4606
+ shortSha,
4607
+ subject,
4608
+ authorName,
4609
+ authoredAt: Number.parseInt(authoredAt, 10) * 1e3
4610
+ };
4611
+ }).filter((entry) => entry.sha && entry.subject);
4612
+ } catch (error) {
4613
+ if (error instanceof GitError && /does not have any commits yet/i.test(error.stderr)) {
4614
+ return [];
4615
+ }
4616
+ throw error;
4617
+ }
4618
+ }
4619
+ async function getGitCommitDiff(cwd, sha) {
4620
+ const { stdout } = await runGit(cwd, [
4621
+ "show",
4622
+ "--format=medium",
4623
+ "--no-color",
4624
+ "--end-of-options",
4625
+ sha
4626
+ ]);
4627
+ return stdout;
4628
+ }
4340
4629
  async function getGitStatusSummary(cwd) {
4341
4630
  const { stdout } = await runGit(cwd, ["status", "--short"]);
4342
4631
  return stdout.trim();
@@ -4357,12 +4646,12 @@ async function discardChanges(cwd, paths) {
4357
4646
  if (paths.length === 0) return;
4358
4647
  const trackedPaths = [];
4359
4648
  const untrackedPaths = [];
4360
- for (const path9 of paths) {
4649
+ for (const path10 of paths) {
4361
4650
  try {
4362
- await runGit(cwd, ["ls-files", "--error-unmatch", "--", path9]);
4363
- trackedPaths.push(path9);
4651
+ await runGit(cwd, ["ls-files", "--error-unmatch", "--", path10]);
4652
+ trackedPaths.push(path10);
4364
4653
  } catch {
4365
- untrackedPaths.push(path9);
4654
+ untrackedPaths.push(path10);
4366
4655
  }
4367
4656
  }
4368
4657
  if (trackedPaths.length > 0) {
@@ -4401,6 +4690,7 @@ async function runGitPush(cwd, options) {
4401
4690
  if (!remote) {
4402
4691
  remote = await getPreferredRemote(cwd) ?? void 0;
4403
4692
  }
4693
+ const summaryBranch = branch ?? await getCurrentBranchName(cwd);
4404
4694
  if (remote && branch) {
4405
4695
  args.push(remote, `HEAD:${branch}`);
4406
4696
  } else if (remote) {
@@ -4418,8 +4708,13 @@ async function runGitPush(cwd, options) {
4418
4708
  if (options?.auth) {
4419
4709
  await persistGitHttpCredentials(cwd, options.auth, remoteMetadata);
4420
4710
  }
4421
- const message = stdout || stderr || "Push completed successfully";
4422
- return { success: true, message };
4711
+ return {
4712
+ success: true,
4713
+ message: "Push completed successfully",
4714
+ remote,
4715
+ branch: summaryBranch,
4716
+ updated: !isPushUpToDate(stdout, stderr)
4717
+ };
4423
4718
  } catch (error) {
4424
4719
  throw normalizeGitAuthFailure(error, {
4425
4720
  operation: "push",
@@ -4443,6 +4738,7 @@ async function runGitPull(cwd, options) {
4443
4738
  if (!remote && branch) {
4444
4739
  remote = await getPreferredRemote(cwd) ?? "origin";
4445
4740
  }
4741
+ const summaryBranch = branch ?? await getCurrentBranchName(cwd);
4446
4742
  if (remote && branch) {
4447
4743
  args.push(remote, branch);
4448
4744
  }
@@ -4472,8 +4768,14 @@ async function runGitPull(cwd, options) {
4472
4768
  }
4473
4769
  }
4474
4770
  }
4475
- const message = stdout || stderr || "Pull completed successfully";
4476
- return { success: true, message, updatedFiles };
4771
+ return {
4772
+ success: true,
4773
+ message: "Pull completed successfully",
4774
+ remote,
4775
+ branch: summaryBranch,
4776
+ updated: !isPullUpToDate(stdout, stderr),
4777
+ updatedFiles
4778
+ };
4477
4779
  } catch (error) {
4478
4780
  throw normalizeGitAuthFailure(error, {
4479
4781
  operation: "pull",
@@ -4485,8 +4787,63 @@ async function runGitPull(cwd, options) {
4485
4787
  await authExecution.cleanup();
4486
4788
  }
4487
4789
  }
4790
+ async function runGitFetch(cwd, options) {
4791
+ const args = ["fetch"];
4792
+ const remote = options?.remote;
4793
+ const metadataRemote = remote ?? await getPreferredRemote(cwd) ?? void 0;
4794
+ const prune = options?.prune ?? true;
4795
+ if (remote) {
4796
+ args.push(remote);
4797
+ } else {
4798
+ args.push("--all");
4799
+ }
4800
+ if (prune) {
4801
+ args.push("--prune");
4802
+ }
4803
+ const remoteUrl = metadataRemote ? await getRemoteUrl(cwd, metadataRemote) : null;
4804
+ const remoteMetadata = parseRemoteUrlMetadata(remoteUrl ?? void 0);
4805
+ const authExecution = await prepareGitAuthExecution(options?.auth, remoteMetadata);
4806
+ try {
4807
+ const { stdout, stderr } = await runGit(cwd, args, {
4808
+ timeoutMs: options?.timeoutMs ?? GIT_NETWORK_TIMEOUT_MS,
4809
+ env: authExecution.env,
4810
+ config: authExecution.config
4811
+ });
4812
+ if (options?.auth) {
4813
+ await persistGitHttpCredentials(cwd, options.auth, remoteMetadata);
4814
+ }
4815
+ const message = stdout || stderr || "Fetch completed successfully";
4816
+ return {
4817
+ success: true,
4818
+ message,
4819
+ updatedRefs: parseFetchUpdatedRefs(stderr)
4820
+ };
4821
+ } catch (error) {
4822
+ throw normalizeGitAuthFailure(error, {
4823
+ operation: "fetch",
4824
+ remote: metadataRemote,
4825
+ remoteUrl: remoteMetadata.sanitizedUrl ?? remoteUrl ?? void 0,
4826
+ attemptedCredentialAuth: Boolean(options?.auth)
4827
+ });
4828
+ } finally {
4829
+ await authExecution.cleanup();
4830
+ }
4831
+ }
4832
+ function parseFetchUpdatedRefs(stderr) {
4833
+ const refs = [];
4834
+ for (const rawLine of stderr.split("\n")) {
4835
+ const line = rawLine.trimEnd();
4836
+ const arrowIndex = line.indexOf(" -> ");
4837
+ if (arrowIndex < 0) continue;
4838
+ const target = line.slice(arrowIndex + 4).trim();
4839
+ if (!target) continue;
4840
+ refs.push(target);
4841
+ }
4842
+ return refs;
4843
+ }
4488
4844
  async function runGitCheckout(cwd, ref, options) {
4489
4845
  const args = ["checkout"];
4846
+ const formatCheckoutError = (error, fallbackMessage) => error instanceof GitError ? error.stderr.trim() || error.message || fallbackMessage : fallbackMessage;
4490
4847
  let isRemoteRef = false;
4491
4848
  try {
4492
4849
  const { stdout: remoteList } = await runGit(cwd, ["remote"]);
@@ -4498,15 +4855,22 @@ async function runGitCheckout(cwd, ref, options) {
4498
4855
  if (isRemoteRef && !options?.createBranch) {
4499
4856
  const remoteSeparatorIndex = ref.indexOf("/");
4500
4857
  const branchName = remoteSeparatorIndex >= 0 ? ref.slice(remoteSeparatorIndex + 1) : ref;
4501
- args.push("-b", branchName, ref);
4858
+ try {
4859
+ await runGit(cwd, ["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`]);
4860
+ const { stdout, stderr } = await runGit(cwd, ["checkout", branchName]);
4861
+ const message = stdout || stderr || `Checkout to ${branchName} completed`;
4862
+ return { success: true, message, branch: branchName };
4863
+ } catch {
4864
+ args.push("-b", branchName, ref);
4865
+ }
4502
4866
  try {
4503
4867
  const { stdout, stderr } = await runGit(cwd, args);
4504
4868
  const message = stdout || stderr || `Checkout to ${ref} completed`;
4505
4869
  return { success: true, message, branch: branchName };
4506
- } catch {
4870
+ } catch (error) {
4507
4871
  return {
4508
4872
  success: false,
4509
- message: `Failed to checkout remote branch '${ref}'`
4873
+ message: formatCheckoutError(error, `Failed to checkout remote branch '${ref}'`)
4510
4874
  };
4511
4875
  }
4512
4876
  } else {
@@ -4520,10 +4884,10 @@ async function runGitCheckout(cwd, ref, options) {
4520
4884
  const branch = branchMatch?.[1] ?? ref;
4521
4885
  const message = stdout || stderr || `Checkout to ${ref} completed`;
4522
4886
  return { success: true, message, branch };
4523
- } catch {
4887
+ } catch (error) {
4524
4888
  return {
4525
4889
  success: false,
4526
- message: `Failed to checkout '${ref}'`
4890
+ message: formatCheckoutError(error, `Failed to checkout '${ref}'`)
4527
4891
  };
4528
4892
  }
4529
4893
  }
@@ -4538,23 +4902,42 @@ async function runGitCreateBranch(cwd, branchName, options) {
4538
4902
  }
4539
4903
  async function runGitListBranches(cwd) {
4540
4904
  const { stdout: localOutput } = await runGit(cwd, ["branch", "--list"]);
4905
+ const { stdout: localVerboseOutput } = await runGit(cwd, ["branch", "--list", "-vv"]);
4541
4906
  const { stdout: remoteOutput } = await runGit(cwd, ["branch", "-r"]);
4542
4907
  const branches = [];
4543
4908
  let current = "";
4909
+ const linkedWorktreePathsByBranch = /* @__PURE__ */ new Map();
4910
+ const localVerboseLines = localVerboseOutput.split("\n").filter((line) => line.trim());
4911
+ for (const line of localVerboseLines) {
4912
+ const normalizedLine = line.replace(/^[*+ ]\s+/, "");
4913
+ const branchMatch = normalizedLine.match(/^([^\s]+)\s+/);
4914
+ const worktreeMatch = line.match(/\((.+?)\)\s/);
4915
+ if (!branchMatch?.[1] || !worktreeMatch?.[1]) {
4916
+ continue;
4917
+ }
4918
+ const worktreePath = worktreeMatch[1];
4919
+ if (worktreePath.startsWith("/") || worktreePath.startsWith("~")) {
4920
+ linkedWorktreePathsByBranch.set(branchMatch[1], worktreePath);
4921
+ }
4922
+ }
4544
4923
  const localLines = localOutput.split("\n").filter((line) => line.trim());
4545
4924
  for (const line of localLines) {
4546
4925
  const isCurrent = line.startsWith("*");
4547
- const name = line.replace(/^\*?\s+/, "").trim();
4926
+ const name = line.replace(/^[*+ ]\s+/, "").trim();
4548
4927
  if (name.startsWith("(HEAD detached")) {
4549
4928
  if (isCurrent) {
4550
4929
  current = "";
4551
4930
  }
4552
4931
  continue;
4553
4932
  }
4933
+ if (linkedWorktreePathsByBranch.has(name) && !isCurrent) {
4934
+ continue;
4935
+ }
4554
4936
  branches.push({
4555
4937
  name,
4556
4938
  isRemote: false,
4557
- isCurrent
4939
+ isCurrent,
4940
+ linkedWorktreePath: linkedWorktreePathsByBranch.get(name)
4558
4941
  });
4559
4942
  if (isCurrent) {
4560
4943
  current = name;
@@ -4602,6 +4985,23 @@ async function resolveRemoteBranchTarget(cwd, mode) {
4602
4985
  return null;
4603
4986
  }
4604
4987
  }
4988
+ async function getCurrentBranchName(cwd) {
4989
+ try {
4990
+ const { stdout } = await runGit(cwd, ["branch", "--show-current"]);
4991
+ const branch = stdout.trim();
4992
+ return branch || void 0;
4993
+ } catch {
4994
+ return void 0;
4995
+ }
4996
+ }
4997
+ function isPushUpToDate(stdout, stderr) {
4998
+ return /Everything up-to-date/i.test(`${stdout}
4999
+ ${stderr}`);
5000
+ }
5001
+ function isPullUpToDate(stdout, stderr) {
5002
+ return /Already up[ -]to[ -]date\.?/i.test(`${stdout}
5003
+ ${stderr}`);
5004
+ }
4605
5005
  async function getPreferredRemote(cwd) {
4606
5006
  try {
4607
5007
  const { stdout } = await runGit(cwd, ["remote"]);
@@ -4986,7 +5386,7 @@ var init_context_builder = __esm({
4986
5386
  });
4987
5387
 
4988
5388
  // packages/server/src/terminal/pty-host.ts
4989
- import { chmodSync, existsSync as existsSync4, statSync } from "node:fs";
5389
+ import { chmodSync as chmodSync2, existsSync as existsSync4, statSync } from "node:fs";
4990
5390
  import { createRequire } from "node:module";
4991
5391
  import path6 from "node:path";
4992
5392
  function ensureNodePtySpawnHelperExecutable(deps = {}) {
@@ -4998,7 +5398,7 @@ function ensureNodePtySpawnHelperExecutable(deps = {}) {
4998
5398
  const resolve4 = deps.resolve ?? ((id) => require2.resolve(id));
4999
5399
  const fileExists = deps.existsSync ?? existsSync4;
5000
5400
  const stat7 = deps.statSync ?? statSync;
5001
- const chmod = deps.chmodSync ?? chmodSync;
5401
+ const chmod = deps.chmodSync ?? chmodSync2;
5002
5402
  let packageJsonPath;
5003
5403
  try {
5004
5404
  packageJsonPath = resolve4(NODE_PTY_PKG);
@@ -7033,14 +7433,14 @@ var init_manager3 = __esm({
7033
7433
  // packages/server/src/workspace/validator.ts
7034
7434
  import { constants } from "fs";
7035
7435
  import { access, stat as stat5 } from "fs/promises";
7036
- async function validatePath(path9) {
7436
+ async function validatePath(path10) {
7037
7437
  try {
7038
- const stats = await stat5(path9);
7438
+ const stats = await stat5(path10);
7039
7439
  if (!stats.isDirectory()) {
7040
7440
  return { valid: false, error: "Path is not a directory" };
7041
7441
  }
7042
- await access(path9, constants.R_OK);
7043
- await access(path9, constants.W_OK);
7442
+ await access(path10, constants.R_OK);
7443
+ await access(path10, constants.W_OK);
7044
7444
  return { valid: true };
7045
7445
  } catch (error) {
7046
7446
  if (error.code === "ENOENT") {
@@ -7057,8 +7457,8 @@ var init_validator = __esm({
7057
7457
  "packages/server/src/workspace/validator.ts"() {
7058
7458
  "use strict";
7059
7459
  WorkspaceValidator = class {
7060
- async validate(path9) {
7061
- const result = await validatePath(path9);
7460
+ async validate(path10) {
7461
+ const result = await validatePath(path10);
7062
7462
  if (!result.valid) {
7063
7463
  throw new Error(`Invalid workspace path: ${result.error}`);
7064
7464
  }
@@ -7070,12 +7470,12 @@ var init_validator = __esm({
7070
7470
  // packages/server/src/fs/gitignore.ts
7071
7471
  import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
7072
7472
  import ignore from "ignore";
7073
- import { join as join4, relative } from "path";
7074
- function normalizePath(path9) {
7075
- return path9.replace(/\\/g, "/");
7473
+ import { join as join4, relative as relative3 } from "path";
7474
+ function normalizePath(path10) {
7475
+ return path10.replace(/\\/g, "/");
7076
7476
  }
7077
- function relativeToRoot(rootPath, path9) {
7078
- return normalizePath(relative(rootPath, path9));
7477
+ function relativeToRoot(rootPath, path10) {
7478
+ return normalizePath(relative3(rootPath, path10));
7079
7479
  }
7080
7480
  function isDefaultTreeIgnored(name) {
7081
7481
  return name.startsWith(".") || name === "node_modules" || name === ".git";
@@ -7083,11 +7483,11 @@ function isDefaultTreeIgnored(name) {
7083
7483
  function isAlwaysTreeIgnored(name) {
7084
7484
  return name === "node_modules" || name === ".git";
7085
7485
  }
7086
- function isIgnoredByGitignore(ig, path9) {
7087
- if (!path9 || path9.startsWith("..")) {
7486
+ function isIgnoredByGitignore(ig, path10) {
7487
+ if (!path10 || path10.startsWith("..")) {
7088
7488
  return false;
7089
7489
  }
7090
- return ig.ignores(path9) || ig.ignores(`${path9}/`);
7490
+ return ig.ignores(path10) || ig.ignores(`${path10}/`);
7091
7491
  }
7092
7492
  function createGitignoreFilter(rootPath, dirPath) {
7093
7493
  const gitignorePath = join4(rootPath, ".gitignore");
@@ -7107,16 +7507,16 @@ function createGitignoreFilter(rootPath, dirPath) {
7107
7507
  function createWatcherIgnoreFilter(rootPath) {
7108
7508
  const gitignorePath = join4(rootPath, ".gitignore");
7109
7509
  if (!existsSync5(gitignorePath)) {
7110
- return (path9) => DEFAULT_WATCHER_IGNORED_PATTERNS.some((p) => p.test(normalizePath(path9)));
7510
+ return (path10) => DEFAULT_WATCHER_IGNORED_PATTERNS.some((p) => p.test(normalizePath(path10)));
7111
7511
  }
7112
7512
  const gitignoreContent = readFileSync5(gitignorePath, "utf-8");
7113
7513
  const ig = ignore().add(gitignoreContent);
7114
- return (path9) => {
7115
- const normalizedPath = normalizePath(path9);
7514
+ return (path10) => {
7515
+ const normalizedPath = normalizePath(path10);
7116
7516
  if (DEFAULT_WATCHER_IGNORED_PATTERNS.some((p) => p.test(normalizedPath))) {
7117
7517
  return true;
7118
7518
  }
7119
- const relativePath = relativeToRoot(rootPath, path9);
7519
+ const relativePath = relativeToRoot(rootPath, path10);
7120
7520
  return isIgnoredByGitignore(ig, relativePath);
7121
7521
  };
7122
7522
  }
@@ -7236,6 +7636,11 @@ var init_manager4 = __esm({
7236
7636
  new WorkspaceWatcher(workspaceId, rootPath, this.deps.broadcaster)
7237
7637
  );
7238
7638
  }
7639
+ hydrateWatchers() {
7640
+ for (const workspace of this.list()) {
7641
+ this.startWatcher(workspace.id, workspace.path);
7642
+ }
7643
+ }
7239
7644
  updateUiState(workspaceId, uiState) {
7240
7645
  const workspace = this.get(workspaceId);
7241
7646
  if (!workspace) {
@@ -7270,6 +7675,7 @@ var init_manager4 = __esm({
7270
7675
  workspaceId: existing.id,
7271
7676
  patch: { lastActiveAt: Date.now() }
7272
7677
  });
7678
+ this.deps.autoFetch?.triggerOpenTimeFetch(existing.id);
7273
7679
  return existing;
7274
7680
  }
7275
7681
  const workspace = {
@@ -7307,6 +7713,7 @@ var init_manager4 = __esm({
7307
7713
  patch: workspace
7308
7714
  });
7309
7715
  this.startWatcher(workspace.id, workspace.path);
7716
+ this.deps.autoFetch?.triggerOpenTimeFetch(workspace.id);
7310
7717
  return workspace;
7311
7718
  }
7312
7719
  /**
@@ -7391,12 +7798,12 @@ var init_manager4 = __esm({
7391
7798
  * @param path - Workspace path
7392
7799
  * @returns Workspace or undefined
7393
7800
  */
7394
- getByPath(path9) {
7801
+ getByPath(path10) {
7395
7802
  const row = this.deps.db.prepare(
7396
7803
  `SELECT id, path, target_runtime, wsl_distro, opened_at, last_active_at, ui_state
7397
7804
  FROM workspaces
7398
7805
  WHERE path = ?`
7399
- ).get(path9);
7806
+ ).get(path10);
7400
7807
  if (!row) return void 0;
7401
7808
  return {
7402
7809
  id: row.id,
@@ -7417,13 +7824,87 @@ var init_manager4 = __esm({
7417
7824
  const now = Date.now();
7418
7825
  this.deps.db.prepare("UPDATE workspaces SET last_active_at = ? WHERE id = ?").run(now, workspaceId);
7419
7826
  }
7827
+ recordFetch(workspaceId) {
7828
+ this.deps.autoFetch?.recordSuccess(workspaceId);
7829
+ }
7420
7830
  };
7421
7831
  }
7422
7832
  });
7423
7833
 
7424
- // packages/server/src/ws/fencing.ts
7425
- var DEFAULT_OPTIONS, FencingManager;
7426
- var init_fencing = __esm({
7834
+ // packages/server/src/ws/dispatch.ts
7835
+ function registerCommand(op, schema, handler) {
7836
+ handlers.set(op, handler);
7837
+ schemas.set(op, schema);
7838
+ }
7839
+ async function dispatch(msg, ctx, clientId) {
7840
+ const handler = handlers.get(msg.op);
7841
+ if (!handler) {
7842
+ return {
7843
+ kind: "result",
7844
+ id: msg.id,
7845
+ ok: false,
7846
+ error: {
7847
+ code: "unknown_op",
7848
+ message: `Unknown operation: ${msg.op}`
7849
+ }
7850
+ };
7851
+ }
7852
+ try {
7853
+ const schema = schemas.get(msg.op);
7854
+ let args = msg.args;
7855
+ if (schema) {
7856
+ args = schema.parse(msg.args);
7857
+ }
7858
+ const data = await handler(args, ctx, clientId);
7859
+ return {
7860
+ kind: "result",
7861
+ id: msg.id,
7862
+ ok: true,
7863
+ data
7864
+ };
7865
+ } catch (error) {
7866
+ const normalizedError = normalizeError(error);
7867
+ return {
7868
+ kind: "result",
7869
+ id: msg.id,
7870
+ ok: false,
7871
+ error: normalizedError
7872
+ };
7873
+ }
7874
+ }
7875
+ function normalizeError(error) {
7876
+ const candidate = error;
7877
+ if (candidate.name === "ZodError") {
7878
+ return {
7879
+ code: "validation_error",
7880
+ message: "Invalid arguments",
7881
+ details: candidate.errors
7882
+ };
7883
+ }
7884
+ if (candidate.code) {
7885
+ return {
7886
+ code: candidate.code,
7887
+ message: candidate.message ?? String(candidate.code),
7888
+ details: candidate.details
7889
+ };
7890
+ }
7891
+ return {
7892
+ code: "internal_error",
7893
+ message: candidate.message || "An internal error occurred"
7894
+ };
7895
+ }
7896
+ var handlers, schemas;
7897
+ var init_dispatch = __esm({
7898
+ "packages/server/src/ws/dispatch.ts"() {
7899
+ "use strict";
7900
+ handlers = /* @__PURE__ */ new Map();
7901
+ schemas = /* @__PURE__ */ new Map();
7902
+ }
7903
+ });
7904
+
7905
+ // packages/server/src/ws/fencing.ts
7906
+ var DEFAULT_OPTIONS, FencingManager;
7907
+ var init_fencing = __esm({
7427
7908
  "packages/server/src/ws/fencing.ts"() {
7428
7909
  "use strict";
7429
7910
  DEFAULT_OPTIONS = {
@@ -7593,127 +8074,6 @@ var init_fencing = __esm({
7593
8074
  }
7594
8075
  });
7595
8076
 
7596
- // packages/server/src/ws/dispatch.ts
7597
- async function debounce(key, op, windowMs) {
7598
- let entry = debounceMap.get(key);
7599
- if (entry) {
7600
- clearTimeout(entry.timer);
7601
- entry.op = op;
7602
- } else {
7603
- let resolve4;
7604
- let reject;
7605
- const promise = new Promise((res, rej) => {
7606
- resolve4 = res;
7607
- reject = rej;
7608
- });
7609
- entry = {
7610
- timer: void 0,
7611
- promise,
7612
- resolve: resolve4,
7613
- reject,
7614
- op
7615
- };
7616
- debounceMap.set(key, entry);
7617
- }
7618
- entry.timer = setTimeout(async () => {
7619
- debounceMap.delete(key);
7620
- try {
7621
- const result = await entry.op();
7622
- entry.resolve(result);
7623
- } catch (err) {
7624
- entry.reject(err);
7625
- }
7626
- }, windowMs);
7627
- return entry.promise;
7628
- }
7629
- function registerCommand(op, schema, handler) {
7630
- handlers.set(op, handler);
7631
- schemas.set(op, schema);
7632
- }
7633
- async function dispatch(msg, ctx, clientId) {
7634
- const handler = handlers.get(msg.op);
7635
- if (!handler) {
7636
- return {
7637
- kind: "result",
7638
- id: msg.id,
7639
- ok: false,
7640
- error: {
7641
- code: "unknown_op",
7642
- message: `Unknown operation: ${msg.op}`
7643
- }
7644
- };
7645
- }
7646
- try {
7647
- const schema = schemas.get(msg.op);
7648
- let args = msg.args;
7649
- if (schema) {
7650
- args = schema.parse(msg.args);
7651
- }
7652
- const data = await executeWithDebounce(msg.op, args, ctx, clientId);
7653
- return {
7654
- kind: "result",
7655
- id: msg.id,
7656
- ok: true,
7657
- data
7658
- };
7659
- } catch (error) {
7660
- const normalizedError = normalizeError(error);
7661
- return {
7662
- kind: "result",
7663
- id: msg.id,
7664
- ok: false,
7665
- error: normalizedError
7666
- };
7667
- }
7668
- }
7669
- async function executeWithDebounce(op, args, ctx, clientId) {
7670
- const handler = handlers.get(op);
7671
- if (op === "git.status") {
7672
- const workspaceId = getWorkspaceId(args);
7673
- const key = workspaceId ? `git.status:${workspaceId}` : op;
7674
- return debounce(key, () => handler(args, ctx, clientId), DEBOUNCE_GIT_STATUS_MS);
7675
- }
7676
- return handler(args, ctx, clientId);
7677
- }
7678
- function getWorkspaceId(args) {
7679
- if (typeof args !== "object" || args === null || !("workspaceId" in args)) {
7680
- return void 0;
7681
- }
7682
- const workspaceId = args.workspaceId;
7683
- return typeof workspaceId === "string" ? workspaceId : void 0;
7684
- }
7685
- function normalizeError(error) {
7686
- const candidate = error;
7687
- if (candidate.name === "ZodError") {
7688
- return {
7689
- code: "validation_error",
7690
- message: "Invalid arguments",
7691
- details: candidate.errors
7692
- };
7693
- }
7694
- if (candidate.code) {
7695
- return {
7696
- code: candidate.code,
7697
- message: candidate.message ?? String(candidate.code),
7698
- details: candidate.details
7699
- };
7700
- }
7701
- return {
7702
- code: "internal_error",
7703
- message: candidate.message || "An internal error occurred"
7704
- };
7705
- }
7706
- var handlers, schemas, debounceMap, DEBOUNCE_GIT_STATUS_MS;
7707
- var init_dispatch = __esm({
7708
- "packages/server/src/ws/dispatch.ts"() {
7709
- "use strict";
7710
- handlers = /* @__PURE__ */ new Map();
7711
- schemas = /* @__PURE__ */ new Map();
7712
- debounceMap = /* @__PURE__ */ new Map();
7713
- DEBOUNCE_GIT_STATUS_MS = 500;
7714
- }
7715
- });
7716
-
7717
8077
  // packages/server/src/commands/terminal.ts
7718
8078
  import { basename } from "node:path";
7719
8079
  import { z as z5 } from "zod";
@@ -8562,6 +8922,7 @@ var init_hub = __esm({
8562
8922
  handleClose(client) {
8563
8923
  this.clients.delete(client.id);
8564
8924
  this.discardPendingBinaryWaiters(client.id);
8925
+ this.deps.commandContext?.autoFetch.unregisterViewer(client.id);
8565
8926
  }
8566
8927
  /**
8567
8928
  * Takeover: Force close existing writer and accept new one
@@ -8754,7 +9115,7 @@ var init_hub = __esm({
8754
9115
 
8755
9116
  // packages/server/src/commands/workspace.ts
8756
9117
  import { readdir as readdir2 } from "node:fs/promises";
8757
- import { homedir as homedir3 } from "node:os";
9118
+ import { homedir as homedir2 } from "node:os";
8758
9119
  import { join as join5 } from "node:path";
8759
9120
  import { z as z6 } from "zod";
8760
9121
  var init_workspace = __esm({
@@ -8770,7 +9131,7 @@ var init_workspace = __esm({
8770
9131
  path: z6.string().optional()
8771
9132
  }),
8772
9133
  async (args) => {
8773
- const basePath = args.path || homedir3();
9134
+ const basePath = args.path || homedir2();
8774
9135
  const entries = await readdir2(basePath, { withFileTypes: true });
8775
9136
  const directories = entries.filter((entry) => entry.isDirectory()).map((entry) => ({
8776
9137
  name: entry.name,
@@ -8843,6 +9204,35 @@ var init_workspace = __esm({
8843
9204
  }
8844
9205
  });
8845
9206
 
9207
+ // packages/server/src/commands/workspace-activity.ts
9208
+ import { z as z7 } from "zod";
9209
+ var init_workspace_activity = __esm({
9210
+ "packages/server/src/commands/workspace-activity.ts"() {
9211
+ "use strict";
9212
+ init_dispatch();
9213
+ registerCommand(
9214
+ "workspace.activate",
9215
+ z7.object({
9216
+ workspaceId: z7.string()
9217
+ }),
9218
+ async (args, ctx, clientId) => {
9219
+ if (!clientId) {
9220
+ return {};
9221
+ }
9222
+ ctx.autoFetch.registerViewer(clientId, args.workspaceId);
9223
+ return {};
9224
+ }
9225
+ );
9226
+ registerCommand("workspace.deactivate", z7.object({}), async (_args, ctx, clientId) => {
9227
+ if (!clientId) {
9228
+ return {};
9229
+ }
9230
+ ctx.autoFetch.unregisterViewer(clientId);
9231
+ return {};
9232
+ });
9233
+ }
9234
+ });
9235
+
8846
9236
  // packages/server/src/provider-runtime/runtime-status.ts
8847
9237
  function canAutoInstall(provider, platform, missingCommands, missingPrerequisites, availableCommands) {
8848
9238
  const strategies = provider.install.strategies[platform] ?? [];
@@ -8937,7 +9327,7 @@ var init_runtime_status = __esm({
8937
9327
  });
8938
9328
 
8939
9329
  // packages/server/src/commands/session.ts
8940
- import { z as z7 } from "zod";
9330
+ import { z as z8 } from "zod";
8941
9331
  function getProviderFromRegistry(providerId, registry) {
8942
9332
  return registry.find((provider) => provider.id === providerId);
8943
9333
  }
@@ -8948,8 +9338,8 @@ var init_session = __esm({
8948
9338
  init_dispatch();
8949
9339
  registerCommand(
8950
9340
  "session.list",
8951
- z7.object({
8952
- workspaceId: z7.string()
9341
+ z8.object({
9342
+ workspaceId: z8.string()
8953
9343
  }),
8954
9344
  async (args, ctx) => {
8955
9345
  return ctx.sessionMgr.getForWorkspace(args.workspaceId);
@@ -8957,10 +9347,10 @@ var init_session = __esm({
8957
9347
  );
8958
9348
  registerCommand(
8959
9349
  "session.create",
8960
- z7.object({
8961
- workspaceId: z7.string(),
8962
- providerId: z7.string(),
8963
- draft: z7.string().optional()
9350
+ z8.object({
9351
+ workspaceId: z8.string(),
9352
+ providerId: z8.string(),
9353
+ draft: z8.string().optional()
8964
9354
  }),
8965
9355
  async (args, ctx) => {
8966
9356
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
@@ -8994,8 +9384,8 @@ var init_session = __esm({
8994
9384
  );
8995
9385
  registerCommand(
8996
9386
  "session.stop",
8997
- z7.object({
8998
- sessionId: z7.string()
9387
+ z8.object({
9388
+ sessionId: z8.string()
8999
9389
  }),
9000
9390
  async (args, ctx) => {
9001
9391
  await ctx.sessionMgr.stop(args.sessionId);
@@ -9003,8 +9393,8 @@ var init_session = __esm({
9003
9393
  );
9004
9394
  registerCommand(
9005
9395
  "session.remove",
9006
- z7.object({
9007
- sessionId: z7.string()
9396
+ z8.object({
9397
+ sessionId: z8.string()
9008
9398
  }),
9009
9399
  async (args, ctx) => {
9010
9400
  const session = ctx.sessionMgr.get(args.sessionId);
@@ -9022,7 +9412,7 @@ var init_session = __esm({
9022
9412
 
9023
9413
  // packages/server/src/fs/tree.ts
9024
9414
  import { readdir as readdir3, stat as stat6 } from "fs/promises";
9025
- import { join as join6, relative as relative2 } from "path";
9415
+ import { join as join6, relative as relative4 } from "path";
9026
9416
  async function readTree(rootPath, subdir) {
9027
9417
  const targetPath = subdir ? join6(rootPath, subdir) : rootPath;
9028
9418
  const filter = createGitignoreFilter(rootPath, targetPath);
@@ -9033,7 +9423,7 @@ async function readTree(rootPath, subdir) {
9033
9423
  continue;
9034
9424
  }
9035
9425
  const fullPath = join6(targetPath, entry.name);
9036
- const relPath = relative2(rootPath, fullPath);
9426
+ const relPath = relative4(rootPath, fullPath);
9037
9427
  if (entry.isDirectory()) {
9038
9428
  nodes.push({
9039
9429
  name: entry.name,
@@ -9077,7 +9467,7 @@ async function searchFiles(rootPath, query, limit = 10) {
9077
9467
  filteredEntries.sort((a, b) => a.name.localeCompare(b.name));
9078
9468
  for (const entry of filteredEntries) {
9079
9469
  const fullPath = join6(dirPath, entry.name);
9080
- const relPath = relative2(rootPath, fullPath);
9470
+ const relPath = relative4(rootPath, fullPath);
9081
9471
  if (entry.isDirectory()) {
9082
9472
  await walk(fullPath);
9083
9473
  continue;
@@ -9169,7 +9559,7 @@ var init_tree = __esm({
9169
9559
  });
9170
9560
 
9171
9561
  // packages/server/src/commands/file.ts
9172
- import { z as z8 } from "zod";
9562
+ import { z as z9 } from "zod";
9173
9563
  var init_file = __esm({
9174
9564
  "packages/server/src/commands/file.ts"() {
9175
9565
  "use strict";
@@ -9178,9 +9568,9 @@ var init_file = __esm({
9178
9568
  init_dispatch();
9179
9569
  registerCommand(
9180
9570
  "file.readTree",
9181
- z8.object({
9182
- workspaceId: z8.string(),
9183
- subPath: z8.string().optional()
9571
+ z9.object({
9572
+ workspaceId: z9.string(),
9573
+ subPath: z9.string().optional()
9184
9574
  }),
9185
9575
  async (args, ctx) => {
9186
9576
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
@@ -9192,10 +9582,10 @@ var init_file = __esm({
9192
9582
  );
9193
9583
  registerCommand(
9194
9584
  "file.search",
9195
- z8.object({
9196
- workspaceId: z8.string(),
9197
- query: z8.string(),
9198
- limit: z8.number().int().positive().max(50).optional()
9585
+ z9.object({
9586
+ workspaceId: z9.string(),
9587
+ query: z9.string(),
9588
+ limit: z9.number().int().positive().max(50).optional()
9199
9589
  }),
9200
9590
  async (args, ctx) => {
9201
9591
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
@@ -9207,9 +9597,9 @@ var init_file = __esm({
9207
9597
  );
9208
9598
  registerCommand(
9209
9599
  "file.read",
9210
- z8.object({
9211
- workspaceId: z8.string(),
9212
- path: z8.string()
9600
+ z9.object({
9601
+ workspaceId: z9.string(),
9602
+ path: z9.string()
9213
9603
  }),
9214
9604
  async (args, ctx) => {
9215
9605
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
@@ -9221,9 +9611,9 @@ var init_file = __esm({
9221
9611
  );
9222
9612
  registerCommand(
9223
9613
  "file.create",
9224
- z8.object({
9225
- workspaceId: z8.string(),
9226
- path: z8.string()
9614
+ z9.object({
9615
+ workspaceId: z9.string(),
9616
+ path: z9.string()
9227
9617
  }),
9228
9618
  async (args, ctx) => {
9229
9619
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
@@ -9241,9 +9631,9 @@ var init_file = __esm({
9241
9631
  );
9242
9632
  registerCommand(
9243
9633
  "file.mkdir",
9244
- z8.object({
9245
- workspaceId: z8.string(),
9246
- path: z8.string()
9634
+ z9.object({
9635
+ workspaceId: z9.string(),
9636
+ path: z9.string()
9247
9637
  }),
9248
9638
  async (args, ctx) => {
9249
9639
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
@@ -9261,9 +9651,9 @@ var init_file = __esm({
9261
9651
  );
9262
9652
  registerCommand(
9263
9653
  "file.delete",
9264
- z8.object({
9265
- workspaceId: z8.string(),
9266
- path: z8.string()
9654
+ z9.object({
9655
+ workspaceId: z9.string(),
9656
+ path: z9.string()
9267
9657
  }),
9268
9658
  async (args, ctx) => {
9269
9659
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
@@ -9281,11 +9671,11 @@ var init_file = __esm({
9281
9671
  );
9282
9672
  registerCommand(
9283
9673
  "file.write",
9284
- z8.object({
9285
- workspaceId: z8.string(),
9286
- path: z8.string(),
9287
- content: z8.string(),
9288
- baseHash: z8.string().optional()
9674
+ z9.object({
9675
+ workspaceId: z9.string(),
9676
+ path: z9.string(),
9677
+ content: z9.string(),
9678
+ baseHash: z9.string().optional()
9289
9679
  // For conflict detection
9290
9680
  }),
9291
9681
  async (args, ctx) => {
@@ -9344,11 +9734,11 @@ async function getUntrackedFileDiff(cwd, filePath) {
9344
9734
  await rm5(tempDir, { recursive: true, force: true });
9345
9735
  }
9346
9736
  }
9347
- async function getFileDiff(cwd, path9, staged = false) {
9348
- if (!staged && !await isTrackedPath(cwd, path9)) {
9349
- return getUntrackedFileDiff(cwd, path9);
9737
+ async function getFileDiff(cwd, path10, staged = false) {
9738
+ if (!staged && !await isTrackedPath(cwd, path10)) {
9739
+ return getUntrackedFileDiff(cwd, path10);
9350
9740
  }
9351
- const args = staged ? ["diff", "--staged", "--", path9] : ["diff", "--", path9];
9741
+ const args = staged ? ["diff", "--staged", "--", path10] : ["diff", "--", path10];
9352
9742
  const result = await runGit(cwd, args);
9353
9743
  return result.stdout;
9354
9744
  }
@@ -9359,8 +9749,7 @@ var init_diff = __esm({
9359
9749
  }
9360
9750
  });
9361
9751
 
9362
- // packages/server/src/commands/git.ts
9363
- import { z as z9 } from "zod";
9752
+ // packages/server/src/commands/git-events.ts
9364
9753
  function emitGitStateChanged(ctx, workspaceId, options) {
9365
9754
  ctx.eventBus.emit({
9366
9755
  type: "git.state.changed",
@@ -9370,21 +9759,38 @@ function emitGitStateChanged(ctx, workspaceId, options) {
9370
9759
  worktreeChanged: options?.worktreeChanged
9371
9760
  });
9372
9761
  }
9373
- var gitHttpAuthSchema;
9762
+ var init_git_events = __esm({
9763
+ "packages/server/src/commands/git-events.ts"() {
9764
+ "use strict";
9765
+ }
9766
+ });
9767
+
9768
+ // packages/server/src/commands/git.ts
9769
+ import { z as z10 } from "zod";
9770
+ async function runGitNetworkOperation(ctx, workspaceId, op) {
9771
+ if (!ctx.autoFetch?.runExclusive) {
9772
+ return op();
9773
+ }
9774
+ return ctx.autoFetch.runExclusive(workspaceId, op);
9775
+ }
9776
+ var gitHttpAuthSchema, gitCommitRevisionSchema, GIT_BACKGROUND_FETCH_TIMEOUT_MS;
9374
9777
  var init_git2 = __esm({
9375
9778
  "packages/server/src/commands/git.ts"() {
9376
9779
  "use strict";
9377
9780
  init_cli();
9378
9781
  init_diff();
9379
9782
  init_dispatch();
9380
- gitHttpAuthSchema = z9.object({
9381
- username: z9.string(),
9382
- password: z9.string()
9783
+ init_git_events();
9784
+ gitHttpAuthSchema = z10.object({
9785
+ username: z10.string(),
9786
+ password: z10.string()
9383
9787
  });
9788
+ gitCommitRevisionSchema = z10.string().regex(/^[0-9a-fA-F]{7,64}$/, "Invalid git commit revision");
9789
+ GIT_BACKGROUND_FETCH_TIMEOUT_MS = 30 * 1e3;
9384
9790
  registerCommand(
9385
9791
  "git.status",
9386
- z9.object({
9387
- workspaceId: z9.string()
9792
+ z10.object({
9793
+ workspaceId: z10.string()
9388
9794
  }),
9389
9795
  async (args, ctx) => {
9390
9796
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
@@ -9396,9 +9802,9 @@ var init_git2 = __esm({
9396
9802
  );
9397
9803
  registerCommand(
9398
9804
  "git.stage",
9399
- z9.object({
9400
- workspaceId: z9.string(),
9401
- paths: z9.array(z9.string())
9805
+ z10.object({
9806
+ workspaceId: z10.string(),
9807
+ paths: z10.array(z10.string())
9402
9808
  }),
9403
9809
  async (args, ctx) => {
9404
9810
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
@@ -9412,10 +9818,10 @@ var init_git2 = __esm({
9412
9818
  );
9413
9819
  registerCommand(
9414
9820
  "git.diff",
9415
- z9.object({
9416
- workspaceId: z9.string(),
9417
- path: z9.string(),
9418
- staged: z9.boolean().optional()
9821
+ z10.object({
9822
+ workspaceId: z10.string(),
9823
+ path: z10.string(),
9824
+ staged: z10.boolean().optional()
9419
9825
  }),
9420
9826
  async (args, ctx) => {
9421
9827
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
@@ -9427,11 +9833,43 @@ var init_git2 = __esm({
9427
9833
  };
9428
9834
  }
9429
9835
  );
9836
+ registerCommand(
9837
+ "git.log",
9838
+ z10.object({
9839
+ workspaceId: z10.string(),
9840
+ limit: z10.number().int().min(1).max(50).optional()
9841
+ }),
9842
+ async (args, ctx) => {
9843
+ const workspace = ctx.workspaceMgr.get(args.workspaceId);
9844
+ if (!workspace) {
9845
+ throw { code: "workspace_not_found", message: `Workspace not found: ${args.workspaceId}` };
9846
+ }
9847
+ return {
9848
+ entries: await getGitHistory(workspace.path, args.limit ?? 5)
9849
+ };
9850
+ }
9851
+ );
9852
+ registerCommand(
9853
+ "git.show",
9854
+ z10.object({
9855
+ workspaceId: z10.string(),
9856
+ sha: gitCommitRevisionSchema
9857
+ }),
9858
+ async (args, ctx) => {
9859
+ const workspace = ctx.workspaceMgr.get(args.workspaceId);
9860
+ if (!workspace) {
9861
+ throw { code: "workspace_not_found", message: `Workspace not found: ${args.workspaceId}` };
9862
+ }
9863
+ return {
9864
+ diff: await getGitCommitDiff(workspace.path, args.sha)
9865
+ };
9866
+ }
9867
+ );
9430
9868
  registerCommand(
9431
9869
  "git.unstage",
9432
- z9.object({
9433
- workspaceId: z9.string(),
9434
- paths: z9.array(z9.string())
9870
+ z10.object({
9871
+ workspaceId: z10.string(),
9872
+ paths: z10.array(z10.string())
9435
9873
  }),
9436
9874
  async (args, ctx) => {
9437
9875
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
@@ -9445,9 +9883,9 @@ var init_git2 = __esm({
9445
9883
  );
9446
9884
  registerCommand(
9447
9885
  "git.discard",
9448
- z9.object({
9449
- workspaceId: z9.string(),
9450
- paths: z9.array(z9.string())
9886
+ z10.object({
9887
+ workspaceId: z10.string(),
9888
+ paths: z10.array(z10.string())
9451
9889
  }),
9452
9890
  async (args, ctx) => {
9453
9891
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
@@ -9463,9 +9901,9 @@ var init_git2 = __esm({
9463
9901
  );
9464
9902
  registerCommand(
9465
9903
  "git.commit",
9466
- z9.object({
9467
- workspaceId: z9.string(),
9468
- message: z9.string()
9904
+ z10.object({
9905
+ workspaceId: z10.string(),
9906
+ message: z10.string()
9469
9907
  }),
9470
9908
  async (args, ctx) => {
9471
9909
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
@@ -9482,11 +9920,11 @@ var init_git2 = __esm({
9482
9920
  );
9483
9921
  registerCommand(
9484
9922
  "git.push",
9485
- z9.object({
9486
- workspaceId: z9.string(),
9487
- remote: z9.string().optional(),
9488
- branch: z9.string().optional(),
9489
- force: z9.boolean().optional(),
9923
+ z10.object({
9924
+ workspaceId: z10.string(),
9925
+ remote: z10.string().optional(),
9926
+ branch: z10.string().optional(),
9927
+ force: z10.boolean().optional(),
9490
9928
  auth: gitHttpAuthSchema.optional()
9491
9929
  }),
9492
9930
  async (args, ctx) => {
@@ -9494,12 +9932,16 @@ var init_git2 = __esm({
9494
9932
  if (!workspace) {
9495
9933
  throw { code: "workspace_not_found", message: `Workspace not found: ${args.workspaceId}` };
9496
9934
  }
9497
- const result = await runGitPush(workspace.path, {
9498
- remote: args.remote,
9499
- branch: args.branch,
9500
- force: args.force,
9501
- auth: args.auth
9502
- });
9935
+ const result = await runGitNetworkOperation(
9936
+ ctx,
9937
+ args.workspaceId,
9938
+ () => runGitPush(workspace.path, {
9939
+ remote: args.remote,
9940
+ branch: args.branch,
9941
+ force: args.force,
9942
+ auth: args.auth
9943
+ })
9944
+ );
9503
9945
  emitGitStateChanged(ctx, args.workspaceId, {
9504
9946
  branchChanged: true,
9505
9947
  worktreeChanged: true
@@ -9509,10 +9951,10 @@ var init_git2 = __esm({
9509
9951
  );
9510
9952
  registerCommand(
9511
9953
  "git.pull",
9512
- z9.object({
9513
- workspaceId: z9.string(),
9514
- remote: z9.string().optional(),
9515
- branch: z9.string().optional(),
9954
+ z10.object({
9955
+ workspaceId: z10.string(),
9956
+ remote: z10.string().optional(),
9957
+ branch: z10.string().optional(),
9516
9958
  auth: gitHttpAuthSchema.optional()
9517
9959
  }),
9518
9960
  async (args, ctx) => {
@@ -9520,11 +9962,16 @@ var init_git2 = __esm({
9520
9962
  if (!workspace) {
9521
9963
  throw { code: "workspace_not_found", message: `Workspace not found: ${args.workspaceId}` };
9522
9964
  }
9523
- const result = await runGitPull(workspace.path, {
9524
- remote: args.remote,
9525
- branch: args.branch,
9526
- auth: args.auth
9527
- });
9965
+ const result = await runGitNetworkOperation(
9966
+ ctx,
9967
+ args.workspaceId,
9968
+ () => runGitPull(workspace.path, {
9969
+ remote: args.remote,
9970
+ branch: args.branch,
9971
+ auth: args.auth
9972
+ })
9973
+ );
9974
+ ctx.workspaceMgr.recordFetch(args.workspaceId);
9528
9975
  emitGitStateChanged(ctx, args.workspaceId, {
9529
9976
  treeChanged: true,
9530
9977
  branchChanged: true,
@@ -9533,12 +9980,46 @@ var init_git2 = __esm({
9533
9980
  return result;
9534
9981
  }
9535
9982
  );
9983
+ registerCommand(
9984
+ "git.fetch",
9985
+ z10.object({
9986
+ workspaceId: z10.string(),
9987
+ remote: z10.string().optional(),
9988
+ prune: z10.boolean().optional(),
9989
+ auth: gitHttpAuthSchema.optional(),
9990
+ background: z10.boolean().optional()
9991
+ }),
9992
+ async (args, ctx, clientId) => {
9993
+ const workspace = ctx.workspaceMgr.get(args.workspaceId);
9994
+ if (!workspace) {
9995
+ throw { code: "workspace_not_found", message: `Workspace not found: ${args.workspaceId}` };
9996
+ }
9997
+ try {
9998
+ const isInternalBackgroundFetch = args.background === true && !clientId;
9999
+ const runFetch = () => runGitFetch(workspace.path, {
10000
+ remote: args.remote,
10001
+ prune: args.prune,
10002
+ auth: args.auth,
10003
+ timeoutMs: args.background ? GIT_BACKGROUND_FETCH_TIMEOUT_MS : void 0
10004
+ });
10005
+ const result = isInternalBackgroundFetch ? await runFetch() : await runGitNetworkOperation(ctx, args.workspaceId, runFetch);
10006
+ ctx.workspaceMgr.recordFetch(args.workspaceId);
10007
+ emitGitStateChanged(ctx, args.workspaceId, { branchChanged: true });
10008
+ return result;
10009
+ } catch (err) {
10010
+ if (args.background && err instanceof GitAuthError) {
10011
+ return { success: false, message: err.message, updatedRefs: [] };
10012
+ }
10013
+ throw err;
10014
+ }
10015
+ }
10016
+ );
9536
10017
  registerCommand(
9537
10018
  "git.checkout",
9538
- z9.object({
9539
- workspaceId: z9.string(),
9540
- ref: z9.string(),
9541
- createBranch: z9.boolean().optional()
10019
+ z10.object({
10020
+ workspaceId: z10.string(),
10021
+ ref: z10.string(),
10022
+ createBranch: z10.boolean().optional()
9542
10023
  }),
9543
10024
  async (args, ctx) => {
9544
10025
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
@@ -9560,10 +10041,10 @@ var init_git2 = __esm({
9560
10041
  );
9561
10042
  registerCommand(
9562
10043
  "git.branch",
9563
- z9.object({
9564
- workspaceId: z9.string(),
9565
- name: z9.string(),
9566
- startPoint: z9.string().optional()
10044
+ z10.object({
10045
+ workspaceId: z10.string(),
10046
+ name: z10.string(),
10047
+ startPoint: z10.string().optional()
9567
10048
  }),
9568
10049
  async (args, ctx) => {
9569
10050
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
@@ -9582,8 +10063,8 @@ var init_git2 = __esm({
9582
10063
  );
9583
10064
  registerCommand(
9584
10065
  "git.branches",
9585
- z9.object({
9586
- workspaceId: z9.string()
10066
+ z10.object({
10067
+ workspaceId: z10.string()
9587
10068
  }),
9588
10069
  async (args, ctx) => {
9589
10070
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
@@ -9597,8 +10078,8 @@ var init_git2 = __esm({
9597
10078
  });
9598
10079
 
9599
10080
  // packages/server/src/config/config-io.ts
9600
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync6, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "node:fs";
9601
- import { homedir as homedir4 } from "node:os";
10081
+ import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync6, renameSync, writeFileSync as writeFileSync3 } from "node:fs";
10082
+ import { homedir as homedir3 } from "node:os";
9602
10083
  import { basename as basename2, dirname as dirname4, join as join7 } from "node:path";
9603
10084
  function resolveConfigPath(configType) {
9604
10085
  if (configType === "codex") {
@@ -9606,14 +10087,18 @@ function resolveConfigPath(configType) {
9606
10087
  if (testHome && testHome.trim()) {
9607
10088
  return join7(testHome, "config.toml");
9608
10089
  }
9609
- return resolveCodexConfigPath();
10090
+ const codexHome = process.env.CODEX_HOME;
10091
+ if (codexHome && codexHome.trim()) {
10092
+ return join7(codexHome, "config.toml");
10093
+ }
10094
+ return join7(homedir3(), ".codex", "config.toml");
9610
10095
  }
9611
10096
  if (configType === "claude") {
9612
10097
  const testHome = process.env.CODER_STUDIO_CLAUDE_HOME;
9613
10098
  if (testHome && testHome.trim()) {
9614
10099
  return join7(testHome, "settings.json");
9615
10100
  }
9616
- return join7(homedir4(), ".claude", "settings.json");
10101
+ return join7(homedir3(), ".claude", "settings.json");
9617
10102
  }
9618
10103
  throw new Error(`Unknown config type: ${configType}`);
9619
10104
  }
@@ -9642,7 +10127,7 @@ function writeConfigFile(configType, content) {
9642
10127
  }
9643
10128
  const tempPath = `${configPath}.tmp`;
9644
10129
  writeFileSync3(tempPath, content, "utf-8");
9645
- renameSync2(tempPath, configPath);
10130
+ renameSync(tempPath, configPath);
9646
10131
  return { success: true, backupPath };
9647
10132
  } catch (error) {
9648
10133
  return {
@@ -9657,24 +10142,23 @@ function createBackup(filePath) {
9657
10142
  const ext = filePath.split(".").pop() ?? "";
9658
10143
  const base = basename2(filePath, `.${ext}`);
9659
10144
  const dir = dirname4(filePath);
9660
- const ts = formatTimestamp2(/* @__PURE__ */ new Date());
10145
+ const ts = formatTimestamp(/* @__PURE__ */ new Date());
9661
10146
  const backupPath = join7(dir, `${base}.bak.${ts}.${ext}`);
9662
10147
  writeFileSync3(backupPath, original, "utf-8");
9663
10148
  return backupPath;
9664
10149
  }
9665
- function formatTimestamp2(d) {
10150
+ function formatTimestamp(d) {
9666
10151
  const pad = (n) => String(n).padStart(2, "0");
9667
10152
  return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
9668
10153
  }
9669
10154
  var init_config_io = __esm({
9670
10155
  "packages/server/src/config/config-io.ts"() {
9671
10156
  "use strict";
9672
- init_codex_config_audit();
9673
10157
  }
9674
10158
  });
9675
10159
 
9676
10160
  // packages/server/src/commands/settings.ts
9677
- import { z as z10 } from "zod";
10161
+ import { z as z11 } from "zod";
9678
10162
  function flattenSettings(obj, prefix = "") {
9679
10163
  const result = {};
9680
10164
  for (const [key, value] of Object.entries(obj)) {
@@ -9687,7 +10171,7 @@ function flattenSettings(obj, prefix = "") {
9687
10171
  }
9688
10172
  return result;
9689
10173
  }
9690
- var EMPTY_CODEX_AUDIT, SettingsSchema;
10174
+ var SettingsSchema;
9691
10175
  var init_settings2 = __esm({
9692
10176
  "packages/server/src/commands/settings.ts"() {
9693
10177
  "use strict";
@@ -9697,34 +10181,27 @@ var init_settings2 = __esm({
9697
10181
  init_provider_config_repo();
9698
10182
  init_settings();
9699
10183
  init_dispatch();
9700
- EMPTY_CODEX_AUDIT = {
9701
- codex: {
9702
- configPath: "",
9703
- exists: false,
9704
- findings: []
9705
- }
9706
- };
9707
- SettingsSchema = z10.object({
9708
- defaultProviderId: z10.string().optional(),
9709
- notifications: z10.object({
9710
- enabled: z10.boolean().optional(),
9711
- soundEnabled: z10.boolean().optional(),
10184
+ SettingsSchema = z11.object({
10185
+ defaultProviderId: z11.string().optional(),
10186
+ notifications: z11.object({
10187
+ enabled: z11.boolean().optional(),
10188
+ soundEnabled: z11.boolean().optional(),
9712
10189
  // Legacy field — accepted for backward compat with older clients but
9713
10190
  // no longer surfaced in the UI. The web client now picks the channel
9714
10191
  // automatically based on workspace focus + page visibility.
9715
- onlyWhenBackgrounded: z10.boolean().optional()
10192
+ onlyWhenBackgrounded: z11.boolean().optional()
9716
10193
  }).optional(),
9717
- supervisor: z10.object({
9718
- evaluationTimeoutSec: z10.number().int().min(1).max(MAX_SUPERVISOR_EVALUATION_TIMEOUT_SEC).default(DEFAULT_SUPERVISOR_EVALUATION_TIMEOUT_SEC).optional()
10194
+ supervisor: z11.object({
10195
+ evaluationTimeoutSec: z11.number().int().min(1).max(MAX_SUPERVISOR_EVALUATION_TIMEOUT_SEC).default(DEFAULT_SUPERVISOR_EVALUATION_TIMEOUT_SEC).optional()
9719
10196
  }).optional(),
9720
- appearance: z10.object({
9721
- theme: z10.enum(["dark"]).optional(),
9722
- terminalRenderer: z10.enum(["standard", "compatibility"]).optional(),
9723
- locale: z10.enum(["zh", "en"]).optional()
10197
+ appearance: z11.object({
10198
+ theme: z11.enum(["dark"]).optional(),
10199
+ terminalRenderer: z11.enum(["standard", "compatibility"]).optional(),
10200
+ locale: z11.enum(["zh", "en"]).optional()
9724
10201
  }).optional(),
9725
10202
  providers: ProviderSettingsSchema.optional()
9726
10203
  });
9727
- registerCommand("settings.get", z10.object({}), async (_args, ctx) => {
10204
+ registerCommand("settings.get", z11.object({}), async (_args, ctx) => {
9728
10205
  const row = ctx.db.prepare("SELECT key, value FROM user_settings").all();
9729
10206
  const settings = {};
9730
10207
  for (const { key, value } of row) {
@@ -9748,11 +10225,6 @@ var init_settings2 = __esm({
9748
10225
  flattenSettings(sanitizeProviderLaunchConfig(config), `providers.${providerId}`)
9749
10226
  );
9750
10227
  }
9751
- try {
9752
- settings.externalConfigAudit = ctx.codexConfigAudit?.audit() ?? EMPTY_CODEX_AUDIT;
9753
- } catch {
9754
- settings.externalConfigAudit = null;
9755
- }
9756
10228
  if (Object.prototype.hasOwnProperty.call(settings, SUPERVISOR_EVALUATION_TIMEOUT_SETTING_KEY)) {
9757
10229
  settings[SUPERVISOR_EVALUATION_TIMEOUT_SETTING_KEY] = resolveSupervisorEvaluationTimeoutSec(
9758
10230
  settings[SUPERVISOR_EVALUATION_TIMEOUT_SETTING_KEY]
@@ -9762,7 +10234,7 @@ var init_settings2 = __esm({
9762
10234
  });
9763
10235
  registerCommand(
9764
10236
  "settings.update",
9765
- z10.object({
10237
+ z11.object({
9766
10238
  settings: SettingsSchema
9767
10239
  }),
9768
10240
  async (args, ctx) => {
@@ -9792,31 +10264,12 @@ var init_settings2 = __esm({
9792
10264
  };
9793
10265
  }
9794
10266
  );
9795
- registerCommand(
9796
- "settings.cleanupCodexConfig",
9797
- z10.object({
9798
- removeIds: z10.array(z10.enum(["toml_notify", "toml_codex_hooks"])).min(1)
9799
- }),
9800
- async (args, ctx) => {
9801
- const result = ctx.codexConfigAudit?.cleanup(args.removeIds) ?? {
9802
- removed: [],
9803
- backupPath: null,
9804
- noop: true
9805
- };
9806
- return {
9807
- removed: result.removed,
9808
- backupPath: result.backupPath,
9809
- noop: result.noop,
9810
- audit: ctx.codexConfigAudit?.audit() ?? EMPTY_CODEX_AUDIT
9811
- };
9812
- }
9813
- );
9814
10267
  registerCommand(
9815
10268
  "settings.previewCommand",
9816
- z10.object({
9817
- providerId: z10.string(),
10269
+ z11.object({
10270
+ providerId: z11.string(),
9818
10271
  config: ProviderLaunchConfigInputSchema,
9819
- workspacePath: z10.string().optional()
10272
+ workspacePath: z11.string().optional()
9820
10273
  }),
9821
10274
  async (args, ctx) => {
9822
10275
  const provider = ctx.providerRegistry.find((item) => item.id === args.providerId);
@@ -9837,8 +10290,8 @@ var init_settings2 = __esm({
9837
10290
  );
9838
10291
  registerCommand(
9839
10292
  "settings.readConfigFile",
9840
- z10.object({
9841
- configType: z10.enum(["codex", "claude"])
10293
+ z11.object({
10294
+ configType: z11.enum(["codex", "claude"])
9842
10295
  }),
9843
10296
  async (args) => {
9844
10297
  const result = readConfigFile(args.configType);
@@ -9847,9 +10300,9 @@ var init_settings2 = __esm({
9847
10300
  );
9848
10301
  registerCommand(
9849
10302
  "settings.writeConfigFile",
9850
- z10.object({
9851
- configType: z10.enum(["codex", "claude"]),
9852
- content: z10.string()
10303
+ z11.object({
10304
+ configType: z11.enum(["codex", "claude"]),
10305
+ content: z11.string()
9853
10306
  }),
9854
10307
  async (args) => {
9855
10308
  const result = writeConfigFile(args.configType, args.content);
@@ -9860,19 +10313,19 @@ var init_settings2 = __esm({
9860
10313
  });
9861
10314
 
9862
10315
  // packages/server/src/commands/provider.ts
9863
- import { z as z11 } from "zod";
10316
+ import { z as z12 } from "zod";
9864
10317
  var init_provider = __esm({
9865
10318
  "packages/server/src/commands/provider.ts"() {
9866
10319
  "use strict";
9867
10320
  init_runtime_status();
9868
10321
  init_dispatch();
9869
- registerCommand("provider.runtimeStatus", z11.object({}), async (_args, ctx) => {
10322
+ registerCommand("provider.runtimeStatus", z12.object({}), async (_args, ctx) => {
9870
10323
  return buildProviderRuntimeStatus(ctx.providerRegistry, ctx.providerRuntimeDeps);
9871
10324
  });
9872
10325
  registerCommand(
9873
10326
  "provider.install.start",
9874
- z11.object({
9875
- providerId: z11.string()
10327
+ z12.object({
10328
+ providerId: z12.string()
9876
10329
  }),
9877
10330
  async (args, ctx) => {
9878
10331
  if (!ctx.providerInstallMgr) {
@@ -9886,8 +10339,8 @@ var init_provider = __esm({
9886
10339
  );
9887
10340
  registerCommand(
9888
10341
  "provider.install.get",
9889
- z11.object({
9890
- jobId: z11.string()
10342
+ z12.object({
10343
+ jobId: z12.string()
9891
10344
  }),
9892
10345
  async (args, ctx) => {
9893
10346
  if (!ctx.providerInstallMgr) {
@@ -9910,29 +10363,29 @@ var init_provider = __esm({
9910
10363
  });
9911
10364
 
9912
10365
  // packages/server/src/commands/supervisor.ts
9913
- import { z as z12 } from "zod";
10366
+ import { z as z13 } from "zod";
9914
10367
  var supervisorObjectiveSchema, createSupervisorSchema, updateSupervisorSchema, sessionIdSchema, supervisorIdSchema;
9915
10368
  var init_supervisor2 = __esm({
9916
10369
  "packages/server/src/commands/supervisor.ts"() {
9917
10370
  "use strict";
9918
10371
  init_dispatch();
9919
- supervisorObjectiveSchema = z12.string().trim().min(1).max(4e3);
9920
- createSupervisorSchema = z12.object({
9921
- sessionId: z12.string(),
9922
- workspaceId: z12.string(),
10372
+ supervisorObjectiveSchema = z13.string().trim().min(1).max(4e3);
10373
+ createSupervisorSchema = z13.object({
10374
+ sessionId: z13.string(),
10375
+ workspaceId: z13.string(),
9923
10376
  objective: supervisorObjectiveSchema,
9924
- evaluatorProviderId: z12.string()
10377
+ evaluatorProviderId: z13.string()
9925
10378
  }).strict();
9926
- updateSupervisorSchema = z12.object({
9927
- id: z12.string(),
10379
+ updateSupervisorSchema = z13.object({
10380
+ id: z13.string(),
9928
10381
  objective: supervisorObjectiveSchema.optional(),
9929
- evaluatorProviderId: z12.string().optional()
10382
+ evaluatorProviderId: z13.string().optional()
9930
10383
  }).strict().refine(
9931
10384
  (input2) => input2.objective !== void 0 || input2.evaluatorProviderId !== void 0,
9932
10385
  "objective or evaluatorProviderId is required"
9933
10386
  );
9934
- sessionIdSchema = z12.object({ sessionId: z12.string() });
9935
- supervisorIdSchema = z12.object({ id: z12.string() });
10387
+ sessionIdSchema = z13.object({ sessionId: z13.string() });
10388
+ supervisorIdSchema = z13.object({ id: z13.string() });
9936
10389
  registerCommand("supervisor.create", createSupervisorSchema, async (args, ctx) => {
9937
10390
  return {
9938
10391
  supervisor: await ctx.supervisorMgr.create({
@@ -9975,6 +10428,10 @@ import path8 from "node:path";
9975
10428
  function normalizeWorktreePath(worktreePath) {
9976
10429
  return path8.resolve(worktreePath);
9977
10430
  }
10431
+ async function getGitCommonDirPath(repoPath) {
10432
+ const { stdout } = await runGit(repoPath, ["rev-parse", "--git-common-dir"]);
10433
+ return normalizeWorktreePath(path8.resolve(repoPath, stdout.trim()));
10434
+ }
9978
10435
  async function resolveWorktreePath(repoPath, worktreePath) {
9979
10436
  const normalizedRequested = normalizeWorktreePath(worktreePath);
9980
10437
  const worktrees = await listWorktrees(repoPath);
@@ -10060,19 +10517,30 @@ async function getWorktreeTree(worktreePath) {
10060
10517
  for (const line of lines) {
10061
10518
  const isDir = line.endsWith("/");
10062
10519
  const name = isDir ? line.slice(0, -1) : line;
10063
- const path9 = `${worktreePath}/${name}`;
10520
+ const path10 = `${worktreePath}/${name}`;
10064
10521
  nodes.push({
10065
10522
  name,
10066
- path: path9,
10523
+ path: path10,
10067
10524
  kind: isDir ? "dir" : "file"
10068
10525
  });
10069
10526
  }
10070
10527
  return nodes;
10071
10528
  }
10072
- async function createWorktree(repoPath, branch, path9) {
10073
- await runGit(repoPath, ["worktree", "add", path9, branch]);
10529
+ async function createWorktree(repoPath, branch, worktreePath) {
10530
+ let createArgs = ["worktree", "add", worktreePath, branch];
10531
+ try {
10532
+ await runGit(repoPath, ["rev-parse", "--verify", "--quiet", `${branch}^{commit}`]);
10533
+ } catch (error) {
10534
+ if (error instanceof GitError) {
10535
+ createArgs = ["worktree", "add", "-b", branch, worktreePath];
10536
+ } else {
10537
+ throw error;
10538
+ }
10539
+ }
10540
+ await runGit(repoPath, createArgs);
10074
10541
  const worktrees = await listWorktrees(repoPath);
10075
- const created = worktrees.find((wt) => wt.path === path9);
10542
+ const normalizedRequested = normalizeWorktreePath(worktreePath);
10543
+ const created = worktrees.find((wt) => normalizeWorktreePath(wt.path) === normalizedRequested);
10076
10544
  if (!created) {
10077
10545
  throw new Error("Failed to find created worktree");
10078
10546
  }
@@ -10094,13 +10562,41 @@ var init_worktree = __esm({
10094
10562
  });
10095
10563
 
10096
10564
  // packages/server/src/commands/worktree.ts
10097
- import { z as z13 } from "zod";
10565
+ import path9 from "node:path";
10566
+ import { z as z14 } from "zod";
10567
+ async function findRelatedWorkspaceIds(ctx, workspacePath) {
10568
+ const targetCommonDir = await getGitCommonDirPath(workspacePath);
10569
+ const relatedWorkspaceIds = await Promise.all(
10570
+ ctx.workspaceMgr.list().map(async (workspace) => {
10571
+ try {
10572
+ const commonDir = await getGitCommonDirPath(workspace.path);
10573
+ return commonDir === targetCommonDir ? workspace.id : null;
10574
+ } catch {
10575
+ return null;
10576
+ }
10577
+ })
10578
+ );
10579
+ return relatedWorkspaceIds.filter((workspaceId) => Boolean(workspaceId));
10580
+ }
10581
+ function emitWorktreeChangedForWorkspaceIds(ctx, workspaceIds) {
10582
+ for (const workspaceId of workspaceIds) {
10583
+ if (!ctx.workspaceMgr.get(workspaceId)) {
10584
+ continue;
10585
+ }
10586
+ emitGitStateChanged(ctx, workspaceId, { worktreeChanged: true });
10587
+ }
10588
+ }
10589
+ function isWorkspaceOpenForPath(ctx, workspacePath) {
10590
+ const targetPath = path9.resolve(workspacePath);
10591
+ return ctx.workspaceMgr.list().some((openWorkspace) => path9.resolve(openWorkspace.path) === targetPath);
10592
+ }
10098
10593
  var init_worktree2 = __esm({
10099
10594
  "packages/server/src/commands/worktree.ts"() {
10100
10595
  "use strict";
10101
10596
  init_worktree();
10102
10597
  init_dispatch();
10103
- registerCommand("worktree.list", z13.object({ workspaceId: z13.string() }), async (args, ctx) => {
10598
+ init_git_events();
10599
+ registerCommand("worktree.list", z14.object({ workspaceId: z14.string() }), async (args, ctx) => {
10104
10600
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
10105
10601
  if (!workspace) {
10106
10602
  throw { code: "workspace_not_found", message: `Workspace not found: ${args.workspaceId}` };
@@ -10109,7 +10605,7 @@ var init_worktree2 = __esm({
10109
10605
  });
10110
10606
  registerCommand(
10111
10607
  "worktree.status",
10112
- z13.object({ workspaceId: z13.string(), worktreePath: z13.string() }),
10608
+ z14.object({ workspaceId: z14.string(), worktreePath: z14.string() }),
10113
10609
  async (args, ctx) => {
10114
10610
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
10115
10611
  if (!workspace) {
@@ -10121,10 +10617,10 @@ var init_worktree2 = __esm({
10121
10617
  );
10122
10618
  registerCommand(
10123
10619
  "worktree.diff",
10124
- z13.object({
10125
- workspaceId: z13.string(),
10126
- worktreePath: z13.string(),
10127
- staged: z13.boolean().optional().default(false)
10620
+ z14.object({
10621
+ workspaceId: z14.string(),
10622
+ worktreePath: z14.string(),
10623
+ staged: z14.boolean().optional().default(false)
10128
10624
  }),
10129
10625
  async (args, ctx) => {
10130
10626
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
@@ -10137,7 +10633,7 @@ var init_worktree2 = __esm({
10137
10633
  );
10138
10634
  registerCommand(
10139
10635
  "worktree.tree",
10140
- z13.object({ workspaceId: z13.string(), worktreePath: z13.string() }),
10636
+ z14.object({ workspaceId: z14.string(), worktreePath: z14.string() }),
10141
10637
  async (args, ctx) => {
10142
10638
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
10143
10639
  if (!workspace) {
@@ -10149,33 +10645,44 @@ var init_worktree2 = __esm({
10149
10645
  );
10150
10646
  registerCommand(
10151
10647
  "worktree.create",
10152
- z13.object({
10153
- workspaceId: z13.string(),
10154
- branch: z13.string(),
10155
- path: z13.string()
10648
+ z14.object({
10649
+ workspaceId: z14.string(),
10650
+ branch: z14.string(),
10651
+ path: z14.string()
10156
10652
  }),
10157
10653
  async (args, ctx) => {
10158
10654
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
10159
10655
  if (!workspace) {
10160
10656
  throw { code: "workspace_not_found", message: `Workspace not found: ${args.workspaceId}` };
10161
10657
  }
10162
- return { worktree: await createWorktree(workspace.path, args.branch, args.path) };
10658
+ const relatedWorkspaceIds = await findRelatedWorkspaceIds(ctx, workspace.path);
10659
+ const worktree = await createWorktree(workspace.path, args.branch, args.path);
10660
+ emitWorktreeChangedForWorkspaceIds(ctx, relatedWorkspaceIds);
10661
+ return { worktree };
10163
10662
  }
10164
10663
  );
10165
10664
  registerCommand(
10166
10665
  "worktree.remove",
10167
- z13.object({
10168
- workspaceId: z13.string(),
10169
- worktreePath: z13.string(),
10170
- force: z13.boolean().optional().default(false)
10666
+ z14.object({
10667
+ workspaceId: z14.string(),
10668
+ worktreePath: z14.string(),
10669
+ force: z14.boolean().optional().default(false)
10171
10670
  }),
10172
10671
  async (args, ctx) => {
10173
10672
  const workspace = ctx.workspaceMgr.get(args.workspaceId);
10174
10673
  if (!workspace) {
10175
10674
  throw { code: "workspace_not_found", message: `Workspace not found: ${args.workspaceId}` };
10176
10675
  }
10676
+ const relatedWorkspaceIds = await findRelatedWorkspaceIds(ctx, workspace.path);
10177
10677
  const worktreePath = await resolveWorktreePath(workspace.path, args.worktreePath);
10678
+ if (isWorkspaceOpenForPath(ctx, worktreePath)) {
10679
+ throw {
10680
+ code: "worktree_in_use",
10681
+ message: `Cannot remove an open worktree workspace: ${worktreePath}`
10682
+ };
10683
+ }
10178
10684
  await removeWorktree(workspace.path, worktreePath, args.force);
10685
+ emitWorktreeChangedForWorkspaceIds(ctx, relatedWorkspaceIds);
10179
10686
  return {};
10180
10687
  }
10181
10688
  );
@@ -10183,7 +10690,7 @@ var init_worktree2 = __esm({
10183
10690
  });
10184
10691
 
10185
10692
  // packages/server/src/commands/fencing.ts
10186
- import { z as z14 } from "zod";
10693
+ import { z as z15 } from "zod";
10187
10694
  function createMockFencingRequest() {
10188
10695
  return {
10189
10696
  ip: "127.0.0.1",
@@ -10196,9 +10703,9 @@ var init_fencing2 = __esm({
10196
10703
  init_dispatch();
10197
10704
  registerCommand(
10198
10705
  "fencing.request",
10199
- z14.object({
10200
- workspaceId: z14.string(),
10201
- tabId: z14.string()
10706
+ z15.object({
10707
+ workspaceId: z15.string(),
10708
+ tabId: z15.string()
10202
10709
  }),
10203
10710
  async (args, ctx, clientId) => {
10204
10711
  return ctx.fencingMgr.requestControl(
@@ -10211,7 +10718,7 @@ var init_fencing2 = __esm({
10211
10718
  );
10212
10719
  registerCommand(
10213
10720
  "fencing.heartbeat",
10214
- z14.object({ workspaceId: z14.string() }),
10721
+ z15.object({ workspaceId: z15.string() }),
10215
10722
  async (args, ctx, clientId) => {
10216
10723
  const success = ctx.fencingMgr.heartbeat(args.workspaceId, clientId);
10217
10724
  return { success };
@@ -10219,13 +10726,13 @@ var init_fencing2 = __esm({
10219
10726
  );
10220
10727
  registerCommand(
10221
10728
  "fencing.release",
10222
- z14.object({ workspaceId: z14.string() }),
10729
+ z15.object({ workspaceId: z15.string() }),
10223
10730
  async (args, ctx, clientId) => {
10224
10731
  ctx.fencingMgr.release(args.workspaceId, clientId);
10225
10732
  return {};
10226
10733
  }
10227
10734
  );
10228
- registerCommand("fencing.status", z14.object({ workspaceId: z14.string() }), async (args, ctx) => {
10735
+ registerCommand("fencing.status", z15.object({ workspaceId: z15.string() }), async (args, ctx) => {
10229
10736
  const controller = ctx.fencingMgr.getController(args.workspaceId);
10230
10737
  const isUnresponsive = ctx.fencingMgr.isControllerUnresponsive(args.workspaceId);
10231
10738
  return {
@@ -10236,9 +10743,9 @@ var init_fencing2 = __esm({
10236
10743
  });
10237
10744
  registerCommand(
10238
10745
  "fencing.takeover",
10239
- z14.object({
10240
- workspaceId: z14.string(),
10241
- tabId: z14.string()
10746
+ z15.object({
10747
+ workspaceId: z15.string(),
10748
+ tabId: z15.string()
10242
10749
  }),
10243
10750
  async (args, ctx, clientId) => {
10244
10751
  return ctx.fencingMgr.forceTakeover(
@@ -10257,6 +10764,7 @@ var init_commands = __esm({
10257
10764
  "packages/server/src/commands/index.ts"() {
10258
10765
  "use strict";
10259
10766
  init_workspace();
10767
+ init_workspace_activity();
10260
10768
  init_session();
10261
10769
  init_terminal();
10262
10770
  init_file();
@@ -10270,32 +10778,6 @@ var init_commands = __esm({
10270
10778
  });
10271
10779
 
10272
10780
  // packages/server/src/server.ts
10273
- function createCodexConfigAuditApi() {
10274
- return {
10275
- audit: () => ({ codex: auditCodexConfigToml() }),
10276
- cleanup: (removeIds) => {
10277
- const audit = auditCodexConfigToml();
10278
- return cleanupCodexConfigToml(audit.configPath, { removeIds });
10279
- }
10280
- };
10281
- }
10282
- async function logCodexConfigFindings(auditApi, logger) {
10283
- try {
10284
- const audit = auditApi.audit();
10285
- for (const finding of audit.codex.findings) {
10286
- logger.warn(
10287
- {
10288
- configPath: audit.codex.configPath,
10289
- startLine: finding.startLine,
10290
- findingMessage: finding.message
10291
- },
10292
- "Codex config finding"
10293
- );
10294
- }
10295
- } catch (err) {
10296
- logger.warn({ err }, "Codex config audit failed (non-fatal)");
10297
- }
10298
- }
10299
10781
  async function createServer(configOverrides) {
10300
10782
  const config = parseServerConfig(configOverrides);
10301
10783
  ensureDataDir(config);
@@ -10303,14 +10785,45 @@ async function createServer(configOverrides) {
10303
10785
  const eventBus = new EventBus();
10304
10786
  const fencingMgr = new FencingManager();
10305
10787
  const wsHub = new WsHub({ eventBus, commandContext: null, config, fencingMgr });
10788
+ let workspaceMgr;
10789
+ let commandContext;
10306
10790
  const terminalMgr = new TerminalManager({
10307
10791
  ptyHost: createPtyHost(),
10308
10792
  eventBus,
10309
10793
  db: createTerminalDatabase(db)
10310
10794
  });
10795
+ const settingsRepo = new SettingsRepo(db);
10796
+ const autoFetch = new AutoFetchScheduler({
10797
+ workspaceMgr: { get: (workspaceId) => workspaceMgr.get(workspaceId) },
10798
+ eventBus,
10799
+ settingsRepo,
10800
+ runFetch: async (workspaceId) => {
10801
+ if (!workspaceMgr.get(workspaceId)) {
10802
+ return;
10803
+ }
10804
+ const result = await dispatch(
10805
+ {
10806
+ kind: "command",
10807
+ id: `auto-fetch:${workspaceId}:${Date.now()}`,
10808
+ op: "git.fetch",
10809
+ args: {
10810
+ workspaceId,
10811
+ background: true
10812
+ }
10813
+ },
10814
+ commandContext
10815
+ );
10816
+ if (!result.ok) {
10817
+ throw new Error(result.error?.message ?? "Background fetch failed");
10818
+ }
10819
+ const data = result.data;
10820
+ if (data.success === false) {
10821
+ throw new Error(data.message ?? "Background fetch failed");
10822
+ }
10823
+ }
10824
+ });
10311
10825
  const sessionDb = createSessionDatabase(db);
10312
10826
  const providerConfigRepo = new ProviderConfigRepo(db);
10313
- const settingsRepo = new SettingsRepo(db);
10314
10827
  const sessionMgr = new SessionManager({
10315
10828
  terminalMgr,
10316
10829
  eventBus,
@@ -10320,10 +10833,11 @@ async function createServer(configOverrides) {
10320
10833
  providerConfigRepo
10321
10834
  });
10322
10835
  let supervisorMgr;
10323
- const workspaceMgr = new WorkspaceManager({
10836
+ workspaceMgr = new WorkspaceManager({
10324
10837
  db,
10325
10838
  eventBus,
10326
10839
  broadcaster: wsHub,
10840
+ autoFetch,
10327
10841
  teardown: async (workspaceId) => {
10328
10842
  await supervisorMgr?.deleteForWorkspace(workspaceId);
10329
10843
  await sessionMgr.stopForWorkspace(workspaceId);
@@ -10334,9 +10848,9 @@ async function createServer(configOverrides) {
10334
10848
  (err) => console.warn("[uploads] cascade cleanup failed", { wsId: workspaceId, err })
10335
10849
  )
10336
10850
  });
10851
+ workspaceMgr.hydrateWatchers();
10337
10852
  const authSessionRepo = new AuthSessionRepo(db);
10338
10853
  const authLoginBlockRepo = new AuthLoginBlockRepo(db);
10339
- const codexConfigAudit = createCodexConfigAuditApi();
10340
10854
  const app = await buildFastifyApp({
10341
10855
  wsHub,
10342
10856
  db,
@@ -10357,7 +10871,6 @@ async function createServer(configOverrides) {
10357
10871
  }
10358
10872
  });
10359
10873
  wsHub.setLogger(app.log);
10360
- await logCodexConfigFindings(codexConfigAudit, app.log);
10361
10874
  const supervisorRepo = new SupervisorRepo(db);
10362
10875
  const cycleRepo = new SupervisorCycleRepo(db);
10363
10876
  supervisorMgr = new SupervisorManager({
@@ -10375,12 +10888,15 @@ async function createServer(configOverrides) {
10375
10888
  });
10376
10889
  await sessionMgr.hydrate();
10377
10890
  await supervisorMgr.hydrate();
10378
- const providerRuntimeDeps = {};
10891
+ const providerMockOverrides = createE2EProviderMockOverrides();
10892
+ const providerRuntimeDeps = providerMockOverrides ? {
10893
+ commandExists: providerMockOverrides.commandExists
10894
+ } : {};
10379
10895
  const providerInstallMgr = new ProviderInstallManager(providerRegistry, {
10380
10896
  ...providerRuntimeDeps,
10381
- runCommand: runCommandAsString
10897
+ runCommand: providerMockOverrides?.runCommand ?? runCommandAsString
10382
10898
  });
10383
- const commandContext = {
10899
+ commandContext = {
10384
10900
  workspaceMgr,
10385
10901
  sessionMgr,
10386
10902
  terminalMgr,
@@ -10390,9 +10906,9 @@ async function createServer(configOverrides) {
10390
10906
  providerRegistry,
10391
10907
  fencingMgr,
10392
10908
  supervisorMgr,
10909
+ autoFetch,
10393
10910
  providerRuntimeDeps,
10394
- providerInstallMgr,
10395
- codexConfigAudit
10911
+ providerInstallMgr
10396
10912
  };
10397
10913
  wsHub.setCommandContext(commandContext);
10398
10914
  await app.listen({
@@ -10423,6 +10939,7 @@ async function createServer(configOverrides) {
10423
10939
  stopped = true;
10424
10940
  clearTimeout(gcTimer);
10425
10941
  await app.close();
10942
+ autoFetch.stop();
10426
10943
  supervisorMgr.stop();
10427
10944
  terminalMgr.shutdown();
10428
10945
  wsHub.destroy();
@@ -10544,9 +11061,10 @@ var init_server = __esm({
10544
11061
  init_src2();
10545
11062
  init_app();
10546
11063
  init_event_bus();
10547
- init_codex_config_audit();
10548
11064
  init_config();
11065
+ init_auto_fetch();
10549
11066
  init_command_runner();
11067
+ init_e2e_provider_mock();
10550
11068
  init_install_manager();
10551
11069
  init_manager();
10552
11070
  init_db();
@@ -10563,6 +11081,7 @@ var init_server = __esm({
10563
11081
  init_cleanup();
10564
11082
  init_constants();
10565
11083
  init_manager4();
11084
+ init_dispatch();
10566
11085
  init_fencing();
10567
11086
  init_hub();
10568
11087
  init_commands();
@@ -10716,8 +11235,8 @@ var init_workspace_repo = __esm({
10716
11235
  /**
10717
11236
  * Finds a workspace by path
10718
11237
  */
10719
- findByPath(path9) {
10720
- const row = this.db.prepare("SELECT * FROM workspaces WHERE path = ?").get(path9);
11238
+ findByPath(path10) {
11239
+ const row = this.db.prepare("SELECT * FROM workspaces WHERE path = ?").get(path10);
10721
11240
  return row ? this.rowToWorkspace(row) : void 0;
10722
11241
  }
10723
11242
  /**
@@ -10861,11 +11380,11 @@ await init_src4();
10861
11380
 
10862
11381
  // packages/cli/src/config-store.ts
10863
11382
  import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
10864
- import { homedir as homedir5 } from "os";
11383
+ import { homedir as homedir4 } from "os";
10865
11384
  import { basename as basename3, join as join8 } from "path";
10866
11385
  var DEFAULT_DB_FILE = "coder-studio.db";
10867
11386
  function getCliConfigPath() {
10868
- return join8(homedir5(), ".coder-studio", "config.json");
11387
+ return join8(homedir4(), ".coder-studio", "config.json");
10869
11388
  }
10870
11389
  function normalizeDataDir(input2) {
10871
11390
  if (input2.endsWith(".db")) {
@@ -10877,12 +11396,12 @@ function normalizeDataDir(input2) {
10877
11396
  return join8(input2, DEFAULT_DB_FILE);
10878
11397
  }
10879
11398
  function readCliConfig() {
10880
- const path9 = getCliConfigPath();
10881
- if (!existsSync7(path9)) {
11399
+ const path10 = getCliConfigPath();
11400
+ if (!existsSync7(path10)) {
10882
11401
  return null;
10883
11402
  }
10884
11403
  try {
10885
- const parsed = JSON.parse(readFileSync7(path9, "utf-8"));
11404
+ const parsed = JSON.parse(readFileSync7(path10, "utf-8"));
10886
11405
  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") {
10887
11406
  return null;
10888
11407
  }
@@ -10892,8 +11411,8 @@ function readCliConfig() {
10892
11411
  }
10893
11412
  }
10894
11413
  function writeCliConfig(config) {
10895
- const path9 = getCliConfigPath();
10896
- const dir = join8(homedir5(), ".coder-studio");
11414
+ const path10 = getCliConfigPath();
11415
+ const dir = join8(homedir4(), ".coder-studio");
10897
11416
  const normalizedConfig = {
10898
11417
  ...config.host !== void 0 ? { host: config.host } : {},
10899
11418
  ...config.port !== void 0 && config.port > 0 ? { port: config.port } : {},
@@ -10903,7 +11422,7 @@ function writeCliConfig(config) {
10903
11422
  if (!existsSync7(dir)) {
10904
11423
  mkdirSync4(dir, { recursive: true });
10905
11424
  }
10906
- writeFileSync4(path9, JSON.stringify(normalizedConfig, null, 2), "utf-8");
11425
+ writeFileSync4(path10, JSON.stringify(normalizedConfig, null, 2), "utf-8");
10907
11426
  }
10908
11427
 
10909
11428
  // packages/cli/src/auth-control.ts
@@ -10971,26 +11490,26 @@ import { closeSync, existsSync as existsSync8, openSync, readSync, statSync as s
10971
11490
  var DEFAULT_MAX_LINES = 40;
10972
11491
  var DEFAULT_MAX_CHARS = 4e3;
10973
11492
  var DEFAULT_MAX_BYTES = 16 * 1024;
10974
- var getFileSize = (path9) => {
10975
- if (!existsSync8(path9)) {
11493
+ var getFileSize = (path10) => {
11494
+ if (!existsSync8(path10)) {
10976
11495
  return 0;
10977
11496
  }
10978
11497
  try {
10979
- return statSync2(path9).size;
11498
+ return statSync2(path10).size;
10980
11499
  } catch {
10981
11500
  return 0;
10982
11501
  }
10983
11502
  };
10984
- var readLogExcerpt = (path9, {
11503
+ var readLogExcerpt = (path10, {
10985
11504
  startOffset = 0,
10986
11505
  maxBytes = DEFAULT_MAX_BYTES,
10987
11506
  maxLines = DEFAULT_MAX_LINES,
10988
11507
  maxChars = DEFAULT_MAX_CHARS
10989
11508
  } = {}) => {
10990
- if (!existsSync8(path9)) {
11509
+ if (!existsSync8(path10)) {
10991
11510
  return null;
10992
11511
  }
10993
- const fileSize = getFileSize(path9);
11512
+ const fileSize = getFileSize(path10);
10994
11513
  const safeOffset = startOffset > fileSize ? 0 : Math.max(0, startOffset);
10995
11514
  if (fileSize === safeOffset) {
10996
11515
  return null;
@@ -10998,7 +11517,7 @@ var readLogExcerpt = (path9, {
10998
11517
  const bytesToRead = Math.min(fileSize - safeOffset, maxBytes);
10999
11518
  const readStart = fileSize - bytesToRead;
11000
11519
  const buffer = Buffer.allocUnsafe(bytesToRead);
11001
- const fd = openSync(path9, "r");
11520
+ const fd = openSync(path10, "r");
11002
11521
  let content = "";
11003
11522
  let startsMidLine = false;
11004
11523
  try {
@@ -11289,7 +11808,7 @@ function parseArgs(argv) {
11289
11808
  // packages/cli/src/pm2-control.ts
11290
11809
  init_runtime();
11291
11810
  import { mkdirSync as mkdirSync5 } from "fs";
11292
- import { homedir as homedir6 } from "os";
11811
+ import { homedir as homedir5 } from "os";
11293
11812
  import { join as join9 } from "path";
11294
11813
  var MANAGED_SERVER_NAME = "coder-studio-server";
11295
11814
  var PM2_RESTART_DELAY_MS = 2e3;
@@ -11481,11 +12000,11 @@ var deleteManagedServerInSession = async (pm2, {
11481
12000
  return true;
11482
12001
  };
11483
12002
  var ensureLogDirectory = () => {
11484
- mkdirSync5(join9(homedir6(), ".coder-studio", "logs"), { recursive: true });
12003
+ mkdirSync5(join9(homedir5(), ".coder-studio", "logs"), { recursive: true });
11485
12004
  };
11486
12005
  var getLogPaths = () => ({
11487
- outFile: join9(homedir6(), ".coder-studio", "logs", "server.out.log"),
11488
- errFile: join9(homedir6(), ".coder-studio", "logs", "server.err.log")
12006
+ outFile: join9(homedir5(), ".coder-studio", "logs", "server.out.log"),
12007
+ errFile: join9(homedir5(), ".coder-studio", "logs", "server.err.log")
11489
12008
  });
11490
12009
  var captureStartupLogOffsets = () => {
11491
12010
  const { outFile, errFile } = getLogPaths();
@@ -11794,8 +12313,8 @@ function showLogs(status, {
11794
12313
  errorsOnly = false
11795
12314
  } = {}) {
11796
12315
  const paths = errorsOnly ? [status.errFile] : [status.outFile, status.errFile];
11797
- const contents = paths.filter((path9, index, paths2) => paths2.indexOf(path9) === index).flatMap((path9) => {
11798
- const content = readLogExcerpt(path9, { maxLines: tail, maxChars: null });
12316
+ const contents = paths.filter((path10, index, paths2) => paths2.indexOf(path10) === index).flatMap((path10) => {
12317
+ const content = readLogExcerpt(path10, { maxLines: tail, maxChars: null });
11799
12318
  return content ? [content] : [];
11800
12319
  });
11801
12320
  console.log(contents.length === 0 ? "No logs available." : contents.join("\n"));