openfox 2.0.73 → 2.0.74

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.
@@ -5,9 +5,9 @@ import {
5
5
  } from "./chunk-2DCJERYX.js";
6
6
  import {
7
7
  runChatTurn
8
- } from "./chunk-O2BE7KVQ.js";
9
- import "./chunk-GMF36EZ3.js";
10
- import "./chunk-X2Y3AJPP.js";
8
+ } from "./chunk-ZRQDCQ7R.js";
9
+ import "./chunk-2VBETAU2.js";
10
+ import "./chunk-MF3UW36X.js";
11
11
  import "./chunk-WGMJCFCE.js";
12
12
  import "./chunk-EU3WWTFH.js";
13
13
  import "./chunk-XWBNYU2L.js";
@@ -158,4 +158,4 @@ export {
158
158
  startChatSession,
159
159
  stopSessionExecution
160
160
  };
161
- //# sourceMappingURL=chat-handler-LK5RUAGO.js.map
161
+ //# sourceMappingURL=chat-handler-ETKFIHUO.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  getGitBranch,
3
3
  listWorkspaces
4
- } from "./chunk-X2Y3AJPP.js";
4
+ } from "./chunk-MF3UW36X.js";
5
5
  import {
6
6
  AskUserInterrupt,
7
7
  askUserTool
@@ -3814,7 +3814,7 @@ var callSubAgentTool = {
3814
3814
  };
3815
3815
  }
3816
3816
  try {
3817
- const { getToolRegistryForAgent: getToolRegistryForAgent2 } = await import("./tools-X52DFUS6.js");
3817
+ const { getToolRegistryForAgent: getToolRegistryForAgent2 } = await import("./tools-APW7MY4E.js");
3818
3818
  const toolRegistry = getToolRegistryForAgent2(agentDef);
3819
3819
  const turnMetrics = new TurnMetrics();
3820
3820
  const result = await executeSubAgent({
@@ -4813,7 +4813,7 @@ var mcpConfigTool = createTool(
4813
4813
  mcpNotifyChanged?.(sessionId);
4814
4814
  }
4815
4815
  async function rebuildTools() {
4816
- const { setMcpTools: setMcpTools2 } = await import("./tools-X52DFUS6.js");
4816
+ const { setMcpTools: setMcpTools2 } = await import("./tools-APW7MY4E.js");
4817
4817
  const mcpTools = createMcpTools(mcpManagerForTools);
4818
4818
  setMcpTools2(mcpTools);
4819
4819
  }
@@ -5493,4 +5493,4 @@ export {
5493
5493
  getToolRegistryForAgent,
5494
5494
  createToolRegistry
5495
5495
  };
5496
- //# sourceMappingURL=chunk-GMF36EZ3.js.map
5496
+ //# sourceMappingURL=chunk-2VBETAU2.js.map
@@ -0,0 +1,7 @@
1
+ // src/constants.ts
2
+ var VERSION = "2.0.74";
3
+
4
+ export {
5
+ VERSION
6
+ };
7
+ //# sourceMappingURL=chunk-3OZBPWHP.js.map
@@ -8,7 +8,7 @@ import {
8
8
  // src/server/git/workspace.ts
9
9
  import { spawn, execSync } from "child_process";
10
10
  import { mkdir as mkdir2 } from "fs/promises";
11
- import { resolve as resolve2, join as join2 } from "path";
11
+ import { resolve as resolve2, join as join2, isAbsolute } from "path";
12
12
  import { homedir, platform } from "os";
13
13
 
14
14
  // src/server/git/workspace-config.ts
@@ -106,6 +106,13 @@ async function getCommitsBehind(cwd, branch) {
106
106
  function validateRef(cwd, name) {
107
107
  return runGit(cwd, ["check-ref-format", `refs/heads/${name}`]);
108
108
  }
109
+ function validateWorkspaceName(name) {
110
+ if (!name || typeof name !== "string") throw new Error("Workspace name is required");
111
+ if (name.includes("/") || name.includes("\\")) throw new Error("Workspace name cannot contain path separators");
112
+ if (name === "." || name === "..") throw new Error('Workspace name cannot be "." or ".."');
113
+ if (isAbsolute(name)) throw new Error("Workspace name cannot be an absolute path");
114
+ if (name.length > 255) throw new Error("Workspace name is too long");
115
+ }
109
116
  function checkoutBranch(cwd, name) {
110
117
  return runGit(cwd, ["checkout", name]);
111
118
  }
@@ -206,6 +213,7 @@ export {
206
213
  listBranches,
207
214
  getCommitsBehind,
208
215
  validateRef,
216
+ validateWorkspaceName,
209
217
  checkoutBranch,
210
218
  createBranch,
211
219
  workspaceExists,
@@ -213,4 +221,4 @@ export {
213
221
  ensureWorkspace,
214
222
  deleteWorkspace
215
223
  };
216
- //# sourceMappingURL=chunk-X2Y3AJPP.js.map
224
+ //# sourceMappingURL=chunk-MF3UW36X.js.map
@@ -21,7 +21,7 @@ import {
21
21
  tokenFromPassword,
22
22
  verifyPassword,
23
23
  workflowExists
24
- } from "./chunk-TK4BZIXA.js";
24
+ } from "./chunk-TMRFTWXP.js";
25
25
  import {
26
26
  agentExists,
27
27
  createToolRegistry,
@@ -66,7 +66,7 @@ import {
66
66
  setSkillEnabled,
67
67
  skillExists,
68
68
  updateOwnedSkill
69
- } from "./chunk-GMF36EZ3.js";
69
+ } from "./chunk-2VBETAU2.js";
70
70
  import {
71
71
  deleteWorkspace,
72
72
  ensureWorkspace,
@@ -76,8 +76,9 @@ import {
76
76
  loadWorkspaceConfig,
77
77
  runGit,
78
78
  saveWorkspaceConfig,
79
+ validateWorkspaceName,
79
80
  workspaceExists
80
- } from "./chunk-X2Y3AJPP.js";
81
+ } from "./chunk-MF3UW36X.js";
81
82
  import {
82
83
  EventEmitter
83
84
  } from "./chunk-EU3WWTFH.js";
@@ -94,7 +95,7 @@ import {
94
95
  } from "./chunk-NWO6GRYE.js";
95
96
  import {
96
97
  VERSION
97
- } from "./chunk-NE7OWIIO.js";
98
+ } from "./chunk-3OZBPWHP.js";
98
99
  import {
99
100
  createSession,
100
101
  deleteSession,
@@ -2557,6 +2558,8 @@ var SessionManager = class {
2557
2558
  dynamicContextChangedStore = /* @__PURE__ */ new Map();
2558
2559
  debugDumpStore = /* @__PURE__ */ new Map();
2559
2560
  warmedUpSessions = /* @__PURE__ */ new Set();
2561
+ switchLocks = /* @__PURE__ */ new Map();
2562
+ workspaceCreationLocks = /* @__PURE__ */ new Map();
2560
2563
  constructor(providerManager) {
2561
2564
  this.providerManager = providerManager;
2562
2565
  }
@@ -3213,89 +3216,135 @@ var SessionManager = class {
3213
3216
  * Switch to a workspace. Target can be "original" (project root) or a workspace name.
3214
3217
  * If the workspace doesn't exist yet, it's created first.
3215
3218
  * Emits a single type of event — switching is always "opening" a workspace.
3219
+ * Switches are serialized per-session to prevent race conditions.
3216
3220
  */
3217
3221
  async switchWorkspace(sessionId, target, branch) {
3218
- const session = this.requireSession(sessionId);
3219
- const project = getProject(session.projectId);
3220
- if (!project) throw new Error(`Project not found: ${session.projectId}`);
3221
- if (target === "original" && !session.workspace) return session;
3222
- if (target !== "original" && session.workspace?.endsWith(`/${target}`)) return session;
3223
- const previousPath = session.workspace;
3224
- if (previousPath) {
3222
+ if (target !== "original") {
3223
+ validateWorkspaceName(target);
3224
+ }
3225
+ const existingLock = this.switchLocks.get(sessionId);
3226
+ if (existingLock) {
3227
+ await existingLock;
3228
+ }
3229
+ const lockPromise = (async () => {
3230
+ const session = this.requireSession(sessionId);
3231
+ const project = getProject(session.projectId);
3232
+ if (!project) throw new Error(`Project not found: ${session.projectId}`);
3233
+ if (target === "original" && !session.workspace) return session;
3234
+ if (target !== "original" && session.workspace?.split("/").pop() === target) return session;
3235
+ const previousPath = session.workspace;
3236
+ if (previousPath) {
3237
+ try {
3238
+ await devServerManager.stop(previousPath);
3239
+ } catch (err) {
3240
+ logger.error("Error stopping dev server for workspace switch", {
3241
+ sessionId,
3242
+ workspace: previousPath,
3243
+ error: err
3244
+ });
3245
+ }
3246
+ }
3247
+ if (target === "original") {
3248
+ updateSessionWorkdir(sessionId, project.workdir, null);
3249
+ } else {
3250
+ const createLockKey = `${project.name}:${target}`;
3251
+ const existingCreateLock = this.workspaceCreationLocks.get(createLockKey);
3252
+ if (existingCreateLock) {
3253
+ await existingCreateLock;
3254
+ }
3255
+ const createLockPromise = (async () => {
3256
+ const exists = await workspaceExists(project.name, target);
3257
+ if (!exists) {
3258
+ await ensureWorkspace(project.workdir, target, project.name, branch);
3259
+ }
3260
+ })();
3261
+ this.workspaceCreationLocks.set(createLockKey, createLockPromise);
3262
+ try {
3263
+ await createLockPromise;
3264
+ } finally {
3265
+ this.workspaceCreationLocks.delete(createLockKey);
3266
+ }
3267
+ const wsPath = resolve2(getWorkspacesDir(project.name), target);
3268
+ updateSessionWorkdir(sessionId, project.workdir, wsPath);
3269
+ }
3225
3270
  try {
3226
- await devServerManager.stop(previousPath);
3271
+ await shutdownLspManager(sessionId);
3227
3272
  } catch (err) {
3228
- logger.error("Error stopping dev server for workspace switch", {
3229
- sessionId,
3230
- workspace: previousPath,
3231
- error: err
3232
- });
3233
- }
3234
- }
3235
- if (target === "original") {
3236
- updateSessionWorkdir(sessionId, project.workdir, null);
3237
- } else {
3238
- const exists = await workspaceExists(project.name, target);
3239
- if (!exists) {
3240
- await ensureWorkspace(project.workdir, target, project.name, branch);
3273
+ logger.error("Error shutting down LSP for workspace switch", { sessionId, error: err });
3241
3274
  }
3242
- const wsPath = resolve2(getWorkspacesDir(project.name), target);
3243
- updateSessionWorkdir(sessionId, project.workdir, wsPath);
3244
- }
3245
- try {
3246
- await shutdownLspManager(sessionId);
3247
- } catch (err) {
3248
- logger.error("Error shutting down LSP for workspace switch", { sessionId, error: err });
3249
- }
3250
- const effectiveWorkdir = target === "original" ? project.workdir : resolve2(getWorkspacesDir(project.name), target);
3251
- const actualBranch = await getGitBranch(effectiveWorkdir);
3252
- let stalenessHint = "";
3253
- if (target !== "original" && actualBranch) {
3254
- await runGit(effectiveWorkdir, ["fetch", "origin", "--no-tags", "--quiet"]).catch(() => {
3255
- });
3256
- const behind = await getCommitsBehind(effectiveWorkdir, actualBranch);
3257
- if (behind !== null && behind > 0) {
3258
- const plural = behind === 1 ? "" : "s";
3259
- stalenessHint = `
3275
+ const effectiveWorkdir = target === "original" ? project.workdir : resolve2(getWorkspacesDir(project.name), target);
3276
+ const actualBranch = await getGitBranch(effectiveWorkdir);
3277
+ let stalenessHint = "";
3278
+ if (target !== "original" && actualBranch) {
3279
+ await runGit(effectiveWorkdir, ["fetch", "origin", "--no-tags", "--quiet"]).catch(() => {
3280
+ });
3281
+ const behind = await getCommitsBehind(effectiveWorkdir, actualBranch);
3282
+ if (behind !== null && behind > 0) {
3283
+ const plural = behind === 1 ? "" : "s";
3284
+ stalenessHint = `
3260
3285
  (${behind} commit${plural} behind ${actualBranch} on main workspace \u2014 run \`git pull\` to sync)`;
3286
+ }
3261
3287
  }
3262
- }
3263
- const wsLabel = target === "original" ? "original" : target;
3264
- const reminderContent = `<system-reminder>
3288
+ const wsLabel = target === "original" ? "original" : target;
3289
+ const reminderContent = `<system-reminder>
3265
3290
  This session is now operating in workspace "${wsLabel}" on branch "${actualBranch ?? "unknown"}" at ${effectiveWorkdir}.${stalenessHint}
3266
3291
  All file and git operations should use this directory.
3267
3292
  </system-reminder>`;
3268
- this.addMessage(sessionId, {
3269
- role: "user",
3270
- content: reminderContent,
3271
- isSystemGenerated: true,
3272
- messageKind: "auto-prompt",
3273
- metadata: {
3274
- type: "workspace",
3275
- name: "Workspace",
3276
- color: "#22c55e",
3277
- kind: "definition",
3278
- workspaceName: wsLabel,
3279
- ...actualBranch ? { branchName: actualBranch } : {}
3293
+ this.addMessage(sessionId, {
3294
+ role: "user",
3295
+ content: reminderContent,
3296
+ isSystemGenerated: true,
3297
+ messageKind: "auto-prompt",
3298
+ metadata: {
3299
+ type: "workspace",
3300
+ name: "Workspace",
3301
+ color: "#22c55e",
3302
+ kind: "definition",
3303
+ workspaceName: wsLabel,
3304
+ ...actualBranch ? { branchName: actualBranch } : {}
3305
+ }
3306
+ });
3307
+ const updated = this.requireSession(sessionId);
3308
+ this.emit({ type: "session_updated", session: updated });
3309
+ return updated;
3310
+ })();
3311
+ this.switchLocks.set(sessionId, lockPromise);
3312
+ lockPromise.finally(() => {
3313
+ if (this.switchLocks.get(sessionId) === lockPromise) {
3314
+ this.switchLocks.delete(sessionId);
3280
3315
  }
3281
3316
  });
3282
- const updated = this.requireSession(sessionId);
3283
- this.emit({ type: "session_updated", session: updated });
3284
- return updated;
3317
+ return lockPromise;
3285
3318
  }
3286
3319
  /**
3287
3320
  * Delete a workspace by name. If the session is currently in that workspace,
3288
3321
  * switches to original first. Throws if target is "original".
3322
+ * Refuses deletion if other sessions reference this workspace.
3289
3323
  */
3290
3324
  async deleteWorkspace(sessionId, target) {
3291
3325
  if (target === "original") throw new Error("Cannot delete the original workspace");
3326
+ validateWorkspaceName(target);
3292
3327
  const session = this.requireSession(sessionId);
3293
3328
  const project = getProject(session.projectId);
3294
3329
  if (!project) throw new Error(`Project not found: ${session.projectId}`);
3330
+ const otherSessionsUsingIt = this.listSessions().filter(
3331
+ (s) => s.id !== sessionId && s.workspace?.split("/").pop() === target
3332
+ );
3333
+ if (otherSessionsUsingIt.length > 0) {
3334
+ const otherIds = otherSessionsUsingIt.map((s) => s.id).join(", ");
3335
+ throw new Error(
3336
+ `Workspace "${target}" is in use by other session(s): ${otherIds}. Switch them to original first.`
3337
+ );
3338
+ }
3295
3339
  const currentWsName = session.workspace?.split("/").pop();
3296
3340
  if (currentWsName === target) {
3297
3341
  await this.switchWorkspace(sessionId, "original");
3298
3342
  }
3343
+ const effectivePath = resolve2(getWorkspacesDir(project.name), target);
3344
+ try {
3345
+ await devServerManager.stop(effectivePath);
3346
+ } catch {
3347
+ }
3299
3348
  await deleteWorkspace(project.name, target);
3300
3349
  const updated = this.requireSession(sessionId);
3301
3350
  this.emit({ type: "session_updated", session: updated });
@@ -4252,6 +4301,11 @@ function fuzzyScore(filePath, query) {
4252
4301
  import { Router as Router9 } from "express";
4253
4302
  import { spawn as spawn2 } from "child_process";
4254
4303
  var updateInProgress = false;
4304
+ var CACHE_TTL_MS = 60 * 60 * 1e3;
4305
+ var versionCache = {
4306
+ data: null,
4307
+ timestamp: 0
4308
+ };
4255
4309
  function isRunningAsService() {
4256
4310
  return process.env["OPENFOX_SERVICE"] === "true";
4257
4311
  }
@@ -4265,13 +4319,94 @@ async function checkAuth(req, opts) {
4265
4319
  }
4266
4320
  return true;
4267
4321
  }
4322
+ function isDevMode() {
4323
+ return process.env["NODE_ENV"] === "development";
4324
+ }
4325
+ async function getRemote() {
4326
+ return new Promise((resolve8) => {
4327
+ const child = spawn2("git", ["remote"], {
4328
+ stdio: ["ignore", "pipe", "pipe"],
4329
+ windowsHide: true
4330
+ });
4331
+ let output = "";
4332
+ child.stdout?.on("data", (data) => {
4333
+ output += data.toString();
4334
+ });
4335
+ child.on("close", () => {
4336
+ const remotes = output.trim().split("\n").map((r) => r.trim()).filter(Boolean);
4337
+ if (remotes.includes("upstream")) resolve8("upstream");
4338
+ else if (remotes.includes("origin")) resolve8("origin");
4339
+ else resolve8("origin");
4340
+ });
4341
+ child.on("error", () => resolve8("origin"));
4342
+ });
4343
+ }
4344
+ async function getLatestDevelopVersion() {
4345
+ const remote = await getRemote();
4346
+ return new Promise((resolve8) => {
4347
+ const fetchChild = spawn2("git", ["fetch", remote, "develop", "--no-tags"], {
4348
+ stdio: ["ignore", "pipe", "pipe"],
4349
+ windowsHide: true
4350
+ });
4351
+ fetchChild.on("error", (err) => {
4352
+ logger.error("[auto-update] Git fetch error", { error: err.message });
4353
+ resolve8("unknown");
4354
+ });
4355
+ fetchChild.on("close", (code) => {
4356
+ if (code !== 0) {
4357
+ logger.warn("[auto-update] Git fetch failed, trying local tags...");
4358
+ }
4359
+ const tagChild = spawn2("git", ["describe", "--tags", "--abbrev=0", `${remote}/develop`], {
4360
+ stdio: ["ignore", "pipe", "pipe"],
4361
+ windowsHide: true
4362
+ });
4363
+ let tagOutput = "";
4364
+ tagChild.stdout?.on("data", (data) => {
4365
+ tagOutput += data.toString();
4366
+ });
4367
+ tagChild.on("close", (tagCode) => {
4368
+ if (tagCode === 0 && tagOutput.trim()) {
4369
+ resolve8(tagOutput.trim());
4370
+ } else {
4371
+ const fallbackChild = spawn2("git", ["describe", "--tags", "--abbrev=0", "develop"], {
4372
+ stdio: ["ignore", "pipe", "pipe"],
4373
+ windowsHide: true
4374
+ });
4375
+ let fallbackOutput = "";
4376
+ fallbackChild.stdout?.on("data", (data) => {
4377
+ fallbackOutput += data.toString();
4378
+ });
4379
+ fallbackChild.on("close", (fallbackCode) => {
4380
+ if (fallbackCode === 0 && fallbackOutput.trim()) {
4381
+ resolve8(fallbackOutput.trim());
4382
+ } else {
4383
+ logger.warn("[auto-update] No tags found on develop branch");
4384
+ resolve8("unknown");
4385
+ }
4386
+ });
4387
+ fallbackChild.on("error", () => resolve8("unknown"));
4388
+ }
4389
+ });
4390
+ tagChild.on("error", (err) => {
4391
+ logger.error("[auto-update] Git tag error", { error: err.message });
4392
+ resolve8("unknown");
4393
+ });
4394
+ });
4395
+ });
4396
+ }
4268
4397
  function createAutoUpdateRoutes(options = {}) {
4269
4398
  const router = Router9();
4270
- router.get("/check", async (_req, res) => {
4399
+ router.get("/check", async (req, res) => {
4271
4400
  const isService = isRunningAsService();
4401
+ const isDev = isDevMode();
4272
4402
  const current = VERSION;
4403
+ const forceRefresh = req.query["force"] === "true";
4404
+ if (!forceRefresh && versionCache.data && Date.now() - versionCache.timestamp < CACHE_TTL_MS) {
4405
+ res.json({ ...versionCache.data, isService });
4406
+ return;
4407
+ }
4273
4408
  try {
4274
- const latest = await new Promise((resolve8, reject) => {
4409
+ const latest = isDev ? await getLatestDevelopVersion() : await new Promise((resolve8, reject) => {
4275
4410
  const win = process.platform === "win32";
4276
4411
  const child = spawn2(win ? "npm view openfox version" : "npm", win ? [] : ["view", "openfox", "version"], {
4277
4412
  stdio: ["ignore", "pipe", "pipe"],
@@ -4295,9 +4430,15 @@ function createAutoUpdateRoutes(options = {}) {
4295
4430
  reject(new Error("npm view timed out"));
4296
4431
  }, 1e4);
4297
4432
  });
4298
- const isUpdateAvailable = current !== latest;
4433
+ const currentVersion = isDev ? current.replace(/-dev$/, "") : current;
4434
+ const latestVersion = latest.replace(/^v/, "");
4435
+ const isUpdateAvailable = currentVersion !== latestVersion;
4436
+ const now = Date.now();
4437
+ versionCache.data = { current, latest, isUpdateAvailable, isService };
4438
+ versionCache.timestamp = now;
4299
4439
  res.json({ current, latest, isUpdateAvailable, isService });
4300
- } catch {
4440
+ } catch (err) {
4441
+ logger.error("[auto-update] Error in version check", { error: err instanceof Error ? err.message : String(err) });
4301
4442
  res.json({ current, latest: current, isUpdateAvailable: false, isService });
4302
4443
  }
4303
4444
  });
@@ -4889,7 +5030,7 @@ async function createServerHandle(config3) {
4889
5030
  setMcpTools(mcpTools);
4890
5031
  logger.info("MCP tools registered", { count: mcpTools.length });
4891
5032
  }
4892
- const { signalMcpReady } = await import("./server-G2J4RAXL.js");
5033
+ const { signalMcpReady } = await import("./server-T2YP3VT2.js");
4893
5034
  signalMcpReady();
4894
5035
  });
4895
5036
  const app = express();
@@ -5050,7 +5191,7 @@ async function createServerHandle(config3) {
5050
5191
  const { getProject: getProject2 } = await import("./projects-LHRNCUGW.js");
5051
5192
  const project = getProject2(req.params.id);
5052
5193
  if (!project) return res.status(404).json({ error: "Project not found" });
5053
- const { listBranches } = await import("./workspace-ET3FHZYN.js");
5194
+ const { listBranches } = await import("./workspace-6QTTUMMW.js");
5054
5195
  const branches = await listBranches(project.workdir);
5055
5196
  res.json({ branches });
5056
5197
  });
@@ -5060,7 +5201,7 @@ async function createServerHandle(config3) {
5060
5201
  if (!project) return res.status(404).json({ error: "Project not found" });
5061
5202
  const { branch } = req.body;
5062
5203
  if (!branch || typeof branch !== "string") return res.status(400).json({ error: "branch is required" });
5063
- const { checkoutBranch } = await import("./workspace-ET3FHZYN.js");
5204
+ const { checkoutBranch } = await import("./workspace-6QTTUMMW.js");
5064
5205
  try {
5065
5206
  await checkoutBranch(project.workdir, branch);
5066
5207
  res.json({ branch });
@@ -5074,7 +5215,7 @@ async function createServerHandle(config3) {
5074
5215
  if (!project) return res.status(404).json({ error: "Project not found" });
5075
5216
  const { name } = req.body;
5076
5217
  if (!name || typeof name !== "string") return res.status(400).json({ error: "name is required" });
5077
- const { createBranch } = await import("./workspace-ET3FHZYN.js");
5218
+ const { createBranch } = await import("./workspace-6QTTUMMW.js");
5078
5219
  try {
5079
5220
  await createBranch(project.workdir, name);
5080
5221
  res.json({ branch: name });
@@ -5082,11 +5223,49 @@ async function createServerHandle(config3) {
5082
5223
  res.status(400).json({ error: err instanceof Error ? err.message : "Failed to create branch" });
5083
5224
  }
5084
5225
  });
5226
+ app.get("/api/sessions/:id/branches", async (req, res) => {
5227
+ const session = sessionManager.getSession(req.params.id);
5228
+ if (!session) return res.status(404).json({ error: "Session not found" });
5229
+ const effectiveWorkdir = session.workspace ?? session.workdir;
5230
+ const { listBranches } = await import("./workspace-6QTTUMMW.js");
5231
+ const branches = await listBranches(effectiveWorkdir);
5232
+ res.json({ branches });
5233
+ });
5234
+ app.post("/api/sessions/:id/checkout", async (req, res) => {
5235
+ const session = sessionManager.getSession(req.params.id);
5236
+ if (!session) return res.status(404).json({ error: "Session not found" });
5237
+ const { branch } = req.body;
5238
+ if (!branch || typeof branch !== "string") return res.status(400).json({ error: "branch is required" });
5239
+ const effectiveWorkdir = session.workspace ?? session.workdir;
5240
+ const { checkoutBranch, validateRef } = await import("./workspace-6QTTUMMW.js");
5241
+ try {
5242
+ await validateRef(effectiveWorkdir, branch);
5243
+ await checkoutBranch(effectiveWorkdir, branch);
5244
+ res.json({ branch });
5245
+ } catch (err) {
5246
+ res.status(400).json({ error: err instanceof Error ? err.message : "Failed to checkout branch" });
5247
+ }
5248
+ });
5249
+ app.post("/api/sessions/:id/checkout-new", async (req, res) => {
5250
+ const session = sessionManager.getSession(req.params.id);
5251
+ if (!session) return res.status(404).json({ error: "Session not found" });
5252
+ const { name } = req.body;
5253
+ if (!name || typeof name !== "string") return res.status(400).json({ error: "name is required" });
5254
+ const effectiveWorkdir = session.workspace ?? session.workdir;
5255
+ const { createBranch, validateRef } = await import("./workspace-6QTTUMMW.js");
5256
+ try {
5257
+ await validateRef(effectiveWorkdir, name);
5258
+ await createBranch(effectiveWorkdir, name);
5259
+ res.json({ branch: name });
5260
+ } catch (err) {
5261
+ res.status(400).json({ error: err instanceof Error ? err.message : "Failed to create branch" });
5262
+ }
5263
+ });
5085
5264
  app.get("/api/projects/:id/workspaces", async (req, res) => {
5086
5265
  const { getProject: getProject2 } = await import("./projects-LHRNCUGW.js");
5087
5266
  const project = getProject2(req.params.id);
5088
5267
  if (!project) return res.status(404).json({ error: "Project not found" });
5089
- const { listWorkspaces } = await import("./workspace-ET3FHZYN.js");
5268
+ const { listWorkspaces } = await import("./workspace-6QTTUMMW.js");
5090
5269
  const all = await listWorkspaces(project.name);
5091
5270
  const workspacesList = all.filter((ws) => ws.path !== project.workdir);
5092
5271
  res.json({ workspaces: workspacesList });
@@ -5151,7 +5330,7 @@ async function createServerHandle(config3) {
5151
5330
  app.get("/api/sessions/:id", async (req, res) => {
5152
5331
  const { getEventStore: getEventStore2 } = await import("./events-AEQ6GQXI.js");
5153
5332
  const { buildMessagesFromStoredEvents } = await import("./folding-ONH3D4OK.js");
5154
- const { getPendingQuestionsForSession } = await import("./tools-X52DFUS6.js");
5333
+ const { getPendingQuestionsForSession } = await import("./tools-APW7MY4E.js");
5155
5334
  const session = sessionManager.getSession(req.params.id);
5156
5335
  if (!session) {
5157
5336
  return res.status(404).json({ error: "Session not found" });
@@ -5170,8 +5349,8 @@ async function createServerHandle(config3) {
5170
5349
  if (!session) {
5171
5350
  return res.status(404).json({ error: "Session not found" });
5172
5351
  }
5173
- const { stopSessionExecution } = await import("./chat-handler-LK5RUAGO.js");
5174
- const { cancelQuestionsForSession, cancelPathConfirmationsForSession } = await import("./tools-X52DFUS6.js");
5352
+ const { stopSessionExecution } = await import("./chat-handler-ETKFIHUO.js");
5353
+ const { cancelQuestionsForSession, cancelPathConfirmationsForSession } = await import("./tools-APW7MY4E.js");
5175
5354
  sessionManager.clearMessageQueue(sessionId);
5176
5355
  stopSessionExecution(sessionId, sessionManager);
5177
5356
  abortSession(sessionId);
@@ -5359,7 +5538,7 @@ async function createServerHandle(config3) {
5359
5538
  if (!callId || approved === void 0) {
5360
5539
  return res.status(400).json({ error: "callId and approved are required" });
5361
5540
  }
5362
- const { providePathConfirmation } = await import("./tools-X52DFUS6.js");
5541
+ const { providePathConfirmation } = await import("./tools-APW7MY4E.js");
5363
5542
  const result = providePathConfirmation(callId, approved, alwaysAllow);
5364
5543
  if (!result.found) {
5365
5544
  return res.status(404).json({ error: "No pending path confirmation with that ID" });
@@ -5367,7 +5546,7 @@ async function createServerHandle(config3) {
5367
5546
  const { getEventStore: getEventStore2 } = await import("./events-AEQ6GQXI.js");
5368
5547
  const { buildMessagesFromStoredEvents, foldPendingConfirmations } = await import("./folding-ONH3D4OK.js");
5369
5548
  const { createSessionStateMessage } = await import("./protocol-4QFU7WAI.js");
5370
- const { getPendingQuestionsForSession } = await import("./tools-X52DFUS6.js");
5549
+ const { getPendingQuestionsForSession } = await import("./tools-APW7MY4E.js");
5371
5550
  const eventStore = getEventStore2();
5372
5551
  const events = eventStore.getEvents(sessionId);
5373
5552
  const messages = buildMessagesFromStoredEvents(events);
@@ -5388,7 +5567,7 @@ async function createServerHandle(config3) {
5388
5567
  if (!skip && typeof answer !== "string") {
5389
5568
  return res.status(400).json({ error: "answer is required when not skipping" });
5390
5569
  }
5391
- const { provideAnswer } = await import("./tools-X52DFUS6.js");
5570
+ const { provideAnswer } = await import("./tools-APW7MY4E.js");
5392
5571
  const found = provideAnswer(callId, answer ?? "", skip ?? false);
5393
5572
  if (!found) {
5394
5573
  return res.status(404).json({ error: "No pending question with that ID" });
@@ -5448,7 +5627,7 @@ async function createServerHandle(config3) {
5448
5627
  backend: activeProvider?.backend ?? llmClient.getBackend(),
5449
5628
  model: llmClient.getModel()
5450
5629
  };
5451
- const { runAgentTurn, TurnMetrics } = await import("./orchestrator-RTZVMWQV.js");
5630
+ const { runAgentTurn, TurnMetrics } = await import("./orchestrator-O2YAAZ3U.js");
5452
5631
  runAgentTurn(
5453
5632
  {
5454
5633
  sessionManager,
@@ -5486,8 +5665,8 @@ async function createServerHandle(config3) {
5486
5665
  if (!session) {
5487
5666
  return res.status(404).json({ error: "Session not found" });
5488
5667
  }
5489
- const { stopSessionExecution } = await import("./chat-handler-LK5RUAGO.js");
5490
- const { cancelQuestionsForSession, cancelPathConfirmationsForSession } = await import("./tools-X52DFUS6.js");
5668
+ const { stopSessionExecution } = await import("./chat-handler-ETKFIHUO.js");
5669
+ const { cancelQuestionsForSession, cancelPathConfirmationsForSession } = await import("./tools-APW7MY4E.js");
5491
5670
  const queuedMessages = sessionManager.getQueueState(sessionId);
5492
5671
  sessionManager.clearMessageQueue(sessionId);
5493
5672
  stopSessionExecution(sessionId, sessionManager);
@@ -5518,10 +5697,13 @@ async function createServerHandle(config3) {
5518
5697
  if (!session) {
5519
5698
  return res.status(404).json({ error: "Session not found" });
5520
5699
  }
5521
- const { messageId } = req.body;
5700
+ const { messageId, content } = req.body;
5522
5701
  if (typeof messageId !== "string" || !messageId) {
5523
5702
  return res.status(400).json({ error: "messageId is required" });
5524
5703
  }
5704
+ if (content !== void 0 && (typeof content !== "string" || !content.trim())) {
5705
+ return res.status(400).json({ error: "content must be a non-empty string if provided" });
5706
+ }
5525
5707
  const { getEventStore: getEventStore2 } = await import("./events-AEQ6GQXI.js");
5526
5708
  const { buildMessagesFromStoredEvents } = await import("./folding-ONH3D4OK.js");
5527
5709
  const eventStore = getEventStore2();
@@ -5537,7 +5719,7 @@ async function createServerHandle(config3) {
5537
5719
  }
5538
5720
  const { truncateSessionMessages } = await import("./events-AEQ6GQXI.js");
5539
5721
  truncateSessionMessages(sessionId, msgIndex - 1);
5540
- sessionManager.queueMessage(sessionId, "asap", msg.content, msg.attachments, msg.messageKind);
5722
+ sessionManager.queueMessage(sessionId, "asap", content ?? msg.content, msg.attachments, msg.messageKind);
5541
5723
  res.json({ success: true });
5542
5724
  });
5543
5725
  app.post("/api/sessions/:id/chat", async (req, res) => {
@@ -6110,7 +6292,7 @@ async function createServerHandle(config3) {
6110
6292
  });
6111
6293
  async function rebuildMcpTools() {
6112
6294
  const { createMcpTools: createMcpTools2 } = await import("./tool-adapter-MOGEI6TP.js");
6113
- const { setMcpTools: setMcpTools2 } = await import("./tools-X52DFUS6.js");
6295
+ const { setMcpTools: setMcpTools2 } = await import("./tools-APW7MY4E.js");
6114
6296
  const mcpTools = createMcpTools2(mcpManager);
6115
6297
  setMcpTools2(mcpTools);
6116
6298
  }
@@ -6494,7 +6676,7 @@ async function createServerHandle(config3) {
6494
6676
  const state = sessionManager.getContextState(sessionId);
6495
6677
  wssExports.broadcastForSession(sessionId, createContextStateMessage(state));
6496
6678
  });
6497
- const { QueueProcessor } = await import("./processor-3O26I7DO.js");
6679
+ const { QueueProcessor } = await import("./processor-FKAS3K4L.js");
6498
6680
  const queueProcessor = new QueueProcessor({
6499
6681
  sessionManager,
6500
6682
  providerManager,
@@ -6574,4 +6756,4 @@ export {
6574
6756
  createServerHandle,
6575
6757
  createServer
6576
6758
  };
6577
- //# sourceMappingURL=chunk-EYXDM2FY.js.map
6759
+ //# sourceMappingURL=chunk-QFR6JF32.js.map