agent-workbench 0.4.1 → 0.5.1

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/server.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // packages/server/src/index.ts
4
4
  import { existsSync as existsSync4 } from "node:fs";
5
5
  import { createServer } from "node:http";
6
- import path49 from "node:path";
6
+ import path51 from "node:path";
7
7
  import { fileURLToPath as fileURLToPath2 } from "node:url";
8
8
  import express from "express";
9
9
 
@@ -153,7 +153,8 @@ function parseSessionSummary(value) {
153
153
  var CONVERSATION_ROLES = ["user", "assistant"];
154
154
  var CONVERSATION_IMAGE_SOURCES = [
155
155
  "content",
156
- "attachment"
156
+ "attachment",
157
+ "pasted"
157
158
  ];
158
159
  var CONVERSATION_NOTICES = [
159
160
  "compacted",
@@ -277,7 +278,7 @@ function parseConversationPart(value) {
277
278
  const index = asFiniteNumber(record["index"]);
278
279
  const mediaType = asString(record["mediaType"]);
279
280
  if (index === null || mediaType === null) return null;
280
- const source = record["source"] === "attachment" ? "attachment" : "content";
281
+ const source = asLiteral(record["source"], CONVERSATION_IMAGE_SOURCES) ?? "content";
281
282
  return { kind: "image", index, mediaType, source };
282
283
  }
283
284
  case "notice": {
@@ -680,6 +681,38 @@ function cleanDeviceLabel(label) {
680
681
  return label.replace(/\s+/g, " ").trim().slice(0, REMOTE_DEVICE_LABEL_MAX_CHARS);
681
682
  }
682
683
 
684
+ // packages/shared/src/settings.ts
685
+ var CONSOLE_SHELL_IDS = [
686
+ "auto",
687
+ "pwsh",
688
+ "powershell",
689
+ "cmd",
690
+ "git-bash",
691
+ "login",
692
+ "bash",
693
+ "sh",
694
+ "zsh"
695
+ ];
696
+ function asConsoleShellId(value) {
697
+ return asLiteral(value, CONSOLE_SHELL_IDS);
698
+ }
699
+ function asAgentIdList(value) {
700
+ if (!Array.isArray(value)) return null;
701
+ return [...new Set(value.filter((item) => typeof item === "string" && isAgentId(item)))];
702
+ }
703
+ function parseAppSettingsChange(record) {
704
+ const rawShell = record["consoleShell"];
705
+ const rawAgents = record["disabledAgents"];
706
+ const consoleShell = rawShell === void 0 ? void 0 : asConsoleShellId(rawShell);
707
+ const disabledAgents = rawAgents === void 0 ? void 0 : asAgentIdList(rawAgents);
708
+ if (consoleShell === null || disabledAgents === null) return null;
709
+ if (consoleShell === void 0 && disabledAgents === void 0) return null;
710
+ return {
711
+ ...consoleShell !== void 0 ? { consoleShell } : {},
712
+ ...disabledAgents !== void 0 ? { disabledAgents } : {}
713
+ };
714
+ }
715
+
683
716
  // packages/shared/src/protocol.ts
684
717
  var PROTOCOL_VERSION = 8;
685
718
  var WS_PATH = "/ws";
@@ -1070,6 +1103,10 @@ function parseClientMessage(raw) {
1070
1103
  const draft = parseComposerDraft(record["draft"]);
1071
1104
  return terminalId === null || draft === null ? null : { type: "composer.draft", terminalId, draft };
1072
1105
  }
1106
+ case "settings.update": {
1107
+ const change = parseAppSettingsChange(record);
1108
+ return change === null ? null : { type: "settings.update", ...change };
1109
+ }
1073
1110
  default:
1074
1111
  return null;
1075
1112
  }
@@ -4183,7 +4220,7 @@ function createAntigravityAdapter(options = {}) {
4183
4220
 
4184
4221
  // packages/server/src/agents/claude-code/index.ts
4185
4222
  import { homedir as homedir5 } from "node:os";
4186
- import path19 from "node:path";
4223
+ import path21 from "node:path";
4187
4224
 
4188
4225
  // packages/server/src/agents/claude-code/agent-defaults.ts
4189
4226
  import { readFile as readFile6 } from "node:fs/promises";
@@ -4519,8 +4556,8 @@ function claudeCodeEnvironment(base) {
4519
4556
  }
4520
4557
 
4521
4558
  // packages/server/src/agents/claude-code/history.ts
4522
- import { readdir as readdir7, stat as stat11 } from "node:fs/promises";
4523
- import path18 from "node:path";
4559
+ import { readdir as readdir7, stat as stat12 } from "node:fs/promises";
4560
+ import path20 from "node:path";
4524
4561
 
4525
4562
  // packages/server/src/agents/claude-code/plans-store.ts
4526
4563
  import { readFile as readFile8, stat as stat9 } from "node:fs/promises";
@@ -4694,10 +4731,30 @@ async function readPlan(target, ref) {
4694
4731
  }
4695
4732
 
4696
4733
  // packages/server/src/agents/claude-code/session-follower.ts
4697
- import path17 from "node:path";
4734
+ import path19 from "node:path";
4698
4735
 
4699
4736
  // packages/server/src/agents/claude-code/jsonl-events.ts
4737
+ import path17 from "node:path";
4738
+
4739
+ // packages/server/src/paste-paths.ts
4740
+ import { tmpdir as tmpdir3 } from "node:os";
4700
4741
  import path16 from "node:path";
4742
+ function pasteRoot() {
4743
+ return path16.join(tmpdir3(), "agent-workbench", "pasted");
4744
+ }
4745
+ var PASTED_IMAGE = /(?:^|[\\/])agent-workbench[\\/]pasted[\\/][A-Za-z0-9_-]+[\\/]pegada-\d+-[0-9a-f]{8}\.(png|jpg|gif|webp)$/i;
4746
+ var MEDIA_TYPES = {
4747
+ png: "image/png",
4748
+ jpg: "image/jpeg",
4749
+ gif: "image/gif",
4750
+ webp: "image/webp"
4751
+ };
4752
+ function pastedImageMediaType(filePath) {
4753
+ const extension = PASTED_IMAGE.exec(filePath)?.[1];
4754
+ return extension === void 0 ? null : MEDIA_TYPES[extension.toLowerCase()] ?? null;
4755
+ }
4756
+
4757
+ // packages/server/src/agents/claude-code/jsonl-events.ts
4701
4758
  var HARNESS_TAGS = [
4702
4759
  "system-reminder",
4703
4760
  "task-notification",
@@ -4880,9 +4937,18 @@ function toQueuedUserEvent(record, lineNumber, limits) {
4880
4937
  if (!isHumanOrigin(attachmentRecord["origin"])) return null;
4881
4938
  const prompt = attachmentRecord["prompt"];
4882
4939
  if (typeof prompt !== "string") return null;
4883
- const cleaned = cleanUserText(prompt);
4884
- if (cleaned.length === 0) return null;
4885
- const { text, truncated } = cut(cleaned, limits.textMaxChars);
4940
+ const { text: cleaned, images } = takePastedImages(cleanUserText(prompt));
4941
+ const parts = images.map((image, index) => ({
4942
+ kind: "image",
4943
+ index,
4944
+ mediaType: image.mediaType,
4945
+ source: "pasted"
4946
+ }));
4947
+ if (cleaned.length > 0) {
4948
+ const { text, truncated } = cut(cleaned, limits.textMaxChars);
4949
+ parts.push({ kind: "text", text, truncated });
4950
+ }
4951
+ if (parts.length === 0) return null;
4886
4952
  const uuid = record["uuid"];
4887
4953
  return {
4888
4954
  eventId: typeof uuid === "string" && uuid.length > 0 ? uuid : `line-${lineNumber}`,
@@ -4890,7 +4956,7 @@ function toQueuedUserEvent(record, lineNumber, limits) {
4890
4956
  // El `timestamp` de la linea es el de cuando se escribio, no el de cuando
4891
4957
  // se absorbio. Es el que corresponde: es cuando el usuario lo mando.
4892
4958
  at: readTimestamp(record["timestamp"] ?? attachmentRecord["timestamp"]),
4893
- parts: [{ kind: "text", text, truncated }],
4959
+ parts,
4894
4960
  model: null,
4895
4961
  usage: null,
4896
4962
  effort: null,
@@ -4956,6 +5022,30 @@ function stripFileReference(text, filename) {
4956
5022
  }
4957
5023
  return text;
4958
5024
  }
5025
+ function sameFilePath(a, b) {
5026
+ return normalizePath(a) === normalizePath(b);
5027
+ }
5028
+ function takePastedImages(text) {
5029
+ const images = [];
5030
+ const tokens = /(^|\s)@(?:"([^"]*)"|(\S+))/g;
5031
+ let match = tokens.exec(text);
5032
+ while (match !== null) {
5033
+ const raw = match[2] ?? match[3] ?? "";
5034
+ const mediaType = pastedImageMediaType(raw);
5035
+ if (mediaType !== null) images.push({ path: raw, mediaType });
5036
+ match = tokens.exec(text);
5037
+ }
5038
+ let rest = text;
5039
+ for (const image of images) rest = stripFileReference(rest, image.path);
5040
+ return { text: rest, images };
5041
+ }
5042
+ function queuedPastedImages(record) {
5043
+ const attachment = recordOf(record["attachment"]);
5044
+ if (attachment === null || attachment["type"] !== "queued_command") return [];
5045
+ const prompt = attachment["prompt"];
5046
+ if (typeof prompt !== "string") return [];
5047
+ return takePastedImages(cleanUserText(prompt)).images.map((image) => image.path);
5048
+ }
4959
5049
  function toConversationEvent(record, lineNumber, limits = TRANSPORT_LIMITS) {
4960
5050
  const type = record["type"];
4961
5051
  if (record["isMeta"] === true) return null;
@@ -5033,7 +5123,7 @@ function documentRefOf(value, target) {
5033
5123
  if (!value.toLowerCase().endsWith(".md")) return null;
5034
5124
  if (value.includes("\0")) return null;
5035
5125
  if (target.cwd.length === 0) return null;
5036
- const absolute = path16.isAbsolute(value) ? value : path16.resolve(target.cwd, value);
5126
+ const absolute = path17.isAbsolute(value) ? value : path17.resolve(target.cwd, value);
5037
5127
  if (target.sessionId.length > 0) {
5038
5128
  const inScratch = relativeInside(scratchRoot(target.cwd, target.sessionId), absolute);
5039
5129
  if (inScratch !== null) return `tmp:${inScratch}`;
@@ -5042,15 +5132,15 @@ function documentRefOf(value, target) {
5042
5132
  return inProject === null ? null : `proj:${inProject}`;
5043
5133
  }
5044
5134
  function relativeInside(root, absolute) {
5045
- const relative = path16.relative(root, absolute);
5135
+ const relative = path17.relative(root, absolute);
5046
5136
  if (relative.length === 0) return null;
5047
- if (relative.startsWith("..") || path16.isAbsolute(relative)) {
5137
+ if (relative.startsWith("..") || path17.isAbsolute(relative)) {
5048
5138
  if (process.platform !== "win32") return null;
5049
- const lower = path16.relative(root.toLowerCase(), absolute.toLowerCase());
5050
- if (lower.length === 0 || lower.startsWith("..") || path16.isAbsolute(lower)) return null;
5051
- return lower.split(path16.sep).join("/");
5139
+ const lower = path17.relative(root.toLowerCase(), absolute.toLowerCase());
5140
+ if (lower.length === 0 || lower.startsWith("..") || path17.isAbsolute(lower)) return null;
5141
+ return lower.split(path17.sep).join("/");
5052
5142
  }
5053
- return relative.split(path16.sep).join("/");
5143
+ return relative.split(path17.sep).join("/");
5054
5144
  }
5055
5145
  function planFileNameOf(value) {
5056
5146
  if (typeof value !== "string" || value.length === 0) return null;
@@ -5600,13 +5690,50 @@ var ConversationFollower = class {
5600
5690
  };
5601
5691
 
5602
5692
  // packages/server/src/agents/claude-code/conversation-image.ts
5603
- import { open as open5 } from "node:fs/promises";
5693
+ import { open as open5, readFile as readFile9, stat as stat10 } from "node:fs/promises";
5694
+ import path18 from "node:path";
5695
+
5696
+ // packages/server/src/image-signature.ts
5697
+ var FORMATS = [
5698
+ {
5699
+ format: { ext: "png", mediaType: "image/png" },
5700
+ test: (b) => b.length > 8 && b.subarray(0, 8).equals(Buffer.from("89504e470d0a1a0a", "hex"))
5701
+ },
5702
+ {
5703
+ format: { ext: "jpg", mediaType: "image/jpeg" },
5704
+ test: (b) => b.length > 3 && b[0] === 255 && b[1] === 216 && b[2] === 255
5705
+ },
5706
+ {
5707
+ format: { ext: "gif", mediaType: "image/gif" },
5708
+ test: (b) => b.length > 6 && b.subarray(0, 4).toString("ascii") === "GIF8"
5709
+ },
5710
+ {
5711
+ format: { ext: "webp", mediaType: "image/webp" },
5712
+ test: (b) => b.length > 12 && b.subarray(0, 4).toString("ascii") === "RIFF" && b.subarray(8, 12).toString("ascii") === "WEBP"
5713
+ }
5714
+ ];
5715
+ var IMAGE_MEDIA_TYPES = new Set(
5716
+ FORMATS.map((entry) => entry.format.mediaType)
5717
+ );
5718
+ function detectImageFormat(bytes) {
5719
+ return FORMATS.find((entry) => entry.test(bytes))?.format ?? null;
5720
+ }
5721
+
5722
+ // packages/server/src/agents/claude-code/conversation-image.ts
5604
5723
  var CHUNK_SIZE2 = 256 * 1024;
5605
5724
  var NEWLINE2 = 10;
5606
5725
  var MAX_IMAGE_BYTES = MAX_SUBMIT_IMAGE_BYTES;
5607
- async function loadConversationImage(filePath, eventId, index, source = "content") {
5726
+ async function loadConversationImage(filePath, eventId, index, source = "content", pastedDir = pasteRoot()) {
5608
5727
  const handle = await open5(filePath, "r").catch(() => null);
5609
5728
  if (handle === null) return null;
5729
+ try {
5730
+ if (source === "pasted") return await findPastedImage(linesOf(handle), eventId, index, pastedDir);
5731
+ return await findInLines(linesOf(handle), eventId, index, source);
5732
+ } finally {
5733
+ await handle.close();
5734
+ }
5735
+ }
5736
+ async function findInLines(lines, eventId, index, source) {
5610
5737
  let seen = 0;
5611
5738
  const chain = /* @__PURE__ */ new Set([eventId]);
5612
5739
  const consider = (line) => {
@@ -5629,56 +5756,126 @@ async function loadConversationImage(filePath, eventId, index, source = "content
5629
5756
  if (typeof ownId === "string" && ownId.length > 0) chain.add(ownId);
5630
5757
  return seen++ === index ? found : null;
5631
5758
  };
5632
- try {
5633
- const buffer = Buffer.allocUnsafe(CHUNK_SIZE2);
5634
- let pending = Buffer.alloc(0);
5635
- let offset = 0;
5636
- for (; ; ) {
5637
- const { bytesRead } = await handle.read(buffer, 0, CHUNK_SIZE2, offset);
5638
- if (bytesRead === 0) break;
5639
- offset += bytesRead;
5640
- const combined = Buffer.concat([pending, buffer.subarray(0, bytesRead)]);
5641
- let start = 0;
5642
- let newlineIndex = combined.indexOf(NEWLINE2, start);
5643
- while (newlineIndex !== -1) {
5644
- const line = combined.subarray(start, newlineIndex).toString("utf8");
5645
- start = newlineIndex + 1;
5646
- newlineIndex = combined.indexOf(NEWLINE2, start);
5647
- const found = consider(line);
5648
- if (found !== null) return found;
5649
- }
5650
- pending = Buffer.from(combined.subarray(start));
5759
+ for await (const line of lines) {
5760
+ const found = consider(line);
5761
+ if (found !== null) return found;
5762
+ }
5763
+ return null;
5764
+ }
5765
+ async function* linesOf(handle) {
5766
+ const buffer = Buffer.allocUnsafe(CHUNK_SIZE2);
5767
+ let pending = Buffer.alloc(0);
5768
+ let offset = 0;
5769
+ for (; ; ) {
5770
+ const { bytesRead } = await handle.read(buffer, 0, CHUNK_SIZE2, offset);
5771
+ if (bytesRead === 0) break;
5772
+ offset += bytesRead;
5773
+ const combined = Buffer.concat([pending, buffer.subarray(0, bytesRead)]);
5774
+ let start = 0;
5775
+ let newlineIndex = combined.indexOf(NEWLINE2, start);
5776
+ while (newlineIndex !== -1) {
5777
+ yield combined.subarray(start, newlineIndex).toString("utf8");
5778
+ start = newlineIndex + 1;
5779
+ newlineIndex = combined.indexOf(NEWLINE2, start);
5780
+ }
5781
+ pending = Buffer.from(combined.subarray(start));
5782
+ }
5783
+ if (pending.length > 0) yield pending.toString("utf8");
5784
+ }
5785
+ async function findPastedImage(lines, eventId, index, pastedDir) {
5786
+ let target = null;
5787
+ let readId = null;
5788
+ for await (const line of lines) {
5789
+ if (target === null) {
5790
+ if (!line.includes(eventId)) continue;
5791
+ const record = parseJsonlLine(line);
5792
+ if (record === null || record["uuid"] !== eventId) continue;
5793
+ target = queuedPastedImages(record)[index] ?? null;
5794
+ if (target === null) return null;
5795
+ const fromDisk = await readPastedFile(target, pastedDir);
5796
+ if (fromDisk !== null) return fromDisk;
5797
+ continue;
5798
+ }
5799
+ if (readId === null) {
5800
+ if (line.includes('"Read"')) readId = readToolUseOf(parseJsonlLine(line), target);
5801
+ continue;
5651
5802
  }
5652
- if (pending.length > 0) return consider(pending.toString("utf8"));
5803
+ if (!line.includes(readId)) continue;
5804
+ const result = toolResultOf(parseJsonlLine(line), readId);
5805
+ if (result === null) continue;
5806
+ const content = result["content"];
5807
+ const image = Array.isArray(content) ? firstImage(content) : null;
5808
+ if (image !== null) return image;
5809
+ readId = null;
5810
+ }
5811
+ return null;
5812
+ }
5813
+ async function readPastedFile(target, pastedDir) {
5814
+ try {
5815
+ const inside = await resolveInside(pastedDir, path18.relative(pastedDir, target), { mustExist: true });
5816
+ const info = await stat10(inside);
5817
+ if (!info.isFile() || info.size === 0 || info.size > MAX_IMAGE_BYTES) return null;
5818
+ const bytes = await readFile9(inside);
5819
+ const format = detectImageFormat(bytes);
5820
+ return format === null ? null : { mediaType: format.mediaType, data: bytes.toString("base64") };
5821
+ } catch {
5653
5822
  return null;
5654
- } finally {
5655
- await handle.close();
5656
5823
  }
5657
5824
  }
5825
+ function readToolUseOf(record, target) {
5826
+ if (record === null || record["type"] !== "assistant") return null;
5827
+ const content = recordOf(record["message"])?.["content"];
5828
+ if (!Array.isArray(content)) return null;
5829
+ for (const block of content) {
5830
+ const tool = recordOf(block);
5831
+ if (tool === null || tool["type"] !== "tool_use" || tool["name"] !== "Read") continue;
5832
+ const id = tool["id"];
5833
+ const filePath = recordOf(tool["input"])?.["file_path"];
5834
+ if (typeof id === "string" && id.length > 0 && typeof filePath === "string" && sameFilePath(filePath, target)) {
5835
+ return id;
5836
+ }
5837
+ }
5838
+ return null;
5839
+ }
5840
+ function toolResultOf(record, toolUseId) {
5841
+ if (record === null || record["type"] !== "user") return null;
5842
+ const content = recordOf(record["message"])?.["content"];
5843
+ if (!Array.isArray(content)) return null;
5844
+ for (const block of content) {
5845
+ const result = recordOf(block);
5846
+ if (result !== null && result["type"] === "tool_result" && result["tool_use_id"] === toolUseId) return result;
5847
+ }
5848
+ return null;
5849
+ }
5658
5850
  function tooBig(data) {
5659
5851
  return data.length > MAX_IMAGE_BYTES * 1.4;
5660
5852
  }
5661
- function imageFromContent(record, eventId, index) {
5662
- if (record["uuid"] !== eventId) return null;
5663
- const message = record["message"];
5664
- if (typeof message !== "object" || message === null) return null;
5665
- const content = message["content"];
5666
- if (!Array.isArray(content)) return null;
5667
- const block = content[index];
5668
- if (typeof block !== "object" || block === null) return null;
5669
- const blockRecord = block;
5670
- if (blockRecord["type"] !== "image") return null;
5671
- const source = blockRecord["source"];
5672
- if (typeof source !== "object" || source === null) return null;
5673
- const sourceRecord = source;
5674
- const data = sourceRecord["data"];
5675
- const mediaType = sourceRecord["media_type"];
5853
+ function imageFromBlock(value) {
5854
+ const block = recordOf(value);
5855
+ if (block === null || block["type"] !== "image") return null;
5856
+ const source = recordOf(block["source"]);
5857
+ if (source === null) return null;
5858
+ const data = source["data"];
5859
+ const mediaType = source["media_type"];
5676
5860
  if (typeof data !== "string" || data.length === 0 || tooBig(data)) return null;
5677
5861
  return {
5678
5862
  mediaType: typeof mediaType === "string" ? mediaType : "image/png",
5679
5863
  data
5680
5864
  };
5681
5865
  }
5866
+ function firstImage(blocks) {
5867
+ for (const block of blocks) {
5868
+ const image = imageFromBlock(block);
5869
+ if (image !== null) return image;
5870
+ }
5871
+ return null;
5872
+ }
5873
+ function imageFromContent(record, eventId, index) {
5874
+ if (record["uuid"] !== eventId) return null;
5875
+ const content = recordOf(record["message"])?.["content"];
5876
+ if (!Array.isArray(content)) return null;
5877
+ return imageFromBlock(content[index]);
5878
+ }
5682
5879
  function imageFromAttachment(record) {
5683
5880
  if (record["type"] !== "attachment") return null;
5684
5881
  const attachment = record["attachment"];
@@ -5750,7 +5947,7 @@ var ClaudeCodeSessionFollower = class {
5750
5947
  }
5751
5948
  noticeChange(filePath) {
5752
5949
  if (isSameFilePath(this.inner.filePath, filePath, process.platform)) return true;
5753
- if (this.inner.getState() === "waiting" && path17.basename(filePath).toLowerCase() === `${this.sessionId.toLowerCase()}.jsonl`) {
5950
+ if (this.inner.getState() === "waiting" && path19.basename(filePath).toLowerCase() === `${this.sessionId.toLowerCase()}.jsonl`) {
5754
5951
  console.warn(
5755
5952
  `[conversation] session ${this.sessionId.slice(0, 8)} wrote to ${filePath}, not to the computed path; moving there.`
5756
5953
  );
@@ -5797,7 +5994,7 @@ var ClaudeCodeSessionFollower = class {
5797
5994
  };
5798
5995
 
5799
5996
  // packages/server/src/agents/claude-code/session-scan.ts
5800
- import { stat as stat10 } from "node:fs/promises";
5997
+ import { stat as stat11 } from "node:fs/promises";
5801
5998
  var HEAD_MAX_LINES = 40;
5802
5999
  var HEAD_MAX_BYTES = 512 * 1024;
5803
6000
  var TAIL_MAX_BYTES = 64 * 1024;
@@ -5822,7 +6019,7 @@ function isRealUserMessage(record) {
5822
6019
  return true;
5823
6020
  }
5824
6021
  async function scanSessionFile(filePath, sessionId) {
5825
- const info = await stat10(filePath);
6022
+ const info = await stat11(filePath);
5826
6023
  const [headLines, tailLines] = await Promise.all([
5827
6024
  readHeadLines(filePath, { maxLines: HEAD_MAX_LINES, maxBytes: HEAD_MAX_BYTES }),
5828
6025
  readTailLines(filePath, { maxBytes: TAIL_MAX_BYTES })
@@ -5895,7 +6092,7 @@ async function scanSessionFile(filePath, sessionId) {
5895
6092
  var SESSION_EXTENSION = /\.jsonl$/i;
5896
6093
  async function pathExists(candidate) {
5897
6094
  try {
5898
- await stat11(candidate);
6095
+ await stat12(candidate);
5899
6096
  return true;
5900
6097
  } catch {
5901
6098
  return false;
@@ -5904,8 +6101,8 @@ async function pathExists(candidate) {
5904
6101
  function toItem(ref, info) {
5905
6102
  return {
5906
6103
  ref,
5907
- sessionId: path18.basename(ref).replace(SESSION_EXTENSION, ""),
5908
- group: path18.basename(path18.dirname(ref)),
6104
+ sessionId: path20.basename(ref).replace(SESSION_EXTENSION, ""),
6105
+ group: path20.basename(path20.dirname(ref)),
5909
6106
  mtimeMs: info.mtimeMs,
5910
6107
  sizeBytes: info.size
5911
6108
  };
@@ -5935,14 +6132,14 @@ function createClaudeCodeHistory(variants) {
5935
6132
  for (const slug of directories) {
5936
6133
  let files;
5937
6134
  try {
5938
- files = (await readdir7(path18.join(root, slug))).filter((name) => name.endsWith(".jsonl"));
6135
+ files = (await readdir7(path20.join(root, slug))).filter((name) => name.endsWith(".jsonl"));
5939
6136
  } catch {
5940
6137
  continue;
5941
6138
  }
5942
6139
  for (const file of files) {
5943
- const ref = path18.join(root, slug, file);
6140
+ const ref = path20.join(root, slug, file);
5944
6141
  try {
5945
- items.push(toItem(ref, await stat11(ref)));
6142
+ items.push(toItem(ref, await stat12(ref)));
5946
6143
  } catch {
5947
6144
  }
5948
6145
  }
@@ -5950,15 +6147,15 @@ function createClaudeCodeHistory(variants) {
5950
6147
  return items;
5951
6148
  },
5952
6149
  async changedRefs(filePath) {
5953
- const relative = path18.relative(sessionsRoot(), filePath);
5954
- const [slug] = relative.split(path18.sep);
6150
+ const relative = path20.relative(sessionsRoot(), filePath);
6151
+ const [slug] = relative.split(path20.sep);
5955
6152
  if (slug === void 0 || slug.length === 0 || slug.startsWith("..")) return null;
5956
6153
  if (!filePath.endsWith(".jsonl")) return null;
5957
6154
  return [filePath];
5958
6155
  },
5959
6156
  async item(ref) {
5960
6157
  try {
5961
- return toItem(ref, await stat11(ref));
6158
+ return toItem(ref, await stat12(ref));
5962
6159
  } catch {
5963
6160
  return null;
5964
6161
  }
@@ -6145,18 +6342,18 @@ function createClaudeCodeAdapter() {
6145
6342
  status,
6146
6343
  defaults: (cwd) => readAgentDefaults(cwd),
6147
6344
  /** La carpeta de la CLI. No se lista ni se entra: regla 2.1. */
6148
- protectedDirs: () => [path19.join(homedir5(), ".claude")],
6345
+ protectedDirs: () => [path21.join(homedir5(), ".claude")],
6149
6346
  dispose: () => status.dispose()
6150
6347
  };
6151
6348
  }
6152
6349
 
6153
6350
  // packages/server/src/agents/codex/index.ts
6154
6351
  import { homedir as homedir7 } from "node:os";
6155
- import path25 from "node:path";
6352
+ import path27 from "node:path";
6156
6353
 
6157
6354
  // packages/server/src/agents/codex/discovery.ts
6158
- import { stat as stat12 } from "node:fs/promises";
6159
- import path22 from "node:path";
6355
+ import { stat as stat13 } from "node:fs/promises";
6356
+ import path24 from "node:path";
6160
6357
 
6161
6358
  // packages/server/src/debug.ts
6162
6359
  var enabled = process.env["AGENT_WORKBENCH_DEBUG"] === "1";
@@ -6167,11 +6364,11 @@ function debugLog(scope, message) {
6167
6364
 
6168
6365
  // packages/server/src/agents/codex/find-rollout.ts
6169
6366
  import { readdir as readdir8 } from "node:fs/promises";
6170
- import path21 from "node:path";
6367
+ import path23 from "node:path";
6171
6368
 
6172
6369
  // packages/server/src/agents/codex/paths.ts
6173
6370
  import { homedir as homedir6 } from "node:os";
6174
- import path20 from "node:path";
6371
+ import path22 from "node:path";
6175
6372
  var INTERACTIVE_SOURCES = ["cli", "vscode"];
6176
6373
  var LISTED_HISTORY_MODES = ["legacy", "paginated"];
6177
6374
  var ROLLOUT_FILE_PATTERN = /^rollout-(\d{4})-(\d{2})-(\d{2})T(\d{2})-(\d{2})-(\d{2})-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i;
@@ -6183,13 +6380,13 @@ function isUuid(value) {
6183
6380
  }
6184
6381
  var warnedHomes = /* @__PURE__ */ new Set();
6185
6382
  function isFullyQualified(candidate) {
6186
- if (!path20.isAbsolute(candidate)) return false;
6383
+ if (!path22.isAbsolute(candidate)) return false;
6187
6384
  return process.platform !== "win32" || /^([a-zA-Z]:[\\/]|[\\/]{2})/.test(candidate);
6188
6385
  }
6189
6386
  function codexHome() {
6190
6387
  const configured = process.env["CODEX_HOME"];
6191
6388
  if (configured === void 0 || configured.length === 0) {
6192
- return path20.join(homedir6(), ".codex");
6389
+ return path22.join(homedir6(), ".codex");
6193
6390
  }
6194
6391
  if (isFullyQualified(configured)) return configured;
6195
6392
  if (!warnedHomes.has(configured)) {
@@ -6202,11 +6399,11 @@ function codexHome() {
6202
6399
  }
6203
6400
  function codexSessionsRoot() {
6204
6401
  const home = codexHome();
6205
- return home === null ? null : path20.join(home, "sessions");
6402
+ return home === null ? null : path22.join(home, "sessions");
6206
6403
  }
6207
6404
  function codexArchivedRoot() {
6208
6405
  const home = codexHome();
6209
- return home === null ? null : path20.join(home, "archived_sessions");
6406
+ return home === null ? null : path22.join(home, "archived_sessions");
6210
6407
  }
6211
6408
  function parseRolloutFileName(name) {
6212
6409
  const match = ROLLOUT_FILE_PATTERN.exec(name);
@@ -6233,7 +6430,7 @@ function localDayFolder(root, epochMs) {
6233
6430
  const year = String(date.getFullYear()).padStart(4, "0");
6234
6431
  const month = String(date.getMonth() + 1).padStart(2, "0");
6235
6432
  const day = String(date.getDate()).padStart(2, "0");
6236
- return path20.join(root, year, month, day);
6433
+ return path22.join(root, year, month, day);
6237
6434
  }
6238
6435
  function localDayFolders(root, fromMs, toMs) {
6239
6436
  const folders = [];
@@ -6265,14 +6462,14 @@ async function numberedFolders(folder, pattern) {
6265
6462
  return (await entriesOf(folder)).filter((entry) => entry.isDirectory() && pattern.test(entry.name)).map((entry) => entry.name).sort();
6266
6463
  }
6267
6464
  async function rolloutFilesIn(folder) {
6268
- return (await entriesOf(folder)).filter((entry) => entry.isFile() && parseRolloutFileName(entry.name) !== null).map((entry) => entry.name).sort().map((name) => path21.join(folder, name));
6465
+ return (await entriesOf(folder)).filter((entry) => entry.isFile() && parseRolloutFileName(entry.name) !== null).map((entry) => entry.name).sort().map((name) => path23.join(folder, name));
6269
6466
  }
6270
6467
  async function walkSessionFiles(root, limit = Number.POSITIVE_INFINITY) {
6271
6468
  const files = [];
6272
6469
  for (const year of await numberedFolders(root, YEAR)) {
6273
- for (const month of await numberedFolders(path21.join(root, year), TWO_DIGITS)) {
6274
- for (const day of await numberedFolders(path21.join(root, year, month), TWO_DIGITS)) {
6275
- for (const file of await rolloutFilesIn(path21.join(root, year, month, day))) {
6470
+ for (const month of await numberedFolders(path23.join(root, year), TWO_DIGITS)) {
6471
+ for (const day of await numberedFolders(path23.join(root, year, month), TWO_DIGITS)) {
6472
+ for (const file of await rolloutFilesIn(path23.join(root, year, month, day))) {
6276
6473
  files.push(file);
6277
6474
  if (files.length >= limit) return files;
6278
6475
  }
@@ -6283,7 +6480,7 @@ async function walkSessionFiles(root, limit = Number.POSITIVE_INFINITY) {
6283
6480
  }
6284
6481
  function matching(files, sessionId) {
6285
6482
  for (const file of files) {
6286
- if (parseRolloutFileName(path21.basename(file))?.sessionId === sessionId) return file;
6483
+ if (parseRolloutFileName(path23.basename(file))?.sessionId === sessionId) return file;
6287
6484
  }
6288
6485
  return null;
6289
6486
  }
@@ -6651,7 +6848,7 @@ var CodexSessionDiscovery = class {
6651
6848
  if (this.seen.has(filePath)) continue;
6652
6849
  let info;
6653
6850
  try {
6654
- info = await stat12(filePath);
6851
+ info = await stat13(filePath);
6655
6852
  } catch {
6656
6853
  continue;
6657
6854
  }
@@ -6690,7 +6887,7 @@ var CodexSessionDiscovery = class {
6690
6887
  this.stopTimerIfIdle();
6691
6888
  }
6692
6889
  async readHead(filePath, size) {
6693
- const name = parseRolloutFileName(path22.basename(filePath));
6890
+ const name = parseRolloutFileName(path24.basename(filePath));
6694
6891
  if (name === null) return { kind: "rejected" };
6695
6892
  let lines;
6696
6893
  try {
@@ -6738,8 +6935,8 @@ var CodexSessionDiscovery = class {
6738
6935
  };
6739
6936
 
6740
6937
  // packages/server/src/agents/codex/history.ts
6741
- import { stat as stat13 } from "node:fs/promises";
6742
- import path24 from "node:path";
6938
+ import { stat as stat14 } from "node:fs/promises";
6939
+ import path26 from "node:path";
6743
6940
 
6744
6941
  // packages/server/src/agents/codex/rollout-scan.ts
6745
6942
  var TAIL_MAX_BYTES2 = 256 * 1024;
@@ -6813,7 +7010,7 @@ async function scanRollout(item) {
6813
7010
  }
6814
7011
 
6815
7012
  // packages/server/src/agents/codex/session-follower.ts
6816
- import path23 from "node:path";
7013
+ import path25 from "node:path";
6817
7014
 
6818
7015
  // packages/server/src/agents/codex/rollout-image.ts
6819
7016
  import { createReadStream } from "node:fs";
@@ -7181,7 +7378,7 @@ var CodexSessionFollower = class {
7181
7378
  return own === filePath || normalizeCwdKey(own, process.platform) === normalizeCwdKey(filePath, process.platform);
7182
7379
  }
7183
7380
  if (this.sessionId.length === 0) return false;
7184
- if (parseRolloutFileName(path23.basename(filePath))?.sessionId !== this.sessionId.toLowerCase()) return false;
7381
+ if (parseRolloutFileName(path25.basename(filePath))?.sessionId !== this.sessionId.toLowerCase()) return false;
7185
7382
  this.jsonl.setPath(filePath);
7186
7383
  return true;
7187
7384
  }
@@ -7219,26 +7416,26 @@ var CodexSessionFollower = class {
7219
7416
  // packages/server/src/agents/codex/history.ts
7220
7417
  async function pathExists2(candidate) {
7221
7418
  try {
7222
- await stat13(candidate);
7419
+ await stat14(candidate);
7223
7420
  return true;
7224
7421
  } catch {
7225
7422
  return false;
7226
7423
  }
7227
7424
  }
7228
- var accepts = (filePath) => ROLLOUT_FILE_PATTERN.test(path24.basename(filePath));
7425
+ var accepts = (filePath) => ROLLOUT_FILE_PATTERN.test(path26.basename(filePath));
7229
7426
  var CODEX_FOLLOW_POLL_MS = 1e3;
7230
7427
  function isInside2(root, filePath) {
7231
- const relative = path24.relative(root, filePath);
7232
- return relative.length > 0 && !relative.startsWith("..") && !path24.isAbsolute(relative);
7428
+ const relative = path26.relative(root, filePath);
7429
+ return relative.length > 0 && !relative.startsWith("..") && !path26.isAbsolute(relative);
7233
7430
  }
7234
7431
  function toItem2(ref, info) {
7235
- const name = parseRolloutFileName(path24.basename(ref));
7432
+ const name = parseRolloutFileName(path26.basename(ref));
7236
7433
  if (name === null) return null;
7237
7434
  return { ref, sessionId: name.sessionId, group: name.sessionId, mtimeMs: info.mtimeMs, sizeBytes: info.size };
7238
7435
  }
7239
7436
  async function itemOf(ref) {
7240
7437
  try {
7241
- return toItem2(ref, await stat13(ref));
7438
+ return toItem2(ref, await stat14(ref));
7242
7439
  } catch {
7243
7440
  return null;
7244
7441
  }
@@ -7432,7 +7629,7 @@ function createCodexAdapter() {
7432
7629
  * credenciales de antes de cambiarla.
7433
7630
  */
7434
7631
  protectedDirs: () => {
7435
- const dirs = [path25.join(homedir7(), ".codex")];
7632
+ const dirs = [path27.join(homedir7(), ".codex")];
7436
7633
  const home = codexHome();
7437
7634
  if (home !== null && !dirs.includes(home)) dirs.unshift(home);
7438
7635
  return dirs;
@@ -7625,7 +7822,7 @@ var DbChangeSignal = class {
7625
7822
  };
7626
7823
 
7627
7824
  // packages/server/src/agents/opencode/history.ts
7628
- import { stat as stat14 } from "node:fs/promises";
7825
+ import { stat as stat15 } from "node:fs/promises";
7629
7826
 
7630
7827
  // packages/server/src/agents/opencode/events.ts
7631
7828
  var TOOL_INPUT_PARSE_MAX_CHARS = 16e3;
@@ -8576,7 +8773,7 @@ function createOpenCodeHistory(deps) {
8576
8773
  async rootExists() {
8577
8774
  if (dbFile === null) return false;
8578
8775
  try {
8579
- await stat14(dbFile);
8776
+ await stat15(dbFile);
8580
8777
  return true;
8581
8778
  } catch {
8582
8779
  return false;
@@ -8608,13 +8805,13 @@ function createOpenCodeHistory(deps) {
8608
8805
  var OPENCODE_SESSION_ID_PATTERN = /^ses_[0-9A-Za-z]{20,40}$/;
8609
8806
 
8610
8807
  // packages/server/src/agents/opencode/paths.ts
8611
- import path26 from "node:path";
8808
+ import path28 from "node:path";
8612
8809
  function isFullyQualified2(candidate, platform) {
8613
8810
  if (platform === "win32") return /^([a-zA-Z]:[\\/]|[\\/]{2})/.test(candidate);
8614
- return path26.posix.isAbsolute(candidate);
8811
+ return path28.posix.isAbsolute(candidate);
8615
8812
  }
8616
8813
  function resolveOpenCodePaths(env, home, platform) {
8617
- const flavor = platform === "win32" ? path26.win32 : path26.posix;
8814
+ const flavor = platform === "win32" ? path28.win32 : path28.posix;
8618
8815
  const variable = (name) => {
8619
8816
  const value = env[name];
8620
8817
  return value === void 0 || value.length === 0 ? null : value;
@@ -9062,8 +9259,8 @@ function launchWithServe(deps, input) {
9062
9259
  import { execFile as execFile2, spawn as nodeSpawn } from "node:child_process";
9063
9260
  import { randomBytes } from "node:crypto";
9064
9261
  import { mkdir as mkdir4 } from "node:fs/promises";
9065
- import { tmpdir as tmpdir3 } from "node:os";
9066
- import path27 from "node:path";
9262
+ import { tmpdir as tmpdir4 } from "node:os";
9263
+ import path29 from "node:path";
9067
9264
  var OPENCODE_SERVE_HOST = "127.0.0.1";
9068
9265
  var OPENCODE_SERVE_START_TIMEOUT_MS = 2e4;
9069
9266
  var OPENCODE_SERVE_IDLE_STOP_MS = 3e5;
@@ -9095,7 +9292,7 @@ function parseListeningLine(line) {
9095
9292
  return `http://${OPENCODE_SERVE_HOST}:${found.port}`;
9096
9293
  }
9097
9294
  function defaultServeCwd() {
9098
- return path27.join(tmpdir3(), "agent-workbench", "opencode-serve");
9295
+ return path29.join(tmpdir4(), "agent-workbench", "opencode-serve");
9099
9296
  }
9100
9297
  var ServeStartError = class extends Error {
9101
9298
  };
@@ -10114,28 +10311,48 @@ var AgentRegistry = class {
10114
10311
  agents = /* @__PURE__ */ new Map();
10115
10312
  /** Las suscripciones a cambios de configuracion que siguen abiertas, para soltarlas al apagar. */
10116
10313
  changeSubscriptions = /* @__PURE__ */ new Set();
10314
+ /**
10315
+ * Las que el usuario apago en Ajustes (Hito 41, §6.32), tal como estaban al
10316
+ * arrancar. Una apagada es, para todo el resto, una CLI que no esta: no se
10317
+ * busca —no se corre su `--version`—, no se lanza, y su historial no se lee
10318
+ * (`historySources`). Cambiarlo vale al reiniciar.
10319
+ */
10320
+ disabled;
10117
10321
  /** El orden del array es el orden de preferencia para `defaultAgent`. */
10118
- constructor(adapters) {
10322
+ constructor(adapters, disabled = []) {
10119
10323
  for (const adapter of adapters) {
10120
10324
  if (this.agents.has(adapter.id)) {
10121
10325
  throw new Error(`Adapter registered twice: ${adapter.id}`);
10122
10326
  }
10123
10327
  this.agents.set(adapter.id, { adapter, location: null });
10124
10328
  }
10329
+ this.disabled = new Set([...disabled].filter((id) => this.agents.has(id)));
10125
10330
  }
10126
10331
  /**
10127
10332
  * Localiza todos a la vez. Se llama una vez al arrancar.
10128
10333
  *
10129
10334
  * En paralelo porque cada uno puede correr `--version`, y eso son segundos
10130
- * que el arranque no tiene por que sumar.
10335
+ * que el arranque no tiene por que sumar. Una apagada no se busca.
10131
10336
  */
10132
10337
  async locateAll() {
10133
10338
  await Promise.all(
10134
10339
  [...this.agents.values()].map(async (entry) => {
10135
- entry.location = await entry.adapter.locate();
10340
+ entry.location = this.disabled.has(entry.adapter.id) ? null : await entry.adapter.locate();
10136
10341
  })
10137
10342
  );
10138
10343
  }
10344
+ /** Las que este arranque dejo apagadas. */
10345
+ disabledIds() {
10346
+ return [...this.agents.keys()].filter((id) => this.disabled.has(id));
10347
+ }
10348
+ /**
10349
+ * Las CLIs cuyo historial se lee: todas menos las apagadas. Instalada o no da
10350
+ * igual —el historial de una ausente se lee igual, apagado en la barra—; una
10351
+ * apagada es lo que el usuario pidio no ver.
10352
+ */
10353
+ historySources() {
10354
+ return this.all().filter((entry) => !this.disabled.has(entry.adapter.id));
10355
+ }
10139
10356
  /**
10140
10357
  * Lo que cada CLI encontrada prepara en las carpetas de la app
10141
10358
  * (`AgentAdapter.prepare`). Se llama una vez al arrancar, despues de
@@ -10287,13 +10504,13 @@ function resolveAgentForOpen(input) {
10287
10504
  }
10288
10505
  return fromTabs ?? input.defaultAgent;
10289
10506
  }
10290
- function createAgentRegistry() {
10507
+ function createAgentRegistry(disabled = []) {
10291
10508
  const registry = new AgentRegistry([
10292
10509
  createClaudeCodeAdapter(),
10293
10510
  createCodexAdapter(),
10294
10511
  createOpenCodeAdapter({ serveEnv: () => registry.composedEnvironment(process.env) }),
10295
10512
  createAntigravityAdapter()
10296
- ]);
10513
+ ], disabled);
10297
10514
  return registry;
10298
10515
  }
10299
10516
 
@@ -10310,7 +10527,7 @@ async function migrateLegacyConfigDir() {
10310
10527
 
10311
10528
  // packages/server/src/conversation-hub.ts
10312
10529
  import { EventEmitter } from "node:events";
10313
- import path28 from "node:path";
10530
+ import path30 from "node:path";
10314
10531
  var INITIAL_EVENT_LIMIT = 300;
10315
10532
  var MAX_PAGE_LIMIT = 500;
10316
10533
  var PENDING_QUESTION_LOOKBACK = 50;
@@ -10371,7 +10588,7 @@ var ConversationHub = class extends EventEmitter {
10371
10588
  await entry.follower.start();
10372
10589
  debugLog(
10373
10590
  "conversation",
10374
- `following ${path28.basename(entry.follower.label)} for ${terminalId.slice(0, 8)}`
10591
+ `following ${path30.basename(entry.follower.label)} for ${terminalId.slice(0, 8)}`
10375
10592
  );
10376
10593
  entry = this.entries.get(terminalId) ?? entry;
10377
10594
  }
@@ -10536,7 +10753,7 @@ var ConversationHub = class extends EventEmitter {
10536
10753
  this.startFollowTicker(terminalId, next);
10537
10754
  debugLog(
10538
10755
  "conversation",
10539
- `${terminalId.slice(0, 8)} changed session: following ${path28.basename(next.follower.label)}`
10756
+ `${terminalId.slice(0, 8)} changed session: following ${path30.basename(next.follower.label)}`
10540
10757
  );
10541
10758
  await next.follower.start();
10542
10759
  next.primed = true;
@@ -10900,18 +11117,18 @@ function openBrowser(url) {
10900
11117
  // packages/server/src/pty-helper.ts
10901
11118
  import { chmodSync, statSync } from "node:fs";
10902
11119
  import { createRequire } from "node:module";
10903
- import path29 from "node:path";
11120
+ import path31 from "node:path";
10904
11121
  function helperCandidates(packageDir, platform, arch) {
10905
11122
  return [
10906
- path29.join(packageDir, "build", "Release", "spawn-helper"),
10907
- path29.join(packageDir, "build", "Debug", "spawn-helper"),
10908
- path29.join(packageDir, "prebuilds", `${platform}-${arch}`, "spawn-helper")
11123
+ path31.join(packageDir, "build", "Release", "spawn-helper"),
11124
+ path31.join(packageDir, "build", "Debug", "spawn-helper"),
11125
+ path31.join(packageDir, "prebuilds", `${platform}-${arch}`, "spawn-helper")
10909
11126
  ];
10910
11127
  }
10911
11128
  function locateNodePty() {
10912
11129
  try {
10913
11130
  const require2 = createRequire(import.meta.url);
10914
- return path29.resolve(path29.dirname(require2.resolve("node-pty")), "..");
11131
+ return path31.resolve(path31.dirname(require2.resolve("node-pty")), "..");
10915
11132
  } catch {
10916
11133
  return null;
10917
11134
  }
@@ -11205,6 +11422,7 @@ var OPENS_ON_HOST = /* @__PURE__ */ new Set([
11205
11422
  ]);
11206
11423
  function remoteRefusal(type, remoteClient) {
11207
11424
  if (!remoteClient) return null;
11425
+ if (type === "settings.update") return serverText("settingsHostOnly");
11208
11426
  if (HOST_ONLY.has(type)) return serverText("remoteHostOnly");
11209
11427
  if (OPENS_ON_HOST.has(type)) return serverText("remoteOpensOnHost");
11210
11428
  return null;
@@ -11571,24 +11789,24 @@ var RemoteAccessService = class {
11571
11789
  };
11572
11790
 
11573
11791
  // packages/server/src/remote-devices-store.ts
11574
- import { chmod, mkdir as mkdir6, readFile as readFile9, stat as stat16 } from "node:fs/promises";
11575
- import path31 from "node:path";
11792
+ import { chmod, mkdir as mkdir6, readFile as readFile10, stat as stat17 } from "node:fs/promises";
11793
+ import path33 from "node:path";
11576
11794
 
11577
11795
  // packages/server/src/vault/write.ts
11578
11796
  import { randomBytes as randomBytes2 } from "node:crypto";
11579
- import { mkdir as mkdir5, rename as rename3, rm as rm2, stat as stat15, writeFile as writeFile3 } from "node:fs/promises";
11797
+ import { mkdir as mkdir5, rename as rename3, rm as rm2, stat as stat16, writeFile as writeFile3 } from "node:fs/promises";
11580
11798
 
11581
11799
  // packages/server/src/vault/paths.ts
11582
11800
  import { createHash as createHash2 } from "node:crypto";
11583
- import path30 from "node:path";
11801
+ import path32 from "node:path";
11584
11802
  function defaultVaultDir() {
11585
- return path30.join(appConfigDir(), "vault");
11803
+ return path32.join(appConfigDir(), "vault");
11586
11804
  }
11587
11805
  function vaultMarkerFile(dir) {
11588
- return path30.join(dir, "vault.json");
11806
+ return path32.join(dir, "vault.json");
11589
11807
  }
11590
11808
  function sessionsRoot2(dir) {
11591
- return path30.join(dir, "sessions");
11809
+ return path32.join(dir, "sessions");
11592
11810
  }
11593
11811
  function isSafeSessionId(sessionId) {
11594
11812
  return isVaultSessionId(sessionId);
@@ -11602,41 +11820,41 @@ function assertSessionKey(agent, sessionId) {
11602
11820
  }
11603
11821
  function agentSessionsDir(dir, agent) {
11604
11822
  if (!isSessionAgentId(agent)) throw new Error(`Unknown source for the local copy: ${JSON.stringify(agent)}`);
11605
- return path30.join(sessionsRoot2(dir), agent);
11823
+ return path32.join(sessionsRoot2(dir), agent);
11606
11824
  }
11607
11825
  function sessionFile(dir, agent, sessionId) {
11608
11826
  assertSessionKey(agent, sessionId);
11609
- return path30.join(sessionsRoot2(dir), agent, `${sessionId}.jsonl`);
11827
+ return path32.join(sessionsRoot2(dir), agent, `${sessionId}.jsonl`);
11610
11828
  }
11611
11829
  function assetsDir(dir, agent, sessionId) {
11612
11830
  assertSessionKey(agent, sessionId);
11613
- return path30.join(sessionsRoot2(dir), agent, `${sessionId}.assets`);
11831
+ return path32.join(sessionsRoot2(dir), agent, `${sessionId}.assets`);
11614
11832
  }
11615
11833
  function assetFile(dir, agent, sessionId, asset) {
11616
11834
  if (!VAULT_ASSET_NAME_PATTERN.test(asset)) throw new Error(`Invalid asset name: ${JSON.stringify(asset)}`);
11617
- return path30.join(assetsDir(dir, agent, sessionId), asset);
11835
+ return path32.join(assetsDir(dir, agent, sessionId), asset);
11618
11836
  }
11619
11837
  function projectFolderName(cwd, platform) {
11620
11838
  const hash = createHash2("sha256").update(normalizeCwdKey(cwd, platform)).digest("hex").slice(0, 8);
11621
- const base = (platform === "win32" ? path30.win32 : path30.posix).basename(cwd);
11839
+ const base = (platform === "win32" ? path32.win32 : path32.posix).basename(cwd);
11622
11840
  const name = base.length === 0 ? "proyecto" : sanitizeFileName(base, 60);
11623
11841
  return `${name}-${hash}`;
11624
11842
  }
11625
11843
  function memoryDir(dir, cwd, platform) {
11626
- return path30.join(dir, "memory", projectFolderName(cwd, platform));
11844
+ return path32.join(dir, "memory", projectFolderName(cwd, platform));
11627
11845
  }
11628
11846
  function projectExportDir(dir, cwd, platform) {
11629
- return path30.join(dir, "export", projectFolderName(cwd, platform));
11847
+ return path32.join(dir, "export", projectFolderName(cwd, platform));
11630
11848
  }
11631
11849
  function projectExportDirFor(dir, project, platform) {
11632
11850
  if (project.cwd.length > 0) return projectExportDir(dir, project.cwd, platform);
11633
11851
  const hash = createHash2("sha256").update(project.key).digest("hex").slice(0, 8);
11634
11852
  const name = project.fallbackName.length > 0 ? sanitizeFileName(project.fallbackName, 60) : "proyecto";
11635
- return path30.join(dir, "export", `${name}-${hash}`);
11853
+ return path32.join(dir, "export", `${name}-${hash}`);
11636
11854
  }
11637
11855
  function sessionExportFile(dir, agent, sessionId) {
11638
11856
  assertSessionKey(agent, sessionId);
11639
- return path30.join(dir, "export", "sesiones", `${agent}-${sessionId}.md`);
11857
+ return path32.join(dir, "export", "sesiones", `${agent}-${sessionId}.md`);
11640
11858
  }
11641
11859
  var RESERVED_WINDOWS_NAME = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i;
11642
11860
  function sanitizeFileName(text, max = 80) {
@@ -11691,7 +11909,7 @@ async function writeFileAtomic(target, data, deps = DEFAULT_WRITE_DEPS) {
11691
11909
  async function ensureVaultMarker(dir, deps = DEFAULT_WRITE_DEPS) {
11692
11910
  const marker = vaultMarkerFile(dir);
11693
11911
  try {
11694
- await stat15(marker);
11912
+ await stat16(marker);
11695
11913
  return;
11696
11914
  } catch {
11697
11915
  }
@@ -11701,7 +11919,7 @@ async function ensureVaultMarker(dir, deps = DEFAULT_WRITE_DEPS) {
11701
11919
  }
11702
11920
  async function assetPresent(file, size) {
11703
11921
  try {
11704
- const info = await stat15(file);
11922
+ const info = await stat16(file);
11705
11923
  return info.isFile() && info.size === size;
11706
11924
  } catch {
11707
11925
  return false;
@@ -11743,7 +11961,7 @@ var RemoteDevicesStore = class {
11743
11961
  async load() {
11744
11962
  let raw;
11745
11963
  try {
11746
- raw = await readFile9(this.filePath, "utf8");
11964
+ raw = await readFile10(this.filePath, "utf8");
11747
11965
  } catch {
11748
11966
  return [];
11749
11967
  }
@@ -11764,7 +11982,7 @@ var RemoteDevicesStore = class {
11764
11982
  save(devices) {
11765
11983
  const text = serializeRemoteDevices(devices);
11766
11984
  const run = async () => {
11767
- await mkdir6(path31.dirname(this.filePath), { recursive: true });
11985
+ await mkdir6(path33.dirname(this.filePath), { recursive: true });
11768
11986
  await writeFileAtomic(this.filePath, text);
11769
11987
  await chmod(this.filePath, 384).catch(() => void 0);
11770
11988
  };
@@ -11783,7 +12001,7 @@ var RemoteDevicesStore = class {
11783
12001
  */
11784
12002
  async stamp() {
11785
12003
  try {
11786
- const info = await stat16(this.filePath);
12004
+ const info = await stat17(this.filePath);
11787
12005
  return `${info.mtimeMs}:${info.size}`;
11788
12006
  } catch {
11789
12007
  return "";
@@ -11897,8 +12115,8 @@ import chokidar from "chokidar";
11897
12115
 
11898
12116
  // packages/server/src/git-repo.ts
11899
12117
  import { execFile as execFile4 } from "node:child_process";
11900
- import { readFile as readFile10 } from "node:fs/promises";
11901
- import path32 from "node:path";
12118
+ import { readFile as readFile11 } from "node:fs/promises";
12119
+ import path34 from "node:path";
11902
12120
  function gitOutputText(output, command) {
11903
12121
  return output.length > 0 ? serverText("raw", { text: output }) : serverText("gitCommandFailed", { command });
11904
12122
  }
@@ -11959,7 +12177,7 @@ async function resolveRepo(cwd) {
11959
12177
  const result = await runGit(cwd, ["rev-parse", "--show-toplevel"]);
11960
12178
  if (result.code === 0) {
11961
12179
  const root = result.stdout.trim();
11962
- if (root.length > 0) return { state: "ready", root: path32.resolve(root), message: null };
12180
+ if (root.length > 0) return { state: "ready", root: path34.resolve(root), message: null };
11963
12181
  }
11964
12182
  const stderr = result.stderr.trim();
11965
12183
  if (/not a git repository|no es un repositorio/i.test(stderr)) {
@@ -11986,7 +12204,7 @@ async function findGitDir(cwd) {
11986
12204
  const result = await runGit(cwd, ["rev-parse", "--absolute-git-dir"]);
11987
12205
  if (result.code !== 0) return null;
11988
12206
  const dir = result.stdout.trim();
11989
- return dir.length > 0 ? path32.resolve(dir) : null;
12207
+ return dir.length > 0 ? path34.resolve(dir) : null;
11990
12208
  }
11991
12209
  function letterToKind(letter) {
11992
12210
  switch (letter) {
@@ -12089,7 +12307,7 @@ function parseWorktrees(raw, currentRoot) {
12089
12307
  worktrees.push({
12090
12308
  path: current.path,
12091
12309
  branch: current.branch,
12092
- isCurrent: path32.resolve(current.path) === path32.resolve(currentRoot)
12310
+ isCurrent: path34.resolve(current.path) === path34.resolve(currentRoot)
12093
12311
  });
12094
12312
  current = null;
12095
12313
  };
@@ -12232,7 +12450,7 @@ function looksBinary(buffer) {
12232
12450
  async function readUntrackedAsDiff(absolutePath, relativePath) {
12233
12451
  let content;
12234
12452
  try {
12235
- content = await readFile10(absolutePath);
12453
+ content = await readFile11(absolutePath);
12236
12454
  } catch {
12237
12455
  return {
12238
12456
  path: relativePath,
@@ -12280,7 +12498,7 @@ async function readUntrackedAsDiff(absolutePath, relativePath) {
12280
12498
  async function readDiff(options) {
12281
12499
  const { repoRoot: repoRoot2, relativePath, staged, untracked } = options;
12282
12500
  if (untracked && !staged) {
12283
- return readUntrackedAsDiff(path32.join(repoRoot2, relativePath), relativePath);
12501
+ return readUntrackedAsDiff(path34.join(repoRoot2, relativePath), relativePath);
12284
12502
  }
12285
12503
  const args = [
12286
12504
  "diff",
@@ -12500,7 +12718,7 @@ var RepoHub = class extends EventEmitter2 {
12500
12718
  // packages/server/src/memory-hub.ts
12501
12719
  import { EventEmitter as EventEmitter3 } from "node:events";
12502
12720
  import { homedir as homedir10 } from "node:os";
12503
- import path34 from "node:path";
12721
+ import path36 from "node:path";
12504
12722
  import chokidar2 from "chokidar";
12505
12723
 
12506
12724
  // packages/server/src/memory-bridge.ts
@@ -12510,14 +12728,14 @@ import {
12510
12728
  mkdir as mkdir7,
12511
12729
  open as open6,
12512
12730
  readdir as readdir9,
12513
- readFile as readFile11,
12731
+ readFile as readFile12,
12514
12732
  realpath as realpath2,
12515
12733
  rename as rename4,
12516
- stat as stat17,
12734
+ stat as stat18,
12517
12735
  unlink as unlink3
12518
12736
  } from "node:fs/promises";
12519
12737
  import { homedir as homedir9 } from "node:os";
12520
- import path33 from "node:path";
12738
+ import path35 from "node:path";
12521
12739
  var MemoryBridgeError = class extends ServerTextError {
12522
12740
  };
12523
12741
  var MAX_LISTED_NOTES = 500;
@@ -12535,9 +12753,9 @@ var GITIGNORE_MEMORY_LINE = `${MEMORY_DIR}/`;
12535
12753
  var BOM2 = Buffer.from([239, 187, 191]);
12536
12754
  async function inspectMemory(cwd, home = homedir9()) {
12537
12755
  await assertMemoryDirInside(cwd);
12538
- const folder = path33.join(cwd, MEMORY_DIR);
12756
+ const folder = path35.join(cwd, MEMORY_DIR);
12539
12757
  const folderExists = await isDirectory2(folder);
12540
- const indexExists = folderExists && await isFile(path33.join(folder, MEMORY_INDEX_FILE));
12758
+ const indexExists = folderExists && await isFile(path35.join(folder, MEMORY_INDEX_FILE));
12541
12759
  const noteNames = folderExists ? await listNoteFiles(folder) : [];
12542
12760
  const notes = await describeNotes(folder, noteNames);
12543
12761
  const files = await Promise.all(
@@ -12569,7 +12787,7 @@ async function installMemory(cwd, options, home = homedir9()) {
12569
12787
  }
12570
12788
  async function importNativeMemory(cwd, home = homedir9()) {
12571
12789
  await assertMemoryDirInside(cwd);
12572
- if (!await isDirectory2(path33.join(cwd, MEMORY_DIR))) {
12790
+ if (!await isDirectory2(path35.join(cwd, MEMORY_DIR))) {
12573
12791
  throw new MemoryBridgeError(serverText("memoryNotInstalled"));
12574
12792
  }
12575
12793
  const plan = new PlanBuilder(cwd);
@@ -12587,9 +12805,9 @@ async function readMemoryNote(cwd, name) {
12587
12805
  }
12588
12806
  const absolute = await resolveInside(cwd, `${MEMORY_DIR}/${name}`);
12589
12807
  try {
12590
- const info = await stat17(absolute);
12808
+ const info = await stat18(absolute);
12591
12809
  if (!info.isFile()) return null;
12592
- const content = await readFile11(absolute);
12810
+ const content = await readFile12(absolute);
12593
12811
  return {
12594
12812
  name,
12595
12813
  text: content.subarray(0, MAX_NOTE_BYTES).toString("utf8"),
@@ -12604,7 +12822,7 @@ function isNoteName(name) {
12604
12822
  if (name.length === 0 || name.length > 255) return false;
12605
12823
  if (name.includes("\0")) return false;
12606
12824
  if (name === "." || name === "..") return false;
12607
- if (path33.basename(name) !== name) return false;
12825
+ if (path35.basename(name) !== name) return false;
12608
12826
  if (/[\\/:*?"<>|\u0000-\u001f]/.test(name)) return false;
12609
12827
  return name.toLowerCase().endsWith(".md");
12610
12828
  }
@@ -12641,7 +12859,7 @@ var PlanBuilder = class {
12641
12859
  /** Nombres de nota que habria en la carpeta, en minusculas. */
12642
12860
  async noteNames() {
12643
12861
  const names = /* @__PURE__ */ new Map();
12644
- for (const name of await listNoteFiles(path33.join(this.cwd, MEMORY_DIR))) {
12862
+ for (const name of await listNoteFiles(path35.join(this.cwd, MEMORY_DIR))) {
12645
12863
  names.set(name.toLowerCase(), name);
12646
12864
  }
12647
12865
  for (const rel of this.files.keys()) {
@@ -12684,7 +12902,7 @@ var PlanBuilder = class {
12684
12902
  continue;
12685
12903
  }
12686
12904
  const target = await realpath2(absolute);
12687
- const info = await stat17(target);
12905
+ const info = await stat18(target);
12688
12906
  if (info.nlink > 1) {
12689
12907
  throw new MemoryBridgeError(serverText("memoryHardLinks", { file: file.rel }));
12690
12908
  }
@@ -12703,7 +12921,7 @@ var PlanBuilder = class {
12703
12921
  );
12704
12922
  if (pending.length === 0) return;
12705
12923
  if (pending.some((file) => file.rel.startsWith(`${MEMORY_DIR}/`))) {
12706
- await mkdir7(path33.join(this.cwd, MEMORY_DIR), { recursive: true });
12924
+ await mkdir7(path35.join(this.cwd, MEMORY_DIR), { recursive: true });
12707
12925
  }
12708
12926
  for (const file of pending) {
12709
12927
  if (file.target === null) throw new Error(`Unvalidated plan: ${file.rel}`);
@@ -12814,7 +13032,7 @@ async function planNativeImport(plan, cwd, home) {
12814
13032
  const local = await plan.noteNames();
12815
13033
  for (const name of await listNoteFiles(folder)) {
12816
13034
  const key = name.toLowerCase();
12817
- const content = await readIfFile(path33.join(folder, name), name);
13035
+ const content = await readIfFile(path35.join(folder, name), name);
12818
13036
  if (content === null) continue;
12819
13037
  const already = chosen.get(key);
12820
13038
  if (already !== void 0) {
@@ -12828,7 +13046,7 @@ async function planNativeImport(plan, cwd, home) {
12828
13046
  if (existing !== null && !existing.equals(content)) skipped.push(name);
12829
13047
  continue;
12830
13048
  }
12831
- plan.put(`${MEMORY_DIR}/${name}`, null, content, "copy", "", path33.join(folder, name));
13049
+ plan.put(`${MEMORY_DIR}/${name}`, null, content, "copy", "", path35.join(folder, name));
12832
13050
  copied.push(name);
12833
13051
  }
12834
13052
  }
@@ -12838,7 +13056,7 @@ async function planNativeImport(plan, cwd, home) {
12838
13056
  const known = new Set(linkTargets(index.toString("utf8")));
12839
13057
  const additions = [];
12840
13058
  for (const folder of folders) {
12841
- const nativeIndex = await readIfFile(path33.join(folder, MEMORY_INDEX_FILE), MEMORY_INDEX_FILE);
13059
+ const nativeIndex = await readIfFile(path35.join(folder, MEMORY_INDEX_FILE), MEMORY_INDEX_FILE);
12842
13060
  if (nativeIndex === null) continue;
12843
13061
  for (const line of nativeIndex.toString("utf8").replace(/^\uFEFF/, "").split(/\r?\n/)) {
12844
13062
  const target = linkTargets(line)[0];
@@ -13063,7 +13281,7 @@ async function describeNotes(folder, names) {
13063
13281
  const stated = [];
13064
13282
  for (const name of names) {
13065
13283
  try {
13066
- const info = await stat17(path33.join(folder, name));
13284
+ const info = await stat18(path35.join(folder, name));
13067
13285
  if (info.isFile()) stated.push({ name, sizeBytes: info.size, modifiedAt: info.mtimeMs });
13068
13286
  } catch {
13069
13287
  }
@@ -13071,7 +13289,7 @@ async function describeNotes(folder, names) {
13071
13289
  stated.sort((a, b) => b.modifiedAt - a.modifiedAt || a.name.localeCompare(b.name));
13072
13290
  const notes = [];
13073
13291
  for (const item of stated.slice(0, MAX_LISTED_NOTES)) {
13074
- const head = await readHead(path33.join(folder, item.name));
13292
+ const head = await readHead(path35.join(folder, item.name));
13075
13293
  notes.push({ ...item, ...describeNoteText(head, item.name) });
13076
13294
  }
13077
13295
  return notes;
@@ -13129,28 +13347,28 @@ async function countNativeMemory(cwd, home, sharedNames) {
13129
13347
  return { available: names.size, pending };
13130
13348
  }
13131
13349
  async function nativeMemoryFolders(cwd, home) {
13132
- const root = path33.join(home, ".claude", "projects");
13350
+ const root = path35.join(home, ".claude", "projects");
13133
13351
  const slug = projectSlugFor(cwd).toLowerCase();
13134
13352
  try {
13135
13353
  const entries = await readdir9(root, { withFileTypes: true });
13136
- return entries.filter((entry) => entry.isDirectory() && entry.name.toLowerCase() === slug).map((entry) => entry.name).sort().map((name) => path33.join(root, name, "memory"));
13354
+ return entries.filter((entry) => entry.isDirectory() && entry.name.toLowerCase() === slug).map((entry) => entry.name).sort().map((name) => path35.join(root, name, "memory"));
13137
13355
  } catch {
13138
13356
  return [];
13139
13357
  }
13140
13358
  }
13141
13359
  function buildGlobalFragments(home) {
13142
13360
  const windows = process.platform === "win32";
13143
- const globalFile = path33.join(home, ...GLOBAL_MEMORY_RELATIVE.split("/"));
13361
+ const globalFile = path35.join(home, ...GLOBAL_MEMORY_RELATIVE.split("/"));
13144
13362
  const hardLink = (link) => windows ? `mklink /H "${link}" "${globalFile}"` : `ln "${globalFile}" "${link}"`;
13145
13363
  const hardLinkNote = windows ? serverText("memoryNoteHardLinkCmd", { path: GLOBAL_MEMORY_RELATIVE }) : serverText("memoryNoteHardLink", { path: GLOBAL_MEMORY_RELATIVE });
13146
- const codexTarget = path33.join(home, ".codex", "AGENTS.md");
13147
- const geminiTarget = path33.join(home, ".gemini", "GEMINI.md");
13364
+ const codexTarget = path35.join(home, ".codex", "AGENTS.md");
13365
+ const geminiTarget = path35.join(home, ".gemini", "GEMINI.md");
13148
13366
  const homeSlashes = home.replace(/\\/g, "/").replace(/\/+$/, "");
13149
13367
  return [
13150
13368
  {
13151
13369
  agent: "claude-code",
13152
13370
  label: MEMORY_AGENT_LABELS["claude-code"],
13153
- target: path33.join(home, ".claude", "CLAUDE.md"),
13371
+ target: path35.join(home, ".claude", "CLAUDE.md"),
13154
13372
  kind: "line",
13155
13373
  text: `@~/${GLOBAL_MEMORY_RELATIVE}`,
13156
13374
  note: serverText("memoryNoteClaude")
@@ -13174,7 +13392,7 @@ function buildGlobalFragments(home) {
13174
13392
  {
13175
13393
  agent: "opencode",
13176
13394
  label: MEMORY_AGENT_LABELS.opencode,
13177
- target: path33.join(home, ".config", "opencode", "opencode.json"),
13395
+ target: path35.join(home, ".config", "opencode", "opencode.json"),
13178
13396
  kind: "json",
13179
13397
  text: `"instructions": ["${homeSlashes}/${GLOBAL_MEMORY_RELATIVE}"]`,
13180
13398
  note: serverText("memoryNoteOpenCode")
@@ -13222,17 +13440,17 @@ async function readGit(cwd) {
13222
13440
  if (!toplevel || !gitDir || !commonDir) return fail(serverText("gitUnexpected", { command: "rev-parse" }));
13223
13441
  const ignored = await checkIgnored(cwd, INDEX_REL);
13224
13442
  if (typeof ignored !== "boolean") return fail(ignored);
13225
- const absoluteGitDir = path33.resolve(cwd, gitDir);
13226
- const absoluteCommonDir = path33.resolve(cwd, commonDir);
13443
+ const absoluteGitDir = path35.resolve(cwd, gitDir);
13444
+ const absoluteCommonDir = path35.resolve(cwd, commonDir);
13227
13445
  let worktree = null;
13228
13446
  let main2 = null;
13229
13447
  if (!await samePath(absoluteGitDir, absoluteCommonDir)) {
13230
- if (path33.basename(absoluteCommonDir).toLowerCase() === ".git") {
13231
- const mainRoot = path33.dirname(absoluteCommonDir);
13232
- const inner = path33.relative(path33.resolve(toplevel), path33.resolve(cwd));
13233
- const sameFolder = inner.length === 0 || inner.startsWith("..") || path33.isAbsolute(inner);
13234
- const mainPath = sameFolder ? mainRoot : path33.join(mainRoot, inner);
13235
- const memoryRel = sameFolder ? MEMORY_DIR : [...inner.split(path33.sep), MEMORY_DIR].join("/");
13448
+ if (path35.basename(absoluteCommonDir).toLowerCase() === ".git") {
13449
+ const mainRoot = path35.dirname(absoluteCommonDir);
13450
+ const inner = path35.relative(path35.resolve(toplevel), path35.resolve(cwd));
13451
+ const sameFolder = inner.length === 0 || inner.startsWith("..") || path35.isAbsolute(inner);
13452
+ const mainPath = sameFolder ? mainRoot : path35.join(mainRoot, inner);
13453
+ const memoryRel = sameFolder ? MEMORY_DIR : [...inner.split(path35.sep), MEMORY_DIR].join("/");
13236
13454
  main2 = { root: mainRoot, memoryRel };
13237
13455
  worktree = {
13238
13456
  mainPath,
@@ -13257,7 +13475,7 @@ async function samePath(a, b) {
13257
13475
  try {
13258
13476
  return await realpath2(value);
13259
13477
  } catch {
13260
- return path33.resolve(value);
13478
+ return path35.resolve(value);
13261
13479
  }
13262
13480
  };
13263
13481
  const [left, right] = await Promise.all([resolve(a), resolve(b)]);
@@ -13299,9 +13517,9 @@ async function listNoteFiles(folder) {
13299
13517
  }
13300
13518
  async function readIfFile(absolute, label) {
13301
13519
  try {
13302
- const info = await stat17(absolute);
13520
+ const info = await stat18(absolute);
13303
13521
  if (!info.isFile()) throw new MemoryBridgeError(serverText("memoryNotAFile", { label }));
13304
- return await readFile11(absolute);
13522
+ return await readFile12(absolute);
13305
13523
  } catch (error) {
13306
13524
  if (isNotFound(error)) return null;
13307
13525
  throw error;
@@ -13323,14 +13541,14 @@ async function readHead(absolute) {
13323
13541
  }
13324
13542
  async function isFile(absolute) {
13325
13543
  try {
13326
- return (await stat17(absolute)).isFile();
13544
+ return (await stat18(absolute)).isFile();
13327
13545
  } catch {
13328
13546
  return false;
13329
13547
  }
13330
13548
  }
13331
13549
  async function isDirectory2(absolute) {
13332
13550
  try {
13333
- return (await stat17(absolute)).isDirectory();
13551
+ return (await stat18(absolute)).isDirectory();
13334
13552
  } catch {
13335
13553
  return false;
13336
13554
  }
@@ -13348,7 +13566,7 @@ async function writeAtomic(target, content, mustNotExist) {
13348
13566
  await handle.close();
13349
13567
  }
13350
13568
  if (mustNotExist && await exists2(target)) {
13351
- throw new MemoryBridgeError(serverText("memoryAppeared", { file: path33.basename(target) }));
13569
+ throw new MemoryBridgeError(serverText("memoryAppeared", { file: path35.basename(target) }));
13352
13570
  }
13353
13571
  await renameWithRetry(temporary, target);
13354
13572
  } catch (error) {
@@ -13359,9 +13577,9 @@ async function writeAtomic(target, content, mustNotExist) {
13359
13577
  async function createTemporary(target) {
13360
13578
  for (let attempt = 1; ; attempt += 1) {
13361
13579
  const suffix = randomBytes4(6).toString("hex");
13362
- const temporary = path33.join(
13363
- path33.dirname(target),
13364
- `.${path33.basename(target)}.${process.pid}.${suffix}.tmp`
13580
+ const temporary = path35.join(
13581
+ path35.dirname(target),
13582
+ `.${path35.basename(target)}.${process.pid}.${suffix}.tmp`
13365
13583
  );
13366
13584
  try {
13367
13585
  return { temporary, handle: await open6(temporary, "wx") };
@@ -13373,7 +13591,7 @@ async function createTemporary(target) {
13373
13591
  }
13374
13592
  async function exists2(absolute) {
13375
13593
  try {
13376
- await stat17(absolute);
13594
+ await stat18(absolute);
13377
13595
  return true;
13378
13596
  } catch {
13379
13597
  return false;
@@ -13599,13 +13817,13 @@ var MemoryHub = class extends EventEmitter3 {
13599
13817
  }
13600
13818
  };
13601
13819
  function keyFor(cwd) {
13602
- const resolved = path34.resolve(cwd);
13820
+ const resolved = path36.resolve(cwd);
13603
13821
  return process.platform === "win32" ? resolved.toLowerCase() : resolved;
13604
13822
  }
13605
13823
  function isRelevantPath(cwd, candidate) {
13606
- const relative = path34.relative(cwd, candidate);
13824
+ const relative = path36.relative(cwd, candidate);
13607
13825
  if (relative.length === 0) return true;
13608
- if (relative.startsWith("..") || path34.isAbsolute(relative)) return false;
13826
+ if (relative.startsWith("..") || path36.isAbsolute(relative)) return false;
13609
13827
  const same = (a, b) => process.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
13610
13828
  const parts = relative.split(/[\\/]/);
13611
13829
  const [first = "", second = "", third = ""] = parts;
@@ -13623,9 +13841,8 @@ function isRelevantPath(cwd, candidate) {
13623
13841
 
13624
13842
  // packages/server/src/paste-store.ts
13625
13843
  import { createHash as createHash3, randomUUID as randomUUID3 } from "node:crypto";
13626
- import { mkdir as mkdir8, readdir as readdir10, rm as rm3, stat as stat18, writeFile as writeFile4 } from "node:fs/promises";
13627
- import { tmpdir as tmpdir4 } from "node:os";
13628
- import path35 from "node:path";
13844
+ import { mkdir as mkdir8, readdir as readdir10, rm as rm3, stat as stat19, writeFile as writeFile4 } from "node:fs/promises";
13845
+ import path37 from "node:path";
13629
13846
 
13630
13847
  // packages/server/src/vault/markdown.ts
13631
13848
  var pad2 = (value) => String(value).padStart(2, "0");
@@ -14140,8 +14357,8 @@ function buildContinuationMessage(input) {
14140
14357
  }
14141
14358
  return sanitizeForPaste(lines.join("\n"));
14142
14359
  }
14143
- function transcriptReferenceFor(path50, style) {
14144
- return style === "at-quoted" ? fileReference(path50, "at-quoted") : `"${path50}"`;
14360
+ function transcriptReferenceFor(path52, style) {
14361
+ return style === "at-quoted" ? fileReference(path52, "at-quoted") : `"${path52}"`;
14145
14362
  }
14146
14363
  function planTranscript(input) {
14147
14364
  const turns = splitTurns(input.events);
@@ -14265,32 +14482,6 @@ function textWithAttachments(text, lines) {
14265
14482
  return lines.length === 0 ? text : [...lines, text].filter((piece) => piece.trim().length > 0).join("\n");
14266
14483
  }
14267
14484
 
14268
- // packages/server/src/image-signature.ts
14269
- var FORMATS = [
14270
- {
14271
- format: { ext: "png", mediaType: "image/png" },
14272
- test: (b) => b.length > 8 && b.subarray(0, 8).equals(Buffer.from("89504e470d0a1a0a", "hex"))
14273
- },
14274
- {
14275
- format: { ext: "jpg", mediaType: "image/jpeg" },
14276
- test: (b) => b.length > 3 && b[0] === 255 && b[1] === 216 && b[2] === 255
14277
- },
14278
- {
14279
- format: { ext: "gif", mediaType: "image/gif" },
14280
- test: (b) => b.length > 6 && b.subarray(0, 4).toString("ascii") === "GIF8"
14281
- },
14282
- {
14283
- format: { ext: "webp", mediaType: "image/webp" },
14284
- test: (b) => b.length > 12 && b.subarray(0, 4).toString("ascii") === "RIFF" && b.subarray(8, 12).toString("ascii") === "WEBP"
14285
- }
14286
- ];
14287
- var IMAGE_MEDIA_TYPES = new Set(
14288
- FORMATS.map((entry) => entry.format.mediaType)
14289
- );
14290
- function detectImageFormat(bytes) {
14291
- return FORMATS.find((entry) => entry.test(bytes))?.format ?? null;
14292
- }
14293
-
14294
14485
  // packages/server/src/paste-store.ts
14295
14486
  var EXTENSIONS = /* @__PURE__ */ new Map([
14296
14487
  ["image/png", "png"],
@@ -14316,7 +14507,7 @@ var PasteStore = class {
14316
14507
  root;
14317
14508
  counter = 0;
14318
14509
  constructor(root) {
14319
- this.root = root ?? path35.join(tmpdir4(), "agent-workbench", "pasted");
14510
+ this.root = root ?? pasteRoot();
14320
14511
  }
14321
14512
  /**
14322
14513
  * Guarda una imagen y devuelve su ruta.
@@ -14343,11 +14534,11 @@ var PasteStore = class {
14343
14534
  if (signature === null) {
14344
14535
  throw new PasteImageError(serverText("imageUnknownFormat"));
14345
14536
  }
14346
- const directory = path35.join(this.root, safeSegment(terminalId));
14537
+ const directory = path37.join(this.root, safeSegment(terminalId));
14347
14538
  await mkdir8(directory, { recursive: true, mode: DIRECTORY_MODE });
14348
14539
  this.counter += 1;
14349
14540
  const name = `pegada-${this.counter}-${randomUUID3().slice(0, 8)}.${signature.ext}`;
14350
- const file = path35.join(directory, name);
14541
+ const file = path37.join(directory, name);
14351
14542
  await writeFile4(file, bytes, { mode: FILE_MODE });
14352
14543
  return { path: file, bytes: bytes.length };
14353
14544
  }
@@ -14365,11 +14556,11 @@ var PasteStore = class {
14365
14556
  if (bytes.length > MAX_TEXT_BYTES) {
14366
14557
  throw new PasteTextError(`The text is ${bytes.length} bytes; the maximum is ${MAX_TEXT_BYTES}.`);
14367
14558
  }
14368
- const directory = path35.join(this.root, safeSegment(terminalId));
14559
+ const directory = path37.join(this.root, safeSegment(terminalId));
14369
14560
  await mkdir8(directory, { recursive: true, mode: DIRECTORY_MODE });
14370
14561
  this.counter += 1;
14371
14562
  const name = `${baseName}-${this.counter}-${randomUUID3().slice(0, 8)}.md`;
14372
- const file = path35.join(directory, name);
14563
+ const file = path37.join(directory, name);
14373
14564
  await writeFile4(file, bytes, { mode: FILE_MODE });
14374
14565
  return { path: file, bytes: bytes.length };
14375
14566
  }
@@ -14399,17 +14590,17 @@ var PasteStore = class {
14399
14590
  })
14400
14591
  );
14401
14592
  }
14402
- const directory = path35.join(this.root, safeSegment(terminalId));
14593
+ const directory = path37.join(this.root, safeSegment(terminalId));
14403
14594
  await mkdir8(directory, { recursive: true, mode: DIRECTORY_MODE });
14404
14595
  this.counter += 1;
14405
14596
  const name = `adjunto-${this.counter}-${randomUUID3().slice(0, 8)}-${safe}`;
14406
- const file = path35.join(directory, name);
14597
+ const file = path37.join(directory, name);
14407
14598
  await writeFile4(file, bytes, { mode: FILE_MODE });
14408
14599
  return { path: file, bytes: bytes.length, name };
14409
14600
  }
14410
14601
  /** Borra lo de una pestana. Se llama al cerrarla. */
14411
14602
  async clearTerminal(terminalId) {
14412
- await rm3(path35.join(this.root, safeSegment(terminalId)), {
14603
+ await rm3(path37.join(this.root, safeSegment(terminalId)), {
14413
14604
  recursive: true,
14414
14605
  force: true
14415
14606
  }).catch(() => {
@@ -14431,9 +14622,9 @@ var PasteStore = class {
14431
14622
  }
14432
14623
  await Promise.all(
14433
14624
  entries.map(async (entry) => {
14434
- const full = path35.join(this.root, entry);
14625
+ const full = path37.join(this.root, entry);
14435
14626
  try {
14436
- const info = await stat18(full);
14627
+ const info = await stat19(full);
14437
14628
  if (now - info.mtimeMs > STALE_AFTER_MS) {
14438
14629
  await rm3(full, { recursive: true, force: true });
14439
14630
  }
@@ -14450,8 +14641,8 @@ function safeSegment(value) {
14450
14641
 
14451
14642
  // packages/server/src/session-index.ts
14452
14643
  import { EventEmitter as EventEmitter4 } from "node:events";
14453
- import { mkdir as mkdir9, readFile as readFile12, stat as stat19, writeFile as writeFile5 } from "node:fs/promises";
14454
- import path36 from "node:path";
14644
+ import { mkdir as mkdir9, readFile as readFile13, stat as stat20, writeFile as writeFile5 } from "node:fs/promises";
14645
+ import path38 from "node:path";
14455
14646
  var CACHE_VERSION = 4;
14456
14647
  var vaultPairKey = (agent, sessionId) => `${agent}/${sessionId}`;
14457
14648
  function vaultOnlySessions(natives, vault, listState, ready) {
@@ -14463,7 +14654,10 @@ function vaultOnlySessions(natives, vault, listState, ready) {
14463
14654
  for (const copy of vault) {
14464
14655
  const pair = vaultPairKey(copy.agent, copy.sessionId);
14465
14656
  if (native.has(pair) || seen.has(pair)) continue;
14466
- if (!isImportedAgentId(copy.agent) && (listState.get(copy.agent) ?? "missing") === "unreadable") continue;
14657
+ if (!isImportedAgentId(copy.agent)) {
14658
+ const state = listState.get(copy.agent) ?? "missing";
14659
+ if (state === "unreadable" || state === "off") continue;
14660
+ }
14467
14661
  seen.add(pair);
14468
14662
  const hasCwd = copy.cwd.length > 0;
14469
14663
  result.push({
@@ -14500,7 +14694,7 @@ function cacheKey(agent, ref) {
14500
14694
  }
14501
14695
  async function pathExists3(candidate) {
14502
14696
  try {
14503
- await stat19(candidate);
14697
+ await stat20(candidate);
14504
14698
  return true;
14505
14699
  } catch {
14506
14700
  return false;
@@ -14550,7 +14744,7 @@ function migrateEntryV3(filePath, value) {
14550
14744
  return {
14551
14745
  agent: "claude-code",
14552
14746
  ref: filePath,
14553
- group: path36.basename(path36.dirname(filePath)),
14747
+ group: path38.basename(path38.dirname(filePath)),
14554
14748
  mtimeMs,
14555
14749
  sizeBytes,
14556
14750
  cwd,
@@ -14815,7 +15009,7 @@ var SessionIndex = class extends EventEmitter4 {
14815
15009
  }
14816
15010
  async loadCache() {
14817
15011
  try {
14818
- const raw = await readFile12(sessionIndexCachePath(), "utf8");
15012
+ const raw = await readFile13(sessionIndexCachePath(), "utf8");
14819
15013
  const parsed = JSON.parse(raw);
14820
15014
  const migrated = migrateIndexCache(parsed);
14821
15015
  if (migrated === null) return;
@@ -14845,7 +15039,7 @@ var SessionIndex = class extends EventEmitter4 {
14845
15039
  }
14846
15040
  this.scanning = true;
14847
15041
  try {
14848
- const sources = this.agents.all().map(({ adapter }) => ({
15042
+ const sources = (this.agents.historySources?.() ?? this.agents.all()).map(({ adapter }) => ({
14849
15043
  agent: adapter.id,
14850
15044
  history: adapter.history
14851
15045
  }));
@@ -14865,6 +15059,9 @@ var SessionIndex = class extends EventEmitter4 {
14865
15059
  const outcome = outcomes[position] ?? { items: null, threw: true };
14866
15060
  states.set(source.agent, await nativeListState(source.history, outcome));
14867
15061
  }
15062
+ for (const { adapter } of this.agents.all()) {
15063
+ if (!states.has(adapter.id)) states.set(adapter.id, "off");
15064
+ }
14868
15065
  this.listState = states;
14869
15066
  if (lists.every((items) => items === null)) {
14870
15067
  this.sessions.clear();
@@ -15040,8 +15237,8 @@ var SessionIndex = class extends EventEmitter4 {
15040
15237
  };
15041
15238
 
15042
15239
  // packages/server/src/settings-store.ts
15043
- import { mkdir as mkdir10, readFile as readFile13, stat as stat20 } from "node:fs/promises";
15044
- import path37 from "node:path";
15240
+ import { mkdir as mkdir10, readFile as readFile14, stat as stat21 } from "node:fs/promises";
15241
+ import path39 from "node:path";
15045
15242
  var SETTINGS_VERSION = 1;
15046
15243
  var VAULT_TOOL_RESULT_DEFAULT_CHARS = 64e3;
15047
15244
  var VAULT_TOOL_RESULT_MIN_CHARS = 4e3;
@@ -15050,13 +15247,15 @@ function defaultAppSettings() {
15050
15247
  return {
15051
15248
  version: SETTINGS_VERSION,
15052
15249
  vault: { enabled: false, dir: null, toolResultMaxChars: VAULT_TOOL_RESULT_DEFAULT_CHARS },
15053
- remote: { enabled: false, port: REMOTE_DEFAULT_PORT }
15250
+ remote: { enabled: false, port: REMOTE_DEFAULT_PORT },
15251
+ console: { shell: "auto" },
15252
+ agents: { disabled: [] }
15054
15253
  };
15055
15254
  }
15056
15255
  function isAbsoluteDir(dir, platform) {
15057
15256
  if (dir.length === 0 || dir.includes("\0")) return false;
15058
15257
  if (platform === "win32") return /^[A-Za-z]:[\\/]/.test(dir) || /^\\\\[^\\/]+[\\/][^\\/]+/.test(dir);
15059
- return path37.posix.isAbsolute(dir);
15258
+ return path39.posix.isAbsolute(dir);
15060
15259
  }
15061
15260
  function clampToolResultMaxChars(value) {
15062
15261
  if (typeof value !== "number" || !Number.isFinite(value)) return VAULT_TOOL_RESULT_DEFAULT_CHARS;
@@ -15066,6 +15265,12 @@ function parseAppSettings(value, platform, warn = () => void 0) {
15066
15265
  const record = asRecord(value);
15067
15266
  if (record === null || record["version"] !== SETTINGS_VERSION) return null;
15068
15267
  const settings2 = defaultAppSettings();
15268
+ const consoleRecord = asRecord(record["console"]);
15269
+ const shell = consoleRecord === null ? null : asLiteral(consoleRecord["shell"], CONSOLE_SHELL_IDS);
15270
+ if (shell !== null) settings2.console.shell = shell;
15271
+ const agentsRecord = asRecord(record["agents"]);
15272
+ const disabled = agentsRecord === null ? null : asAgentIdList(agentsRecord["disabled"]);
15273
+ if (disabled !== null) settings2.agents.disabled = disabled;
15069
15274
  const remote = asRecord(record["remote"]);
15070
15275
  if (remote !== null) {
15071
15276
  settings2.remote.enabled = asBoolean(remote["enabled"]) ?? false;
@@ -15078,7 +15283,7 @@ function parseAppSettings(value, platform, warn = () => void 0) {
15078
15283
  if (typeof dir === "string" && isAbsoluteDir(dir, platform)) {
15079
15284
  settings2.vault.dir = dir;
15080
15285
  } else if (dir !== null && dir !== void 0) {
15081
- warn(`[settings] the local copy folder ${JSON.stringify(dir)} isn't an absolute path: using the default one`);
15286
+ warn(`[settings] the backup folder ${JSON.stringify(dir)} isn't an absolute path: using the default one`);
15082
15287
  }
15083
15288
  settings2.vault.toolResultMaxChars = clampToolResultMaxChars(vault["toolResultMaxChars"]);
15084
15289
  return settings2;
@@ -15087,7 +15292,9 @@ function frozen(settings2) {
15087
15292
  return Object.freeze({
15088
15293
  ...settings2,
15089
15294
  vault: Object.freeze({ ...settings2.vault }),
15090
- remote: Object.freeze({ ...settings2.remote })
15295
+ remote: Object.freeze({ ...settings2.remote }),
15296
+ console: Object.freeze({ ...settings2.console }),
15297
+ agents: Object.freeze({ disabled: Object.freeze([...settings2.agents.disabled]) })
15091
15298
  });
15092
15299
  }
15093
15300
  var SettingsStore = class {
@@ -15107,7 +15314,7 @@ var SettingsStore = class {
15107
15314
  async load() {
15108
15315
  let raw;
15109
15316
  try {
15110
- raw = await readFile13(this.filePath, "utf8");
15317
+ raw = await readFile14(this.filePath, "utf8");
15111
15318
  } catch {
15112
15319
  this.current = frozen(defaultAppSettings());
15113
15320
  return;
@@ -15135,7 +15342,7 @@ var SettingsStore = class {
15135
15342
  async reloadRemote() {
15136
15343
  let parsed;
15137
15344
  try {
15138
- parsed = JSON.parse(await readFile13(this.filePath, "utf8"));
15345
+ parsed = JSON.parse(await readFile14(this.filePath, "utf8"));
15139
15346
  } catch {
15140
15347
  return;
15141
15348
  }
@@ -15146,7 +15353,7 @@ var SettingsStore = class {
15146
15353
  /** Una marca que cambia cuando el archivo cambia (fecha y tamano), o '' si no esta. */
15147
15354
  async stamp() {
15148
15355
  try {
15149
- const info = await stat20(this.filePath);
15356
+ const info = await stat21(this.filePath);
15150
15357
  return `${info.mtimeMs}:${info.size}`;
15151
15358
  } catch {
15152
15359
  return "";
@@ -15167,26 +15374,30 @@ var SettingsStore = class {
15167
15374
  const next = {
15168
15375
  version: SETTINGS_VERSION,
15169
15376
  vault: { ...this.current.vault },
15170
- remote: { ...this.current.remote }
15377
+ remote: { ...this.current.remote },
15378
+ console: { ...this.current.console },
15379
+ agents: { disabled: [...this.current.agents.disabled] }
15171
15380
  };
15172
15381
  const vault = patch.vault ?? {};
15173
15382
  if (vault.enabled !== void 0) next.vault.enabled = vault.enabled;
15174
15383
  if (vault.dir !== void 0) {
15175
15384
  if (vault.dir !== null && !isAbsoluteDir(vault.dir, this.platform)) {
15176
- throw new Error(`The local copy folder has to be an absolute path: ${JSON.stringify(vault.dir)}`);
15385
+ throw new Error(`The backup folder has to be an absolute path: ${JSON.stringify(vault.dir)}`);
15177
15386
  }
15178
15387
  next.vault.dir = vault.dir;
15179
15388
  }
15180
15389
  if (vault.toolResultMaxChars !== void 0) {
15181
15390
  next.vault.toolResultMaxChars = clampToolResultMaxChars(vault.toolResultMaxChars);
15182
15391
  }
15392
+ if (patch.console?.shell !== void 0) next.console.shell = patch.console.shell;
15393
+ if (patch.agents?.disabled !== void 0) next.agents.disabled = [...new Set(patch.agents.disabled)];
15183
15394
  const remote = patch.remote ?? {};
15184
15395
  if (remote.enabled !== void 0) next.remote.enabled = remote.enabled;
15185
15396
  if (remote.port !== void 0) {
15186
15397
  if (!isRemotePort(remote.port)) throw new Error(`The remote access port isn't valid: ${JSON.stringify(remote.port)}`);
15187
15398
  next.remote.port = remote.port;
15188
15399
  }
15189
- await mkdir10(path37.dirname(this.filePath), { recursive: true });
15400
+ await mkdir10(path39.dirname(this.filePath), { recursive: true });
15190
15401
  await writeFileAtomic(this.filePath, `${JSON.stringify(next, null, 2)}
15191
15402
  `);
15192
15403
  this.current = frozen(next);
@@ -15274,8 +15485,8 @@ function supportStartupLineColored() {
15274
15485
 
15275
15486
  // packages/server/src/notes-store.ts
15276
15487
  import { randomUUID as randomUUID4 } from "node:crypto";
15277
- import { mkdir as mkdir11, readFile as readFile14, rename as rename5, rm as rm4, writeFile as writeFile6 } from "node:fs/promises";
15278
- import path38 from "node:path";
15488
+ import { mkdir as mkdir11, readFile as readFile15, rename as rename5, rm as rm4, writeFile as writeFile6 } from "node:fs/promises";
15489
+ import path40 from "node:path";
15279
15490
  var STATE_VERSION2 = 1;
15280
15491
  var WRITE_DEBOUNCE_MS2 = 500;
15281
15492
  var NotesError = class extends ServerTextError {
@@ -15318,7 +15529,7 @@ var NotesStore = class {
15318
15529
  }
15319
15530
  async load() {
15320
15531
  try {
15321
- const raw = await readFile14(this.statePath, "utf8");
15532
+ const raw = await readFile15(this.statePath, "utf8");
15322
15533
  this.notes = parseState2(raw) ?? [];
15323
15534
  } catch {
15324
15535
  this.notes = [];
@@ -15435,7 +15646,7 @@ var NotesStore = class {
15435
15646
  const image = this.findImage(imageId);
15436
15647
  if (image === null) return null;
15437
15648
  try {
15438
- const bytes = await readFile14(this.imagePath(image));
15649
+ const bytes = await readFile15(this.imagePath(image));
15439
15650
  return { mediaType: image.mediaType, data: bytes.toString("base64") };
15440
15651
  } catch {
15441
15652
  return null;
@@ -15455,7 +15666,7 @@ var NotesStore = class {
15455
15666
  const images = [];
15456
15667
  for (const image of note.images) {
15457
15668
  try {
15458
- const bytes = await readFile14(this.imagePath(image));
15669
+ const bytes = await readFile15(this.imagePath(image));
15459
15670
  images.push({ mediaType: image.mediaType, data: bytes.toString("base64") });
15460
15671
  } catch {
15461
15672
  }
@@ -15479,7 +15690,7 @@ var NotesStore = class {
15479
15690
  }
15480
15691
  imagePath(image) {
15481
15692
  const safe = image.imageId.replace(/[^a-zA-Z0-9-]/g, "");
15482
- return path38.join(this.imagesDir, `${safe}.${extensionFor(image.mediaType)}`);
15693
+ return path40.join(this.imagesDir, `${safe}.${extensionFor(image.mediaType)}`);
15483
15694
  }
15484
15695
  async unlinkImage(image) {
15485
15696
  await rm4(this.imagePath(image), { force: true }).catch(() => {
@@ -15497,8 +15708,8 @@ var NotesStore = class {
15497
15708
  async writeNow() {
15498
15709
  this.dirty = false;
15499
15710
  try {
15500
- await mkdir11(path38.dirname(this.statePath), { recursive: true });
15501
- const temporary = path38.join(path38.dirname(this.statePath), `notes.${process.pid}.tmp`);
15711
+ await mkdir11(path40.dirname(this.statePath), { recursive: true });
15712
+ const temporary = path40.join(path40.dirname(this.statePath), `notes.${process.pid}.tmp`);
15502
15713
  await writeFile6(
15503
15714
  temporary,
15504
15715
  JSON.stringify({ version: STATE_VERSION2, notes: this.notes }, null, 2),
@@ -15512,8 +15723,8 @@ var NotesStore = class {
15512
15723
  };
15513
15724
 
15514
15725
  // packages/server/src/draft-store.ts
15515
- import { mkdir as mkdir12, readFile as readFile15, rename as rename6, writeFile as writeFile7 } from "node:fs/promises";
15516
- import path39 from "node:path";
15726
+ import { mkdir as mkdir12, readFile as readFile16, rename as rename6, writeFile as writeFile7 } from "node:fs/promises";
15727
+ import path41 from "node:path";
15517
15728
  var STATE_VERSION3 = 1;
15518
15729
  var WRITE_DEBOUNCE_MS3 = 500;
15519
15730
  var MAX_STORED_DRAFTS = 100;
@@ -15607,7 +15818,7 @@ var DraftStore = class {
15607
15818
  }
15608
15819
  async load() {
15609
15820
  try {
15610
- this.drafts = parseState3(await readFile15(this.statePath, "utf8"));
15821
+ this.drafts = parseState3(await readFile16(this.statePath, "utf8"));
15611
15822
  } catch {
15612
15823
  this.drafts = /* @__PURE__ */ new Map();
15613
15824
  }
@@ -15698,7 +15909,7 @@ var DraftStore = class {
15698
15909
  async writeNow() {
15699
15910
  let onDisk = /* @__PURE__ */ new Map();
15700
15911
  try {
15701
- onDisk = parseState3(await readFile15(this.statePath, "utf8"));
15912
+ onDisk = parseState3(await readFile16(this.statePath, "utf8"));
15702
15913
  } catch {
15703
15914
  }
15704
15915
  const changed = this.changed;
@@ -15712,8 +15923,8 @@ var DraftStore = class {
15712
15923
  trimOldest(merged);
15713
15924
  this.drafts = new Map(merged);
15714
15925
  try {
15715
- await mkdir12(path39.dirname(this.statePath), { recursive: true });
15716
- const temporary = path39.join(path39.dirname(this.statePath), `composer-drafts.${process.pid}.tmp`);
15926
+ await mkdir12(path41.dirname(this.statePath), { recursive: true });
15927
+ const temporary = path41.join(path41.dirname(this.statePath), `composer-drafts.${process.pid}.tmp`);
15717
15928
  const payload = {
15718
15929
  version: STATE_VERSION3,
15719
15930
  drafts: [...merged.values()].map(({ agent, sessionId, updatedAt, draft }) => ({ agent, sessionId, updatedAt, draft }))
@@ -15730,7 +15941,7 @@ var DraftStore = class {
15730
15941
  // packages/server/src/shell-locator.ts
15731
15942
  import { access as access2 } from "node:fs/promises";
15732
15943
  import { constants as fsConstants2 } from "node:fs";
15733
- import path40 from "node:path";
15944
+ import path42 from "node:path";
15734
15945
  async function isExecutable(candidate) {
15735
15946
  try {
15736
15947
  await access2(candidate, process.platform === "win32" ? fsConstants2.R_OK : fsConstants2.X_OK);
@@ -15739,47 +15950,139 @@ async function isExecutable(candidate) {
15739
15950
  return false;
15740
15951
  }
15741
15952
  }
15742
- function windowsPowerShellPath() {
15743
- const systemRoot = process.env["SystemRoot"] ?? process.env["windir"];
15953
+ var SYSTEM_PROBE = {
15954
+ platform: process.platform,
15955
+ env: process.env,
15956
+ findInPath,
15957
+ isExecutable
15958
+ };
15959
+ function windowsPowerShellPath(env) {
15960
+ const systemRoot = env["SystemRoot"] ?? env["windir"];
15744
15961
  if (systemRoot === void 0 || systemRoot.length === 0) return null;
15745
- return path40.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
15962
+ return path42.win32.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
15746
15963
  }
15747
- async function locateWindowsShell() {
15748
- const pwsh = await findInPath("pwsh");
15749
- if (pwsh !== null) return { file: pwsh, args: ["-NoLogo"], name: "PowerShell" };
15750
- const fromPath = await findInPath("powershell");
15751
- if (fromPath !== null) return { file: fromPath, args: ["-NoLogo"], name: "PowerShell" };
15752
- const systemCopy = windowsPowerShellPath();
15753
- if (systemCopy !== null && await isExecutable(systemCopy)) {
15754
- return { file: systemCopy, args: ["-NoLogo"], name: "PowerShell" };
15964
+ function gitBashCandidates(gitPath, env) {
15965
+ const candidates = [];
15966
+ if (gitPath !== null) {
15967
+ let dir = path42.win32.dirname(gitPath);
15968
+ for (let level = 0; level < 3; level++) {
15969
+ candidates.push(path42.win32.join(dir, "bin", "bash.exe"));
15970
+ const parent = path42.win32.dirname(dir);
15971
+ if (parent === dir) break;
15972
+ dir = parent;
15973
+ }
15755
15974
  }
15756
- const comSpec = process.env["ComSpec"];
15757
- if (comSpec !== void 0 && comSpec.length > 0 && await isExecutable(comSpec)) {
15758
- return { file: comSpec, args: [], name: "cmd" };
15975
+ for (const base of [env["ProgramFiles"], env["LOCALAPPDATA"] === void 0 ? void 0 : path42.win32.join(env["LOCALAPPDATA"], "Programs")]) {
15976
+ if (base !== void 0 && base.length > 0) candidates.push(path42.win32.join(base, "Git", "bin", "bash.exe"));
15759
15977
  }
15760
- return null;
15978
+ const systemRoot = (env["SystemRoot"] ?? env["windir"] ?? "").toLowerCase();
15979
+ return [...new Set(candidates)].filter((candidate) => systemRoot.length === 0 || !candidate.toLowerCase().startsWith(systemRoot));
15761
15980
  }
15762
- async function locatePosixShell() {
15763
- const preferred = process.env["SHELL"];
15764
- if (preferred !== void 0 && preferred.length > 0 && await isExecutable(preferred)) {
15765
- return { file: preferred, args: [], name: path40.basename(preferred) };
15981
+ async function locateWindowsShells(probe) {
15982
+ const found = [];
15983
+ const pwsh = await probe.findInPath("pwsh");
15984
+ if (pwsh !== null) found.push({ id: "pwsh", label: "PowerShell 7", location: { file: pwsh, args: ["-NoLogo"], name: "PowerShell" } });
15985
+ const fromPath = await probe.findInPath("powershell");
15986
+ const systemCopy = windowsPowerShellPath(probe.env);
15987
+ const windowsPowerShell = fromPath ?? (systemCopy !== null && await probe.isExecutable(systemCopy) ? systemCopy : null);
15988
+ if (windowsPowerShell !== null) {
15989
+ found.push({ id: "powershell", label: "Windows PowerShell", location: { file: windowsPowerShell, args: ["-NoLogo"], name: "PowerShell" } });
15990
+ }
15991
+ const comSpec = probe.env["ComSpec"];
15992
+ if (comSpec !== void 0 && comSpec.length > 0 && await probe.isExecutable(comSpec)) {
15993
+ found.push({ id: "cmd", label: "cmd", location: { file: comSpec, args: [], name: "cmd" } });
15994
+ }
15995
+ for (const candidate of gitBashCandidates(await probe.findInPath("git"), probe.env)) {
15996
+ if (await probe.isExecutable(candidate)) {
15997
+ found.push({ id: "git-bash", label: "Git Bash", location: { file: candidate, args: ["--login", "-i"], name: "Git Bash" } });
15998
+ break;
15999
+ }
15766
16000
  }
15767
- if (await isExecutable("/bin/bash")) return { file: "/bin/bash", args: [], name: "bash" };
15768
- if (await isExecutable("/bin/sh")) return { file: "/bin/sh", args: [], name: "sh" };
15769
- return null;
16001
+ return found;
16002
+ }
16003
+ async function locatePosixShells(probe) {
16004
+ const found = [];
16005
+ const preferred = probe.env["SHELL"];
16006
+ if (preferred !== void 0 && preferred.length > 0 && await probe.isExecutable(preferred)) {
16007
+ const name = path42.posix.basename(preferred);
16008
+ found.push({ id: "login", label: name, location: { file: preferred, args: [], name } });
16009
+ }
16010
+ for (const [id, file] of [["bash", "/bin/bash"], ["sh", "/bin/sh"], ["zsh", "/bin/zsh"]]) {
16011
+ if (found.some((shell) => shell.location.file === file)) continue;
16012
+ if (await probe.isExecutable(file)) found.push({ id, label: id, location: { file, args: [], name: id } });
16013
+ }
16014
+ return found;
15770
16015
  }
15771
- async function locateShell() {
15772
- return process.platform === "win32" ? locateWindowsShell() : locatePosixShell();
16016
+ async function locateShells(probe = SYSTEM_PROBE) {
16017
+ return probe.platform === "win32" ? locateWindowsShells(probe) : locatePosixShells(probe);
15773
16018
  }
16019
+ var NEVER_AUTO = /* @__PURE__ */ new Set(["git-bash", "zsh"]);
16020
+ function pickShell(candidates, choice) {
16021
+ if (choice !== "auto") {
16022
+ const chosen = candidates.find((candidate) => candidate.id === choice);
16023
+ if (chosen !== void 0) return chosen.location;
16024
+ }
16025
+ return candidates.find((candidate) => !NEVER_AUTO.has(candidate.id))?.location ?? null;
16026
+ }
16027
+
16028
+ // packages/server/src/app-settings-service.ts
16029
+ import { EventEmitter as EventEmitter5 } from "node:events";
16030
+ var AppSettingsChangeError = class extends Error {
16031
+ constructor(reason2) {
16032
+ super(reason2);
16033
+ this.reason = reason2;
16034
+ }
16035
+ reason;
16036
+ };
16037
+ var AppSettingsService = class extends EventEmitter5 {
16038
+ constructor(options) {
16039
+ super();
16040
+ this.options = options;
16041
+ this.disabledAtStart = options.agents.disabledIds();
16042
+ }
16043
+ options;
16044
+ disabledAtStart;
16045
+ /** La consola que abre una nueva con lo que dice `settings.json`. */
16046
+ currentShell() {
16047
+ return pickShell(this.options.shells, this.options.settings.get().console.shell);
16048
+ }
16049
+ status() {
16050
+ const settings2 = this.options.settings.get();
16051
+ return {
16052
+ consoleShell: settings2.console.shell,
16053
+ consoleShells: this.options.shells.map(({ id, label }) => ({ id, label })),
16054
+ shellName: this.currentShell()?.name ?? null,
16055
+ disabledAgents: [...settings2.agents.disabled],
16056
+ disabledAtStart: [...this.disabledAtStart]
16057
+ };
16058
+ }
16059
+ /** Guarda el cambio, avisa al registro si cambio la consola, y emite `status`. */
16060
+ async update(change) {
16061
+ if (change.consoleShell !== void 0 && !this.offered(change.consoleShell)) {
16062
+ throw new AppSettingsChangeError("shell-not-installed");
16063
+ }
16064
+ const before = this.currentShell();
16065
+ await this.options.settings.update({
16066
+ ...change.consoleShell !== void 0 ? { console: { shell: change.consoleShell } } : {},
16067
+ ...change.disabledAgents !== void 0 ? { agents: { disabled: change.disabledAgents } } : {}
16068
+ });
16069
+ const after = this.currentShell();
16070
+ if (after !== before) this.options.onShellChange(after);
16071
+ this.emit("status", this.status());
16072
+ }
16073
+ offered(choice) {
16074
+ return choice === "auto" || this.options.shells.some((shell) => shell.id === choice);
16075
+ }
16076
+ };
15774
16077
 
15775
16078
  // packages/server/src/session-watcher.ts
15776
- import { stat as stat21 } from "node:fs/promises";
16079
+ import { stat as stat22 } from "node:fs/promises";
15777
16080
  import chokidar3 from "chokidar";
15778
16081
  var INDEX_DEBOUNCE_MS = 750;
15779
16082
  var ROOT_PROBE_MS = 5e3;
15780
16083
  async function isDirectory3(target) {
15781
16084
  try {
15782
- return (await stat21(target)).isDirectory();
16085
+ return (await stat22(target)).isDirectory();
15783
16086
  } catch {
15784
16087
  return false;
15785
16088
  }
@@ -15849,7 +16152,7 @@ function watchSessions(index, hub, agents, options = {}) {
15849
16152
  probe.unref();
15850
16153
  probes.add(probe);
15851
16154
  };
15852
- for (const { adapter } of agents.all()) {
16155
+ for (const { adapter } of agents.historySources?.() ?? agents.all()) {
15853
16156
  for (const root of adapter.history.roots()) {
15854
16157
  if (root.watch !== void 0) {
15855
16158
  subscribeRoot(adapter.id, root, root.watch);
@@ -15881,8 +16184,8 @@ function watchSessions(index, hub, agents, options = {}) {
15881
16184
 
15882
16185
  // packages/server/src/terminal-registry.ts
15883
16186
  import { randomUUID as randomUUID5 } from "node:crypto";
15884
- import { EventEmitter as EventEmitter5 } from "node:events";
15885
- import { stat as stat22 } from "node:fs/promises";
16187
+ import { EventEmitter as EventEmitter6 } from "node:events";
16188
+ import { stat as stat23 } from "node:fs/promises";
15886
16189
 
15887
16190
  // packages/server/src/launch-hook-slot.ts
15888
16191
  var LaunchHookSlot = class {
@@ -16144,8 +16447,8 @@ async function resolveLaunchPlan(launch) {
16144
16447
  }
16145
16448
 
16146
16449
  // packages/server/src/workspace-store.ts
16147
- import { mkdir as mkdir13, readFile as readFile16, writeFile as writeFile8 } from "node:fs/promises";
16148
- import path41 from "node:path";
16450
+ import { mkdir as mkdir13, readFile as readFile17, writeFile as writeFile8 } from "node:fs/promises";
16451
+ import path43 from "node:path";
16149
16452
  var STATE_VERSION4 = 1;
16150
16453
  var WRITE_DEBOUNCE_MS4 = 400;
16151
16454
  var LEGACY_TAB_AGENT = "claude-code";
@@ -16267,7 +16570,7 @@ var WorkspaceStore = class {
16267
16570
  pending = null;
16268
16571
  async load() {
16269
16572
  try {
16270
- const raw = await readFile16(workspaceStatePath(), "utf8");
16573
+ const raw = await readFile17(workspaceStatePath(), "utf8");
16271
16574
  return parseState4(raw) ?? EMPTY_STATE();
16272
16575
  } catch {
16273
16576
  return EMPTY_STATE();
@@ -16299,7 +16602,7 @@ var WorkspaceStore = class {
16299
16602
  try {
16300
16603
  await mkdir13(appConfigDir(), { recursive: true });
16301
16604
  const target = workspaceStatePath();
16302
- const temporary = path41.join(path41.dirname(target), `workspace.${process.pid}.tmp`);
16605
+ const temporary = path43.join(path43.dirname(target), `workspace.${process.pid}.tmp`);
16303
16606
  await writeFile8(temporary, JSON.stringify(serializeState(state), null, 2), "utf8");
16304
16607
  const { rename: rename7 } = await import("node:fs/promises");
16305
16608
  await rename7(temporary, target);
@@ -16314,7 +16617,7 @@ var MAX_TERMINALS = 24;
16314
16617
  var RESTART_EXIT_TIMEOUT_MS = 1e4;
16315
16618
  var DEFAULT_COLS = 80;
16316
16619
  var DEFAULT_ROWS = 24;
16317
- var TerminalRegistry = class extends EventEmitter5 {
16620
+ var TerminalRegistry = class extends EventEmitter6 {
16318
16621
  constructor(agents, shell, store, sessionAgentOf = () => null) {
16319
16622
  super();
16320
16623
  this.agents = agents;
@@ -16338,6 +16641,10 @@ var TerminalRegistry = class extends EventEmitter5 {
16338
16641
  unavailableTabs = [];
16339
16642
  /** Pestanas guardadas de una CLI que esta build no conoce. Idem, crudas. */
16340
16643
  foreignTabs = [];
16644
+ /** Otra consola para las que se abran desde ahora (Ajustes, Hito 41). Las abiertas no cambian. */
16645
+ setShell(shell) {
16646
+ this.shell = shell;
16647
+ }
16341
16648
  list() {
16342
16649
  return this.order.map((id) => this.terminals.get(id)?.descriptor).filter((descriptor) => descriptor !== void 0);
16343
16650
  }
@@ -16427,7 +16734,9 @@ var TerminalRegistry = class extends EventEmitter5 {
16427
16734
  cwd: options.cwd,
16428
16735
  // Provisional: `spawn` lo confirma con lo que diga el adaptador.
16429
16736
  sessionId: kind === "agent" ? options.resumeSessionId ?? randomUUID5() : "",
16430
- label: options.label ?? "",
16737
+ // Una consola lleva el nombre de la suya: desde el Hito 41 pueden convivir
16738
+ // PowerShell y Git Bash, y el nombre de la consola nueva ya no es el de todas.
16739
+ label: options.label ?? (kind === "shell" ? this.shell?.name ?? "" : ""),
16431
16740
  resumed,
16432
16741
  createdAt: Date.now(),
16433
16742
  // Lo pone `spawn`. Una entrada que no llega a tener proceso no queda en
@@ -16530,7 +16839,7 @@ var TerminalRegistry = class extends EventEmitter5 {
16530
16839
  async assertDirectory(cwd) {
16531
16840
  let info;
16532
16841
  try {
16533
- info = await stat22(cwd);
16842
+ info = await stat23(cwd);
16534
16843
  } catch {
16535
16844
  throw new TerminalOpenError("invalid-cwd", serverText("cwdMissing", { cwd }));
16536
16845
  }
@@ -16957,19 +17266,19 @@ import { WebSocketServer } from "ws";
16957
17266
 
16958
17267
  // packages/server/src/directory-picker.ts
16959
17268
  import { randomUUID as randomUUID6 } from "node:crypto";
16960
- import { mkdir as mkdir14, readdir as readdir11, stat as stat23 } from "node:fs/promises";
17269
+ import { mkdir as mkdir14, readdir as readdir11, stat as stat24 } from "node:fs/promises";
16961
17270
  import { homedir as homedir11 } from "node:os";
16962
- import path42 from "node:path";
17271
+ import path44 from "node:path";
16963
17272
  var MAX_ENTRIES = 500;
16964
17273
  var MAX_PICKERS = 8;
16965
17274
  var SKIPPED = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "bin", "obj", "$RECYCLE.BIN"]);
16966
17275
  var DirectoryPickerError = class extends ServerTextError {
16967
17276
  };
16968
17277
  function isInsideProtected(target, dirs) {
16969
- const resolved = path42.resolve(target);
17278
+ const resolved = path44.resolve(target);
16970
17279
  return dirs.some((dir) => {
16971
- const protectedDir = path42.resolve(dir);
16972
- return resolved === protectedDir || resolved.startsWith(protectedDir + path42.sep);
17280
+ const protectedDir = path44.resolve(dir);
17281
+ return resolved === protectedDir || resolved.startsWith(protectedDir + path44.sep);
16973
17282
  });
16974
17283
  }
16975
17284
  async function listRoots() {
@@ -16981,7 +17290,7 @@ async function listRoots() {
16981
17290
  letters.map(async (letter) => {
16982
17291
  const drive = `${letter}:\\`;
16983
17292
  try {
16984
- await stat23(drive);
17293
+ await stat24(drive);
16985
17294
  return drive;
16986
17295
  } catch {
16987
17296
  return null;
@@ -17033,14 +17342,14 @@ var DirectoryPickers = class {
17033
17342
  async enter(pickerId, name) {
17034
17343
  const picker = this.require(pickerId);
17035
17344
  if (name === "..") {
17036
- const parent = path42.dirname(picker.current);
17345
+ const parent = path44.dirname(picker.current);
17037
17346
  if (parent !== picker.current) picker.current = parent;
17038
17347
  return this.listing(picker);
17039
17348
  }
17040
17349
  if (!picker.entries.has(name)) {
17041
17350
  throw new DirectoryPickerError(serverText("pickerNotListed"));
17042
17351
  }
17043
- const target = path42.join(picker.current, name);
17352
+ const target = path44.join(picker.current, name);
17044
17353
  if (isInsideProtected(target, this.protectedDirs)) {
17045
17354
  throw new DirectoryPickerError(serverText("pickerCliFolder"));
17046
17355
  }
@@ -17080,7 +17389,7 @@ var DirectoryPickers = class {
17080
17389
  throw new DirectoryPickerError(serverText("pickerNameChars"));
17081
17390
  }
17082
17391
  }
17083
- const target = path42.join(picker.current, clean);
17392
+ const target = path44.join(picker.current, clean);
17084
17393
  if (isInsideProtected(target, this.protectedDirs)) {
17085
17394
  throw new DirectoryPickerError(serverText("pickerNoProjectsHere"));
17086
17395
  }
@@ -17117,7 +17426,7 @@ var DirectoryPickers = class {
17117
17426
  let truncated = false;
17118
17427
  try {
17119
17428
  const found = await readdir11(picker.current, { withFileTypes: true });
17120
- const dirs = found.filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => !SKIPPED.has(name)).filter((name) => !isInsideProtected(path42.join(picker.current, name), this.protectedDirs)).sort((a, b) => a.localeCompare(b));
17429
+ const dirs = found.filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => !SKIPPED.has(name)).filter((name) => !isInsideProtected(path44.join(picker.current, name), this.protectedDirs)).sort((a, b) => a.localeCompare(b));
17121
17430
  truncated = dirs.length > MAX_ENTRIES;
17122
17431
  names = dirs.slice(0, MAX_ENTRIES);
17123
17432
  } catch {
@@ -17128,15 +17437,15 @@ var DirectoryPickers = class {
17128
17437
  path: picker.current,
17129
17438
  entries: names,
17130
17439
  roots,
17131
- canGoUp: path42.dirname(picker.current) !== picker.current,
17440
+ canGoUp: path44.dirname(picker.current) !== picker.current,
17132
17441
  truncated
17133
17442
  };
17134
17443
  }
17135
17444
  };
17136
17445
 
17137
17446
  // packages/server/src/file-browser.ts
17138
- import { readFile as readFile17, readdir as readdir12, stat as stat24 } from "node:fs/promises";
17139
- import path43 from "node:path";
17447
+ import { readFile as readFile18, readdir as readdir12, stat as stat25 } from "node:fs/promises";
17448
+ import path45 from "node:path";
17140
17449
  var MAX_ENTRIES2 = 1e3;
17141
17450
  var SEARCH_MAX_RESULTS = 200;
17142
17451
  var SEARCH_MAX_DIRECTORIES = 4e3;
@@ -17199,7 +17508,7 @@ function languageFor(fileName) {
17199
17508
  const lower = fileName.toLowerCase();
17200
17509
  const byName = LANGUAGE_BY_NAME[lower];
17201
17510
  if (byName !== void 0) return byName;
17202
- const extension = path43.extname(lower);
17511
+ const extension = path45.extname(lower);
17203
17512
  return LANGUAGE_BY_EXTENSION[extension] ?? null;
17204
17513
  }
17205
17514
  function looksBinary2(buffer) {
@@ -17231,7 +17540,7 @@ async function listDirectory(cwd, relativePath, options = {}) {
17231
17540
  let sizeBytes = 0;
17232
17541
  let modifiedAt = 0;
17233
17542
  try {
17234
- const info = await stat24(path43.join(absolute, candidate.name));
17543
+ const info = await stat25(path45.join(absolute, candidate.name));
17235
17544
  sizeBytes = candidate.isDirectory ? 0 : info.size;
17236
17545
  modifiedAt = info.mtimeMs;
17237
17546
  } catch {
@@ -17264,7 +17573,7 @@ async function readLevel(absolute) {
17264
17573
  const always = ALWAYS_SKIP.has(dirent.name.toLowerCase());
17265
17574
  if (dirent.isSymbolicLink()) {
17266
17575
  try {
17267
- const target = await stat24(path43.join(absolute, dirent.name));
17576
+ const target = await stat25(path45.join(absolute, dirent.name));
17268
17577
  candidates.push({
17269
17578
  name: dirent.name,
17270
17579
  isDirectory: target.isDirectory(),
@@ -17293,7 +17602,7 @@ async function markIgnored(repoRoot2, levels) {
17293
17602
  for (const level of levels) {
17294
17603
  for (const entry of level.entries) {
17295
17604
  if (entry.hidden !== null) continue;
17296
- keys.set(entry, toPosixPath(path43.relative(repoRoot2, path43.join(level.absolute, entry.name))));
17605
+ keys.set(entry, toPosixPath(path45.relative(repoRoot2, path45.join(level.absolute, entry.name))));
17297
17606
  }
17298
17607
  }
17299
17608
  if (keys.size === 0) return;
@@ -17354,7 +17663,7 @@ async function searchFiles(cwd, query, options = {}) {
17354
17663
  let sizeBytes = 0;
17355
17664
  let modifiedAt = 0;
17356
17665
  try {
17357
- const info = await stat24(path43.join(level.absolute, candidate.name));
17666
+ const info = await stat25(path45.join(level.absolute, candidate.name));
17358
17667
  sizeBytes = candidate.isDirectory ? 0 : info.size;
17359
17668
  modifiedAt = info.mtimeMs;
17360
17669
  } catch {
@@ -17388,12 +17697,12 @@ async function searchFiles(cwd, query, options = {}) {
17388
17697
  }
17389
17698
  async function readPreview(cwd, relativePath) {
17390
17699
  const absolute = await resolveInside(cwd, relativePath, { mustExist: true });
17391
- const info = await stat24(absolute);
17700
+ const info = await stat25(absolute);
17392
17701
  if (info.isDirectory()) {
17393
17702
  throw new Error("It's a directory, not a file.");
17394
17703
  }
17395
- const content = await readFile17(absolute);
17396
- const fileName = path43.basename(absolute);
17704
+ const content = await readFile18(absolute);
17705
+ const fileName = path45.basename(absolute);
17397
17706
  if (looksBinary2(content)) {
17398
17707
  return {
17399
17708
  path: relativePath,
@@ -17982,14 +18291,14 @@ var TerminalWriteQueue = class {
17982
18291
  };
17983
18292
 
17984
18293
  // packages/server/src/vault/service.ts
17985
- import { mkdir as mkdir17, stat as stat28 } from "node:fs/promises";
18294
+ import { mkdir as mkdir17, stat as stat29 } from "node:fs/promises";
17986
18295
  import { homedir as homedir12 } from "node:os";
17987
- import path47 from "node:path";
18296
+ import path49 from "node:path";
17988
18297
 
17989
18298
  // packages/server/src/vault/move.ts
17990
18299
  import { constants } from "node:fs";
17991
- import { copyFile as copyFile2, lstat, mkdir as mkdir15, readdir as readdir13, rm as rm5, stat as stat25, utimes, writeFile as writeFile9 } from "node:fs/promises";
17992
- import path44 from "node:path";
18300
+ import { copyFile as copyFile2, lstat, mkdir as mkdir15, readdir as readdir13, rm as rm5, stat as stat26, utimes, writeFile as writeFile9 } from "node:fs/promises";
18301
+ import path46 from "node:path";
17993
18302
  var MOVED_ENTRIES = ["vault.json", "sessions", "memory"];
17994
18303
  var OWN_TEMP_FILE = /\.\d+\.[0-9a-f]{12}\.tmp$/;
17995
18304
  async function copyTree(source, target, result) {
@@ -18002,12 +18311,12 @@ async function copyTree(source, target, result) {
18002
18311
  if (info.isDirectory()) {
18003
18312
  await mkdir15(target, { recursive: true });
18004
18313
  for (const name of await readdir13(source)) {
18005
- await copyTree(path44.join(source, name), path44.join(target, name), result);
18314
+ await copyTree(path46.join(source, name), path46.join(target, name), result);
18006
18315
  }
18007
18316
  return;
18008
18317
  }
18009
18318
  if (!info.isFile() || OWN_TEMP_FILE.test(source)) return;
18010
- await mkdir15(path44.dirname(target), { recursive: true });
18319
+ await mkdir15(path46.dirname(target), { recursive: true });
18011
18320
  try {
18012
18321
  await copyFile2(source, target, constants.COPYFILE_EXCL);
18013
18322
  } catch (error) {
@@ -18024,7 +18333,7 @@ async function moveVault(from, to) {
18024
18333
  const result = { copied: 0, skipped: 0 };
18025
18334
  await mkdir15(to, { recursive: true });
18026
18335
  for (const entry of MOVED_ENTRIES) {
18027
- await copyTree(path44.join(from, entry), path44.join(to, entry), result);
18336
+ await copyTree(path46.join(from, entry), path46.join(to, entry), result);
18028
18337
  }
18029
18338
  return result;
18030
18339
  }
@@ -18056,10 +18365,10 @@ function checkVaultTarget(current, target, options) {
18056
18365
  return { kind: "ok" };
18057
18366
  }
18058
18367
  async function canWriteInto(dir) {
18059
- const probe = temporaryPathFor(path44.join(dir, ".escritura"));
18368
+ const probe = temporaryPathFor(path46.join(dir, ".escritura"));
18060
18369
  try {
18061
18370
  await mkdir15(dir, { recursive: true });
18062
- if (!(await stat25(dir)).isDirectory()) return false;
18371
+ if (!(await stat26(dir)).isDirectory()) return false;
18063
18372
  await writeFile9(probe, "");
18064
18373
  return true;
18065
18374
  } catch {
@@ -18162,12 +18471,12 @@ function serializeSession(input) {
18162
18471
  }
18163
18472
 
18164
18473
  // packages/server/src/vault/writer.ts
18165
- import { readdir as readdir15, rm as rm6, stat as stat27 } from "node:fs/promises";
18166
- import path46 from "node:path";
18474
+ import { readdir as readdir15, rm as rm6, stat as stat28 } from "node:fs/promises";
18475
+ import path48 from "node:path";
18167
18476
 
18168
18477
  // packages/server/src/vault/memory-copy.ts
18169
- import { mkdir as mkdir16, readdir as readdir14, readFile as readFile18, stat as stat26, utimes as utimes2 } from "node:fs/promises";
18170
- import path45 from "node:path";
18478
+ import { mkdir as mkdir16, readdir as readdir14, readFile as readFile19, stat as stat27, utimes as utimes2 } from "node:fs/promises";
18479
+ import path47 from "node:path";
18171
18480
  var MEMORY_FILE_MAX_BYTES = 1024 * 1024;
18172
18481
  var MEMORY_MAX_FILES = 500;
18173
18482
  var MEMORY_SOURCE_FILE = "source.json";
@@ -18192,7 +18501,7 @@ async function listMemoryNotes(cwd) {
18192
18501
  let absolute;
18193
18502
  try {
18194
18503
  absolute = await resolveInside(cwd, `${MEMORY_DIR}/${name}`, { mustExist: true });
18195
- const info = await stat26(absolute);
18504
+ const info = await stat27(absolute);
18196
18505
  if (!info.isFile() || info.size > MEMORY_FILE_MAX_BYTES) continue;
18197
18506
  notes.push({ name, absolute, size: info.size, mtimeMs: info.mtimeMs });
18198
18507
  } catch {
@@ -18206,7 +18515,7 @@ async function measureProjectMemory(cwd) {
18206
18515
  }
18207
18516
  async function isUpToDate(target, note) {
18208
18517
  try {
18209
- const info = await stat26(target);
18518
+ const info = await stat27(target);
18210
18519
  return info.isFile() && info.size === note.size && Math.abs(info.mtimeMs - note.mtimeMs) < SAME_MTIME_MS;
18211
18520
  } catch {
18212
18521
  return false;
@@ -18214,7 +18523,7 @@ async function isUpToDate(target, note) {
18214
18523
  }
18215
18524
  async function exists3(file) {
18216
18525
  try {
18217
- await stat26(file);
18526
+ await stat27(file);
18218
18527
  return true;
18219
18528
  } catch {
18220
18529
  return false;
@@ -18233,11 +18542,11 @@ async function copyProjectMemory(dir, cwd, platform, deps = DEFAULT_WRITE_DEPS)
18233
18542
  await mkdir16(target, { recursive: true });
18234
18543
  };
18235
18544
  for (const note of notes) {
18236
- const destination = path45.join(target, note.name);
18545
+ const destination = path47.join(target, note.name);
18237
18546
  if (await isUpToDate(destination, note)) continue;
18238
18547
  let content;
18239
18548
  try {
18240
- content = await readFile18(note.absolute);
18549
+ content = await readFile19(note.absolute);
18241
18550
  } catch {
18242
18551
  continue;
18243
18552
  }
@@ -18249,7 +18558,7 @@ async function copyProjectMemory(dir, cwd, platform, deps = DEFAULT_WRITE_DEPS)
18249
18558
  result.files += 1;
18250
18559
  result.bytes += content.length;
18251
18560
  }
18252
- const source = path45.join(target, MEMORY_SOURCE_FILE);
18561
+ const source = path47.join(target, MEMORY_SOURCE_FILE);
18253
18562
  if (result.files > 0 || !await exists3(source)) {
18254
18563
  await prepare();
18255
18564
  await writeFileAtomic(source, `${JSON.stringify({ cwd, copiedAt: deps.now() })}
@@ -18743,7 +19052,7 @@ var VaultWriter = class {
18743
19052
  }
18744
19053
  async assetOnDisk(dir, agent, sessionId, asset, bytes) {
18745
19054
  try {
18746
- const info = await stat27(assetFile(dir, agent, sessionId, asset));
19055
+ const info = await stat28(assetFile(dir, agent, sessionId, asset));
18747
19056
  return info.isFile() && info.size === bytes;
18748
19057
  } catch {
18749
19058
  return false;
@@ -18843,17 +19152,17 @@ var VaultWriter = class {
18843
19152
  const folders = [dir];
18844
19153
  const subfolders = async (parent) => {
18845
19154
  try {
18846
- return (await readdir15(parent, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => path46.join(parent, entry.name));
19155
+ return (await readdir15(parent, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => path48.join(parent, entry.name));
18847
19156
  } catch {
18848
19157
  return [];
18849
19158
  }
18850
19159
  };
18851
19160
  for (const agentDir of await subfolders(sessionsRoot2(dir))) {
18852
- if (!SESSION_AGENT_IDS.includes(path46.basename(agentDir))) continue;
19161
+ if (!SESSION_AGENT_IDS.includes(path48.basename(agentDir))) continue;
18853
19162
  folders.push(agentDir);
18854
19163
  folders.push(...(await subfolders(agentDir)).filter((folder) => folder.endsWith(".assets")));
18855
19164
  }
18856
- folders.push(...await subfolders(path46.join(dir, "memory")));
19165
+ folders.push(...await subfolders(path48.join(dir, "memory")));
18857
19166
  for (const folder of folders) {
18858
19167
  let names;
18859
19168
  try {
@@ -18863,9 +19172,9 @@ var VaultWriter = class {
18863
19172
  }
18864
19173
  for (const name of names) {
18865
19174
  if (!OWN_TEMP_FILE2.test(name)) continue;
18866
- const file = path46.join(folder, name);
19175
+ const file = path48.join(folder, name);
18867
19176
  try {
18868
- const info = await stat27(file);
19177
+ const info = await stat28(file);
18869
19178
  if (info.isFile() && now - info.mtimeMs > TEMP_MAX_AGE_MS) await rm6(file, { force: true });
18870
19179
  } catch {
18871
19180
  }
@@ -19053,14 +19362,14 @@ var VaultService = class {
19053
19362
  async setDir(target) {
19054
19363
  const { platform, agents, settings: settings2, catalog } = this.options;
19055
19364
  const current = this.dir();
19056
- const protectedDirs = [...agents.protectedDirs(), path47.join(this.options.homeDir ?? homedir12(), ".gemini")];
19365
+ const protectedDirs = [...agents.protectedDirs(), path49.join(this.options.homeDir ?? homedir12(), ".gemini")];
19057
19366
  const check = checkVaultTarget(current, target, { protectedDirs, platform });
19058
19367
  if (check.kind === "refused") throw new VaultError(check.text);
19059
19368
  if (check.kind === "same") return;
19060
19369
  try {
19061
19370
  await this.writer.exclusive("moving", async () => {
19062
19371
  if (!await canWriteInto(target)) throw new VaultError(serverText("vaultCannotWrite"));
19063
- const hadCopy = await stat28(vaultMarkerFile(current)).then(
19372
+ const hadCopy = await stat29(vaultMarkerFile(current)).then(
19064
19373
  () => true,
19065
19374
  () => false
19066
19375
  );
@@ -19094,7 +19403,7 @@ var VaultService = class {
19094
19403
  /** Abre la carpeta de la copia con el explorador del sistema. */
19095
19404
  async reveal() {
19096
19405
  const dir = this.activeDir();
19097
- const isFolder = await stat28(dir).then(
19406
+ const isFolder = await stat29(dir).then(
19098
19407
  (info) => info.isDirectory(),
19099
19408
  () => false
19100
19409
  );
@@ -19118,7 +19427,7 @@ var VaultService = class {
19118
19427
  fromCopy: true
19119
19428
  });
19120
19429
  const file = sessionExportFile(dir, agent, sessionId);
19121
- await mkdir17(path47.dirname(file), { recursive: true });
19430
+ await mkdir17(path49.dirname(file), { recursive: true });
19122
19431
  await writeFileAtomic(file, text, this.writeDeps);
19123
19432
  this.openWithSystem(file);
19124
19433
  return file;
@@ -19222,7 +19531,7 @@ var VaultService = class {
19222
19531
  continue;
19223
19532
  }
19224
19533
  const fileName = this.exportFileName(summary, names);
19225
- await writeFileAtomic(path47.join(folder, fileName), text, this.writeDeps);
19534
+ await writeFileAtomic(path49.join(folder, fileName), text, this.writeDeps);
19226
19535
  entries.push({
19227
19536
  updatedAt: summary.updatedAt,
19228
19537
  agentLabel: this.labelOf(summary.agent),
@@ -19233,9 +19542,9 @@ var VaultService = class {
19233
19542
  });
19234
19543
  await nextTurn3();
19235
19544
  }
19236
- const name = project.cwd.length > 0 ? path47.basename(project.cwd) || project.cwd : project.fallbackName;
19545
+ const name = project.cwd.length > 0 ? path49.basename(project.cwd) || project.cwd : project.fallbackName;
19237
19546
  const index = renderProjectIndex({ name, cwd: project.cwd, exportedAt: this.now(), entries, skipped });
19238
- await writeFileAtomic(path47.join(folder, "index.md"), index, this.writeDeps);
19547
+ await writeFileAtomic(path49.join(folder, "index.md"), index, this.writeDeps);
19239
19548
  this.openWithSystem(folder);
19240
19549
  return { sessions: entries.length, skipped, folder };
19241
19550
  }
@@ -19386,7 +19695,7 @@ function attachTerminalSocket(options) {
19386
19695
  httpServer,
19387
19696
  port,
19388
19697
  token,
19389
- shell,
19698
+ appSettings,
19390
19699
  registry,
19391
19700
  index,
19392
19701
  archived,
@@ -19597,6 +19906,8 @@ function attachTerminalSocket(options) {
19597
19906
  const stopRemoteStatus = remote.onChange(
19598
19907
  () => broadcastToHost({ type: "remote.status", status: remote.status() })
19599
19908
  );
19909
+ const onAppSettingsStatus = (settings2) => broadcast({ type: "settings.status", settings: settings2 });
19910
+ appSettings.on("status", onAppSettingsStatus);
19600
19911
  const stopRemoteRevoked = remote.onRevoked((deviceId) => {
19601
19912
  for (const [socket, id] of remoteSockets) {
19602
19913
  if (deviceId === null || id === deviceId) socket.close(REMOTE_REVOKED_CLOSE_CODE, "revoked");
@@ -19709,7 +20020,7 @@ function attachTerminalSocket(options) {
19709
20020
  defaultAgent: agents.defaultAgent(),
19710
20021
  platform: process.platform,
19711
20022
  defaultCwd,
19712
- shellName: shell?.name ?? null,
20023
+ shellName: appSettings.currentShell()?.name ?? null,
19713
20024
  remoteClient: deviceId !== null
19714
20025
  });
19715
20026
  send(socket, terminalListMessage());
@@ -19722,6 +20033,7 @@ function attachTerminalSocket(options) {
19722
20033
  send(socket, { type: "composer.drafts", drafts: draftEntriesOf(registry.list()) });
19723
20034
  send(socket, { type: "vault.status", status: vault.status() });
19724
20035
  if (deviceId === null) send(socket, { type: "remote.status", status: remote.status() });
20036
+ send(socket, { type: "settings.status", settings: appSettings.status() });
19725
20037
  socket.on("message", (raw) => {
19726
20038
  const text = Array.isArray(raw) ? Buffer.concat(raw).toString("utf8") : Buffer.from(raw).toString("utf8");
19727
20039
  const message = parseClientMessage(text);
@@ -20775,6 +21087,24 @@ function attachTerminalSocket(options) {
20775
21087
  case "search.cancel":
20776
21088
  globalSearch.cancel(message.searchId);
20777
21089
  break;
21090
+ /*
21091
+ Ajustes (Hito 41, §6.32). Una consola que ya no esta, o un disco que no
21092
+ deja escribir, se dicen; lo demas lo valido el parser. A una ventana
21093
+ remota no llega: lo frena `remoteRefusal`.
21094
+ */
21095
+ case "settings.update": {
21096
+ const { type: _type, ...change } = message;
21097
+ void appSettings.update(change).catch((error) => {
21098
+ if (error instanceof AppSettingsChangeError) {
21099
+ sendError(socket, "settings-failed", serverText("settingsShellMissing"));
21100
+ return;
21101
+ }
21102
+ const detail = error instanceof Error ? error.message : String(error);
21103
+ console.warn("[settings] a change failed:", detail);
21104
+ sendError(socket, "settings-failed", serverText("settingsFailed"), detail);
21105
+ });
21106
+ break;
21107
+ }
20778
21108
  /*
20779
21109
  El acceso remoto (hito 37). Hasta aca solo llega una ventana del
20780
21110
  anfitrion: a un equipo remoto lo freno `remoteRefusal`. El estado
@@ -20875,6 +21205,7 @@ function attachTerminalSocket(options) {
20875
21205
  memory.off("status", onMemoryStatus);
20876
21206
  stopAgentChanges();
20877
21207
  stopVaultStatus();
21208
+ appSettings.off("status", onAppSettingsStatus);
20878
21209
  stopRemoteStatus();
20879
21210
  stopRemoteRevoked();
20880
21211
  for (const client of wss.clients) client.terminate();
@@ -20883,8 +21214,8 @@ function attachTerminalSocket(options) {
20883
21214
  }
20884
21215
 
20885
21216
  // packages/server/src/vault/catalog.ts
20886
- import { open as open7, readdir as readdir16, stat as stat29 } from "node:fs/promises";
20887
- import path48 from "node:path";
21217
+ import { open as open7, readdir as readdir16, stat as stat30 } from "node:fs/promises";
21218
+ import path50 from "node:path";
20888
21219
  import { createInterface as createInterface2 } from "node:readline";
20889
21220
  var HEADER_MAX_BYTES = 64 * 1024;
20890
21221
  var LOAD_CONCURRENCY = 16;
@@ -20904,7 +21235,7 @@ async function assetBytesOf(dir, agent, sessionId) {
20904
21235
  for (const name of names) {
20905
21236
  if (!VAULT_ASSET_NAME_PATTERN.test(name)) continue;
20906
21237
  try {
20907
- const info = await stat29(path48.join(folder, name));
21238
+ const info = await stat30(path50.join(folder, name));
20908
21239
  if (info.isFile()) total += info.size;
20909
21240
  } catch {
20910
21241
  }
@@ -20915,7 +21246,7 @@ async function readEntry(dir, agent, sessionId) {
20915
21246
  const file = sessionFile(dir, agent, sessionId);
20916
21247
  let sizeBytes;
20917
21248
  try {
20918
- const info = await stat29(file);
21249
+ const info = await stat30(file);
20919
21250
  if (!info.isFile()) return { kind: "absent" };
20920
21251
  sizeBytes = info.size;
20921
21252
  } catch {
@@ -21124,15 +21455,15 @@ var VaultCatalog = class {
21124
21455
  };
21125
21456
 
21126
21457
  // packages/server/src/index.ts
21127
- var serverDir = path49.dirname(fileURLToPath2(import.meta.url));
21458
+ var serverDir = path51.dirname(fileURLToPath2(import.meta.url));
21128
21459
  var isPackaged = true;
21129
- var webRoot = isPackaged ? serverDir : path49.resolve(serverDir, "../../web");
21130
- var webDist = isPackaged ? path49.join(serverDir, "web") : path49.join(webRoot, "dist");
21131
- var repoRoot = path49.resolve(serverDir, "../../..");
21460
+ var webRoot = isPackaged ? serverDir : path51.resolve(serverDir, "../../web");
21461
+ var webDist = isPackaged ? path51.join(serverDir, "web") : path51.join(webRoot, "dist");
21462
+ var repoRoot = path51.resolve(serverDir, "../../..");
21132
21463
  var isProduction = isPackaged || process.argv.includes("--prod");
21133
21464
  function resolveDefaultCwd() {
21134
21465
  const override = process.env["AGENT_WORKBENCH_CWD"];
21135
- if (override !== void 0 && override.length > 0) return path49.resolve(override);
21466
+ if (override !== void 0 && override.length > 0) return path51.resolve(override);
21136
21467
  if (isPackaged) return process.cwd();
21137
21468
  return repoRoot;
21138
21469
  }
@@ -21207,7 +21538,7 @@ async function mountDevUi(app, httpServer) {
21207
21538
  }
21208
21539
  var LOOKS_LIKE_FILE = /\.[a-zA-Z0-9]{1,8}$/;
21209
21540
  function mountBuiltUi(app) {
21210
- const indexHtml = path49.join(webDist, "index.html");
21541
+ const indexHtml = path51.join(webDist, "index.html");
21211
21542
  if (!existsSync4(indexHtml)) {
21212
21543
  throw new Error(
21213
21544
  `There's no compiled interface in ${webDist}.
@@ -21261,17 +21592,18 @@ ${line}`);
21261
21592
  async function main() {
21262
21593
  const token = createSessionToken();
21263
21594
  const defaultCwd = resolveDefaultCwd();
21264
- const agents = createAgentRegistry();
21265
- const [, shell] = await Promise.all([agents.locateAll(), locateShell()]);
21266
21595
  const movedFrom = await migrateLegacyConfigDir();
21267
21596
  if (movedFrom !== null) console.log(`Configuration moved from ${movedFrom} to ${appConfigDir()}`);
21597
+ const settings2 = new SettingsStore();
21598
+ await settings2.load();
21599
+ const agents = createAgentRegistry(settings2.get().agents.disabled);
21600
+ const [, shells] = await Promise.all([agents.locateAll(), locateShells()]);
21601
+ const shell = pickShell(shells, settings2.get().console.shell);
21268
21602
  await agents.prepareAll();
21269
21603
  ensurePtyHelperExecutable();
21270
21604
  const store = new WorkspaceStore();
21271
21605
  const archived = new ArchivedSessions();
21272
21606
  await archived.load();
21273
- const settings2 = new SettingsStore();
21274
- await settings2.load();
21275
21607
  const catalog = new VaultCatalog();
21276
21608
  await catalog.load(settings2.get().vault.dir ?? defaultVaultDir());
21277
21609
  const index = new SessionIndex(agents, archived, catalog);
@@ -21283,6 +21615,12 @@ async function main() {
21283
21615
  store,
21284
21616
  (sessionId) => index.agentOf(sessionId)
21285
21617
  );
21618
+ const appSettings = new AppSettingsService({
21619
+ settings: settings2,
21620
+ agents,
21621
+ shells,
21622
+ onShellChange: (next) => registry.setShell(next)
21623
+ });
21286
21624
  const notes = new NotesStore();
21287
21625
  await notes.load();
21288
21626
  const drafts = new DraftStore();
@@ -21325,7 +21663,7 @@ async function main() {
21325
21663
  httpServer,
21326
21664
  port,
21327
21665
  token,
21328
- shell,
21666
+ appSettings,
21329
21667
  registry,
21330
21668
  index,
21331
21669
  archived,