agent-workbench 0.2.0 → 0.3.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 path46 from "node:path";
6
+ import path47 from "node:path";
7
7
  import { fileURLToPath as fileURLToPath2 } from "node:url";
8
8
  import express from "express";
9
9
 
@@ -69,6 +69,20 @@ function cycleDistance(from, to, cycle = PERMISSION_MODE_CYCLE) {
69
69
  }
70
70
  var LAUNCH_PERMISSION_MODE = "auto";
71
71
 
72
+ // packages/shared/src/server-text.ts
73
+ function serverText(key, ...params) {
74
+ const [values] = params;
75
+ return values === void 0 ? { key } : { key, params: values };
76
+ }
77
+ var ServerTextError = class extends Error {
78
+ text;
79
+ constructor(text, options) {
80
+ super(text.key, options);
81
+ this.text = text;
82
+ this.name = new.target.name;
83
+ }
84
+ };
85
+
72
86
  // packages/shared/src/agents.ts
73
87
  var AGENT_IDS = ["claude-code", "codex", "opencode", "antigravity"];
74
88
  var IMPORTED_AGENT_IDS = ["gemini-cli", "antigravity-ide"];
@@ -354,21 +368,22 @@ var MEMORY_AGENT_LABELS = {
354
368
  opencode: "OpenCode"
355
369
  };
356
370
  var MEMORY_BLOCK_BODY = [
357
- "## Memoria del proyecto",
371
+ "## Project memory",
358
372
  "",
359
- "La memoria de este proyecto vive en `.agents/memory/` y es compartida: la leen y",
360
- "la escriben todos los agentes que trabajan ac\xE1, con cualquier herramienta.",
373
+ "This project's memory lives in `.agents/memory/` and is shared: every agent",
374
+ "that works here reads and writes it, whatever tool it runs in.",
361
375
  "",
362
- "- Al empezar, le\xE9 `.agents/memory/MEMORY.md`. Es el \xEDndice: una l\xEDnea por nota.",
363
- "- Cuando aprendas algo que sirva en otra sesi\xF3n \u2014una decisi\xF3n y su motivo, una",
364
- " restricci\xF3n, una trampa ya pisada, una preferencia del usuario\u2014, guardalo en",
365
- " un archivo propio dentro de `.agents/memory/` y agreg\xE1 su l\xEDnea al \xEDndice:",
366
- " `- [T\xEDtulo](archivo.md) \u2014 de qu\xE9 trata, en una l\xEDnea`.",
367
- "- Cada nota empieza con `name:` y `description:` entre dos l\xEDneas `---`, y",
368
- " despu\xE9s el hecho. Si ya hay una nota sobre lo mismo, actualizala en vez de",
369
- " crear otra.",
370
- "- No guardes lo que ya cuenta el repositorio ni lo que s\xF3lo importa hoy.",
371
- "- Guard\xE1 la memoria ac\xE1, no en la carpeta de memoria propia de tu herramienta."
376
+ "- When you start, read `.agents/memory/MEMORY.md`. It is the index: one line",
377
+ " per note.",
378
+ "- When you learn something that would help in another session \u2014a decision and",
379
+ " its reason, a constraint, a pitfall already hit, a user preference\u2014, save it",
380
+ " in its own file inside `.agents/memory/` and add its line to the index:",
381
+ " `- [Title](file.md) \u2014 what it is about, in one line`.",
382
+ "- Each note starts with `name:` and `description:` between two `---` lines,",
383
+ " followed by the fact. If there is already a note about the same thing,",
384
+ " update it instead of creating another one.",
385
+ "- Don't save what the repository already records or what only matters today.",
386
+ "- Save memory here, not in your tool's own memory folder."
372
387
  ];
373
388
  function memoryBlock(file) {
374
389
  const lines = [MEMORY_BLOCK_START, ...MEMORY_BLOCK_BODY];
@@ -377,10 +392,10 @@ function memoryBlock(file) {
377
392
  return lines.join("\n");
378
393
  }
379
394
  var MEMORY_INDEX_TEMPLATE = [
380
- "# Memoria del proyecto",
395
+ "# Project memory",
381
396
  "",
382
- "\xCDndice de la memoria compartida entre agentes. Una l\xEDnea por nota, con este",
383
- "formato: `- [T\xEDtulo](archivo.md) \u2014 de qu\xE9 trata, en una l\xEDnea`.",
397
+ "Index of the memory shared between agents. One line per note, in this",
398
+ "format: `- [Title](file.md) \u2014 what it is about, in one line`.",
384
399
  "",
385
400
  ""
386
401
  ].join("\n");
@@ -606,11 +621,14 @@ function parseVaultBodyLine(value) {
606
621
  }
607
622
 
608
623
  // packages/shared/src/protocol.ts
609
- var PROTOCOL_VERSION = 7;
624
+ var PROTOCOL_VERSION = 8;
610
625
  var WS_PATH = "/ws";
611
626
  var TOKEN_QUERY_PARAM = "token";
612
627
  var MAX_SUBMIT_IMAGE_BYTES = 12 * 1024 * 1024;
613
628
  var MAX_SUBMIT_IMAGES = 8;
629
+ var MAX_SUBMIT_FILE_BYTES = 10 * 1024 * 1024;
630
+ var MAX_SUBMIT_FILES = 4;
631
+ var CONTINUE_LABEL_MAX_CHARS = 120;
614
632
  function parseSubmitImage(value) {
615
633
  const record = asRecord(value);
616
634
  if (record === null) return null;
@@ -618,6 +636,13 @@ function parseSubmitImage(value) {
618
636
  const data = asNonEmptyString(record["data"]);
619
637
  return mediaType === null || data === null ? null : { mediaType, data };
620
638
  }
639
+ function parseSubmitFile(value) {
640
+ const record = asRecord(value);
641
+ if (record === null) return null;
642
+ const name = asNonEmptyString(record["name"]);
643
+ const data = asNonEmptyString(record["data"]);
644
+ return name === null || data === null ? null : { name, data };
645
+ }
621
646
  function parseSelection(value) {
622
647
  if (Array.isArray(value)) {
623
648
  const chosen = [];
@@ -648,6 +673,11 @@ function parseClientMessage(raw) {
648
673
  const images = asArrayOf(record["images"], parseSubmitImage);
649
674
  if (terminalId === null || text === null || images === null) return null;
650
675
  const message = { type: "agent.submit", terminalId, text, images };
676
+ if (record["files"] !== void 0) {
677
+ const files = asArrayOf(record["files"], parseSubmitFile);
678
+ if (files === null) return null;
679
+ if (files.length > 0) message.files = files;
680
+ }
651
681
  if (record["send"] === false) message.send = false;
652
682
  return message;
653
683
  }
@@ -909,7 +939,15 @@ function parseClientMessage(raw) {
909
939
  const agent = asLiteral(record["agent"], SESSION_AGENT_IDS);
910
940
  const sessionId = asNonEmptyString(record["sessionId"]);
911
941
  if (requestId === null || agent === null || sessionId === null) return null;
912
- const base = { type: "session.continue", requestId, agent, sessionId };
942
+ const rawLabel = asString(record["label"]);
943
+ const label = rawLabel === null ? "" : rawLabel.replace(/\s+/g, " ").trim().slice(0, CONTINUE_LABEL_MAX_CHARS);
944
+ const base = {
945
+ type: "session.continue",
946
+ requestId,
947
+ agent,
948
+ sessionId,
949
+ ...label.length > 0 ? { label } : {}
950
+ };
913
951
  const rawTarget = record["target"];
914
952
  if (rawTarget === void 0 || rawTarget === null) return null;
915
953
  const target = asLiteral(rawTarget, AGENT_IDS);
@@ -984,6 +1022,14 @@ function insertionIndex(order, describe, descriptor, platform) {
984
1022
  return last === -1 ? order.length : last + 1;
985
1023
  }
986
1024
 
1025
+ // packages/shared/src/pasted-text.ts
1026
+ function pastedBlockPattern() {
1027
+ return /(^|\n)\[Start of pasted text #(\d{1,6})\]\n([\s\S]*?)\n\[End of pasted text #\2\](?=\n|$)/g;
1028
+ }
1029
+ function pastedBoundaryPattern() {
1030
+ return /\[(?:Start|End) of pasted text #\d{1,6}\]/g;
1031
+ }
1032
+
987
1033
  // packages/server/src/archived-sessions.ts
988
1034
  import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
989
1035
  import path2 from "node:path";
@@ -1126,7 +1172,7 @@ var ArchivedSessions = class {
1126
1172
  );
1127
1173
  await rename(temporary, target);
1128
1174
  } catch (error) {
1129
- console.warn("[archivadas] no se pudo guardar la lista:", error);
1175
+ console.warn("[archived] couldn't save the list:", error);
1130
1176
  }
1131
1177
  }
1132
1178
  };
@@ -1202,7 +1248,7 @@ async function locateCommand(command) {
1202
1248
  };
1203
1249
  }
1204
1250
  function commandNotFoundMessage(command, installUrl) {
1205
- return `No se encontro el comando "${command}" en el PATH. Agent Workbench usa la CLI que ya tengas instalada: no la incluye ni la descarga. Instalala desde ${installUrl} y volve a arrancar.`;
1251
+ return serverText("cliMissing", { command, url: installUrl });
1206
1252
  }
1207
1253
 
1208
1254
  // packages/server/src/agents/antigravity/catalog.ts
@@ -1285,7 +1331,7 @@ function nodeHasUnflaggedSqlite(nodeVersion) {
1285
1331
  return major > 23;
1286
1332
  }
1287
1333
  function sqliteUnavailableText(label, nodeVersion = process.version) {
1288
- return `esta version de Node (${nodeVersion}) no trae node:sqlite; el historial de ${label} necesita Node ${NODE_SQLITE_MIN_VERSION} o posterior.`;
1334
+ return `this Node version (${nodeVersion}) has no node:sqlite; the ${label} history needs Node ${NODE_SQLITE_MIN_VERSION} or later.`;
1289
1335
  }
1290
1336
  function isSqliteExperimentalWarning(warning, typeOrOptions) {
1291
1337
  let type = "Warning";
@@ -1440,7 +1486,7 @@ var ReadOnlyDatabase = class {
1440
1486
  db.close();
1441
1487
  this.state = "schema";
1442
1488
  console.warn(
1443
- `[${this.options.label}] a ${this.file} le falta la columna ${missing.table}.${missing.column}: no se lee hasta reiniciar.`
1489
+ `[${this.options.label}] ${this.file} is missing the column ${missing.table}.${missing.column}: it isn't read until a restart.`
1444
1490
  );
1445
1491
  return null;
1446
1492
  }
@@ -1547,7 +1593,7 @@ var FileStampSignal = class {
1547
1593
  try {
1548
1594
  listener(file);
1549
1595
  } catch (error) {
1550
- console.warn("[antigravity] un aviso de cambio del indice fallo:", error);
1596
+ console.warn("[antigravity] an index change listener failed:", error);
1551
1597
  }
1552
1598
  }
1553
1599
  }
@@ -1981,7 +2027,7 @@ var AntigravityCatalog = class {
1981
2027
  if (signature.db.size > CATALOG_LIMITS.summariesBytes) {
1982
2028
  this.summaries = /* @__PURE__ */ new Map();
1983
2029
  this.summariesStamp = signature.key;
1984
- this.warnOnce(signature.key, `el indice de conversaciones pesa mas de ${CATALOG_LIMITS.summariesBytes / 1024 / 1024} MB; no se lee.`);
2030
+ this.warnOnce(signature.key, `the conversation index is over ${CATALOG_LIMITS.summariesBytes / 1024 / 1024} MB; it isn't read.`);
1985
2031
  return;
1986
2032
  }
1987
2033
  const sqlite = this.sqlite();
@@ -2011,7 +2057,7 @@ var AntigravityCatalog = class {
2011
2057
  this.summaries = next;
2012
2058
  this.summariesStamp = copied.key;
2013
2059
  } catch (error) {
2014
- this.warnOnce(signature.key, `no pude leer el indice de conversaciones: ${error instanceof Error ? error.message : String(error)}`);
2060
+ this.warnOnce(signature.key, `couldn't read the conversation index: ${error instanceof Error ? error.message : String(error)}`);
2015
2061
  } finally {
2016
2062
  await rm(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 50 }).catch(() => void 0);
2017
2063
  }
@@ -2228,7 +2274,7 @@ var LogWatcher = class {
2228
2274
  if (lines !== null) this.take(lines);
2229
2275
  }
2230
2276
  if (this.reader?.exhausted === true && !this.disposed) {
2231
- this.warn(`[antigravity] el log de ${label} paso 256 MB; dejo de seguirlo`);
2277
+ this.warn(`[antigravity] the log of ${label} went over 256 MB; stopped following it`);
2232
2278
  this.mode = "stopped";
2233
2279
  }
2234
2280
  } catch {
@@ -2237,7 +2283,7 @@ var LogWatcher = class {
2237
2283
  async search(label) {
2238
2284
  const { launchedAt, pid } = this.options;
2239
2285
  if (this.now() - launchedAt > (this.options.fallbackWindowMs ?? FALLBACK_WINDOW_MS)) {
2240
- this.warn(`[antigravity] no pude descubrir la conversacion de ${label}`);
2286
+ this.warn(`[antigravity] couldn't discover the conversation of ${label}`);
2241
2287
  this.mode = "stopped";
2242
2288
  return;
2243
2289
  }
@@ -2278,7 +2324,7 @@ var LogWatcher = class {
2278
2324
  try {
2279
2325
  this.options.onConversation(id);
2280
2326
  } catch (error) {
2281
- console.warn("[antigravity] el aviso de conversacion nueva fallo:", error);
2327
+ console.warn("[antigravity] the new conversation listener failed:", error);
2282
2328
  }
2283
2329
  }
2284
2330
  }
@@ -2292,12 +2338,17 @@ import path7 from "node:path";
2292
2338
  var TITLE_MAX_LENGTH = 90;
2293
2339
  var UNTITLED_SESSION_TITLE = "Sesion sin titulo";
2294
2340
  function toTitle(raw) {
2295
- let text = raw.replace(/<[^>]{1,80}>/g, " ").replace(/```[\s\S]*?```/g, " ").replace(/\s+/g, " ").trim();
2341
+ let text = withoutPastedText(raw).replace(/<[^>]{1,80}>/g, " ").replace(/```[\s\S]*?```/g, " ").replace(/\s+/g, " ").trim();
2296
2342
  if (text.length > TITLE_MAX_LENGTH) {
2297
2343
  text = `${text.slice(0, TITLE_MAX_LENGTH - 1).trimEnd()}\u2026`;
2298
2344
  }
2299
2345
  return text;
2300
2346
  }
2347
+ function withoutPastedText(raw) {
2348
+ const normalized = raw.replace(/\r\n?/g, "\n");
2349
+ const typed = normalized.replace(pastedBlockPattern(), "$1");
2350
+ return typed.trim().length > 0 ? typed : normalized.replace(pastedBoundaryPattern(), " ");
2351
+ }
2301
2352
 
2302
2353
  // packages/server/src/agents/antigravity/session-follower.ts
2303
2354
  import { stat as stat4 } from "node:fs/promises";
@@ -2350,7 +2401,7 @@ var JsonlFollower = class {
2350
2401
  */
2351
2402
  setPath(filePath) {
2352
2403
  if (this.path !== null) {
2353
- throw new Error(`El seguidor ya tiene archivo: ${this.path}`);
2404
+ throw new Error(`The follower already has a file: ${this.path}`);
2354
2405
  }
2355
2406
  this.path = filePath;
2356
2407
  }
@@ -2855,7 +2906,7 @@ var AntigravitySessionFollower = class {
2855
2906
  get label() {
2856
2907
  if (this.jsonl?.filePath !== null && this.jsonl?.filePath !== void 0) return this.jsonl.filePath;
2857
2908
  if (this.paths !== null) return this.paths.full;
2858
- return "antigravity:(sin conversacion)";
2909
+ return "antigravity:(no conversation)";
2859
2910
  }
2860
2911
  /** El valor provisional sale de `defaults`: aca no hay nada que preparar. */
2861
2912
  async start() {
@@ -3841,7 +3892,7 @@ function createAntigravityAdapter(options = {}) {
3841
3892
  try {
3842
3893
  await installStatusLineScript(integrationsDir());
3843
3894
  } catch (error) {
3844
- console.warn(`[antigravity] no pude reinstalar el script de la status line: ${String(error)}`);
3895
+ console.warn(`[antigravity] couldn't reinstall the status line script: ${String(error)}`);
3845
3896
  }
3846
3897
  }
3847
3898
  const changed = settings2.statusLine.state !== state;
@@ -3875,12 +3926,12 @@ function createAntigravityAdapter(options = {}) {
3875
3926
  launch(input) {
3876
3927
  const { resumeSessionId } = input;
3877
3928
  if (resumeSessionId !== null && !CONVERSATION_ID_PATTERN.test(resumeSessionId)) {
3878
- throw new Error("Id de conversacion de Antigravity CLI invalido.");
3929
+ throw new Error("Invalid Antigravity CLI conversation id.");
3879
3930
  }
3880
3931
  let logPath = cliLogPathFor(input.launchToken);
3881
3932
  if (logPath !== null && input.location.prefixArgs.length > 0 && !SHIM_SAFE_PATH.test(logPath)) {
3882
3933
  console.warn(
3883
- "[antigravity] la CLI llega por un shim y la carpeta temporal tiene caracteres que cmd /c corta: sin log propio, una pestana nueva no descubre su conversacion ni ve un /clear."
3934
+ "[antigravity] the CLI runs through a shim and the temp folder has characters that cmd /c cuts: without its own log, a new tab doesn't discover its conversation or see a /clear."
3884
3935
  );
3885
3936
  logPath = null;
3886
3937
  }
@@ -3994,7 +4045,7 @@ function createAntigravityAdapter(options = {}) {
3994
4045
  try {
3995
4046
  await installStatusLineScript(integrationsDir());
3996
4047
  } catch (error) {
3997
- console.warn(`[antigravity] no pude instalar el script de la status line: ${String(error)}`);
4048
+ console.warn(`[antigravity] couldn't install the status line script: ${String(error)}`);
3998
4049
  }
3999
4050
  await removeStaleCliLogs();
4000
4051
  await store.removeStale();
@@ -4370,28 +4421,24 @@ import path15 from "node:path";
4370
4421
  // packages/server/src/path-guard.ts
4371
4422
  import { realpath } from "node:fs/promises";
4372
4423
  import path14 from "node:path";
4373
- var InvalidPathError = class extends Error {
4374
- constructor(message) {
4375
- super(message);
4376
- this.name = "InvalidPathError";
4377
- }
4424
+ var InvalidPathError = class extends ServerTextError {
4378
4425
  };
4379
4426
  function toPosixPath(value) {
4380
4427
  return value.split(path14.sep).join("/");
4381
4428
  }
4382
4429
  function assertRelativeShape(relativePath) {
4383
4430
  if (relativePath.includes("\0")) {
4384
- throw new InvalidPathError("La ruta trae un byte nulo.");
4431
+ throw new InvalidPathError(serverText("pathNullByte"));
4385
4432
  }
4386
4433
  if (path14.isAbsolute(relativePath) || /^[a-zA-Z]:/.test(relativePath)) {
4387
- throw new InvalidPathError("Solo se aceptan rutas relativas al directorio de la pestana.");
4434
+ throw new InvalidPathError(serverText("pathNotRelative"));
4388
4435
  }
4389
4436
  if (relativePath.startsWith("/") || relativePath.startsWith("\\")) {
4390
- throw new InvalidPathError("Solo se aceptan rutas relativas al directorio de la pestana.");
4437
+ throw new InvalidPathError(serverText("pathNotRelative"));
4391
4438
  }
4392
4439
  const segments = relativePath.split(/[\\/]/);
4393
4440
  if (segments.includes("..")) {
4394
- throw new InvalidPathError("La ruta se sale del directorio de la pestana.");
4441
+ throw new InvalidPathError(serverText("pathOutside"));
4395
4442
  }
4396
4443
  }
4397
4444
  function isInside(root, candidate) {
@@ -4403,25 +4450,25 @@ async function resolveInside(root, relativePath, options = {}) {
4403
4450
  assertRelativeShape(relativePath);
4404
4451
  const absolute = path14.resolve(root, relativePath);
4405
4452
  if (!isInside(root, absolute)) {
4406
- throw new InvalidPathError("La ruta se sale del directorio de la pestana.");
4453
+ throw new InvalidPathError(serverText("pathOutside"));
4407
4454
  }
4408
4455
  let realRoot;
4409
4456
  try {
4410
4457
  realRoot = await realpath(root);
4411
4458
  } catch {
4412
- throw new InvalidPathError("El directorio de la pestana ya no existe.");
4459
+ throw new InvalidPathError(serverText("tabDirGone"));
4413
4460
  }
4414
4461
  let realTarget;
4415
4462
  try {
4416
4463
  realTarget = await realpath(absolute);
4417
4464
  } catch {
4418
4465
  if (options.mustExist === true) {
4419
- throw new InvalidPathError("La ruta no existe.");
4466
+ throw new InvalidPathError(serverText("pathMissing"));
4420
4467
  }
4421
4468
  realTarget = await realpathOfNearestParent(absolute);
4422
4469
  }
4423
4470
  if (!isInside(realRoot, realTarget)) {
4424
- throw new InvalidPathError("La ruta se sale del directorio de la pestana.");
4471
+ throw new InvalidPathError(serverText("pathOutside"));
4425
4472
  }
4426
4473
  return absolute;
4427
4474
  }
@@ -4432,7 +4479,7 @@ async function realpathOfNearestParent(absolute) {
4432
4479
  return await realpath(current);
4433
4480
  } catch {
4434
4481
  const parent = path14.dirname(current);
4435
- if (parent === current) throw new InvalidPathError("La ruta no existe.");
4482
+ if (parent === current) throw new InvalidPathError(serverText("pathMissing"));
4436
4483
  current = parent;
4437
4484
  }
4438
4485
  }
@@ -4449,6 +4496,7 @@ function isPlanFileName(fileName) {
4449
4496
  if (fileName.length === 0 || fileName.length > 255) return false;
4450
4497
  if (fileName.includes("\0")) return false;
4451
4498
  if (path15.basename(fileName) !== fileName) return false;
4499
+ if (path15.win32.basename(fileName) !== fileName) return false;
4452
4500
  if (fileName === "." || fileName === "..") return false;
4453
4501
  return fileName.toLowerCase().endsWith(".md");
4454
4502
  }
@@ -4544,6 +4592,7 @@ import path17 from "node:path";
4544
4592
  import path16 from "node:path";
4545
4593
  var HARNESS_TAGS = [
4546
4594
  "system-reminder",
4595
+ "task-notification",
4547
4596
  "command-name",
4548
4597
  "command-message",
4549
4598
  "command-args",
@@ -4558,6 +4607,11 @@ var HARNESS_BLOCK = new RegExp(`<(${HARNESS_TAGS.join("|")})>[\\s\\S]*?</\\1>`,
4558
4607
  function cleanUserText(raw) {
4559
4608
  return raw.replace(HARNESS_BLOCK, "").trim();
4560
4609
  }
4610
+ function isHumanOrigin(origin) {
4611
+ if (typeof origin !== "object" || origin === null) return true;
4612
+ const kind = origin["kind"];
4613
+ return typeof kind !== "string" || kind === "human";
4614
+ }
4561
4615
  function readUsage(value) {
4562
4616
  if (typeof value !== "object" || value === null) return null;
4563
4617
  const record = value;
@@ -4715,10 +4769,7 @@ function toQueuedUserEvent(record, lineNumber, limits) {
4715
4769
  const attachmentRecord = attachment;
4716
4770
  if (attachmentRecord["type"] !== "queued_command") return null;
4717
4771
  if (attachmentRecord["commandMode"] !== "prompt") return null;
4718
- const origin = attachmentRecord["origin"];
4719
- if (typeof origin === "object" && origin !== null) {
4720
- if (origin["kind"] !== "human") return null;
4721
- }
4772
+ if (!isHumanOrigin(attachmentRecord["origin"])) return null;
4722
4773
  const prompt = attachmentRecord["prompt"];
4723
4774
  if (typeof prompt !== "string") return null;
4724
4775
  const cleaned = cleanUserText(prompt);
@@ -4804,6 +4855,7 @@ function toConversationEvent(record, lineNumber, limits = TRANSPORT_LIMITS) {
4804
4855
  if (record["isSidechain"] === true) return null;
4805
4856
  if (type === "attachment") return toQueuedUserEvent(record, lineNumber, limits);
4806
4857
  if (type !== "user" && type !== "assistant") return null;
4858
+ if (type === "user" && !isHumanOrigin(record["origin"])) return null;
4807
4859
  const message = record["message"];
4808
4860
  if (typeof message !== "object" || message === null) return null;
4809
4861
  const messageRecord = message;
@@ -5452,7 +5504,7 @@ var ClaudeCodeSessionFollower = class {
5452
5504
  if (isSameFilePath(this.inner.filePath, filePath, process.platform)) return true;
5453
5505
  if (this.inner.getState() === "waiting" && path17.basename(filePath).toLowerCase() === `${this.sessionId.toLowerCase()}.jsonl`) {
5454
5506
  console.warn(
5455
- `[conversacion] la sesion ${this.sessionId.slice(0, 8)} escribio en ${filePath}, no en la ruta calculada; me mudo ahi.`
5507
+ `[conversation] session ${this.sessionId.slice(0, 8)} wrote to ${filePath}, not to the computed path; moving there.`
5456
5508
  );
5457
5509
  this.inner = new ConversationFollower(filePath, void 0, {
5458
5510
  ...this.options,
@@ -5891,7 +5943,7 @@ function codexHome() {
5891
5943
  if (!warnedHomes.has(configured)) {
5892
5944
  warnedHomes.add(configured);
5893
5945
  console.warn(
5894
- `[codex] CODEX_HOME no es una ruta absoluta (${configured}): no se lee el historial de Codex.`
5946
+ `[codex] CODEX_HOME isn't an absolute path (${configured}): the Codex history isn't read.`
5895
5947
  );
5896
5948
  }
5897
5949
  return null;
@@ -6185,17 +6237,17 @@ function pickPendingTab(candidate, pending, married = []) {
6185
6237
  terminalId: chosen2.terminalId,
6186
6238
  confirmedByText: true,
6187
6239
  uncertain,
6188
- reason: uncertain ? "dos pestanas mandaron el mismo texto" : null
6240
+ reason: uncertain ? "two tabs sent the same text" : null
6189
6241
  };
6190
6242
  }
6191
6243
  const chosen = latest(eligible);
6192
6244
  let reason2 = null;
6193
6245
  if (eligible.some((tab) => tab !== chosen && Math.abs(tab.launchedAt - chosen.launchedAt) < CLOSE_LAUNCH_MS)) {
6194
- reason2 = "dos pestanas del mismo proyecto se lanzaron con menos de 1,5 s de diferencia";
6246
+ reason2 = "two tabs of the same project were launched less than 1.5 s apart";
6195
6247
  } else if (chosen.submitted.length > 0) {
6196
- reason2 = "el primer mensaje no es el que mando el cuadro de escritura";
6248
+ reason2 = "the first message isn't the one the input box sent";
6197
6249
  } else if (married.some((tab) => tab.terminalId !== chosen.terminalId && tab.cwdKey === candidate.cwdKey)) {
6198
- reason2 = "hay otra pestana de Codex abierta en el mismo proyecto";
6250
+ reason2 = "another Codex tab is open in the same project";
6199
6251
  }
6200
6252
  return { terminalId: chosen.terminalId, confirmedByText: false, uncertain: reason2 !== null, reason: reason2 };
6201
6253
  }
@@ -6303,7 +6355,7 @@ var CodexSessionDiscovery = class {
6303
6355
  scanOnce() {
6304
6356
  if (this.running !== null) return this.running;
6305
6357
  const run = this.scan().catch((error) => {
6306
- console.warn("[codex] fallo el descubrimiento de sesiones:", error);
6358
+ console.warn("[codex] session discovery failed:", error);
6307
6359
  }).finally(() => {
6308
6360
  this.running = null;
6309
6361
  });
@@ -6378,9 +6430,9 @@ var CodexSessionDiscovery = class {
6378
6430
  const id8 = candidate.sessionId.slice(0, 8);
6379
6431
  const tab8 = tab.terminalId.slice(0, 8);
6380
6432
  if (picked.uncertain) {
6381
- console.warn(`[codex] la sesion ${id8} se asigno a la pestana ${tab8} sin confirmar: ${picked.reason ?? ""}`);
6433
+ console.warn(`[codex] session ${id8} was assigned to tab ${tab8} without confirmation: ${picked.reason ?? ""}`);
6382
6434
  }
6383
- debugLog("registro", `sesion de codex ${id8} -> ${tab8} (${picked.confirmedByText ? "texto" : "lanzamiento"})`);
6435
+ debugLog("registry", `codex session ${id8} -> ${tab8} (${picked.confirmedByText ? "text" : "launch"})`);
6384
6436
  tab.reportSessionId(candidate.sessionId);
6385
6437
  }
6386
6438
  this.stopTimerIfIdle();
@@ -6455,7 +6507,7 @@ function listableMeta(line, item) {
6455
6507
  if (meta.id.toLowerCase() !== item.sessionId.toLowerCase()) return null;
6456
6508
  if (typeof meta.source !== "string" || !INTERACTIVE_SOURCES.includes(meta.source)) return null;
6457
6509
  if (meta.historyMode !== null && !LISTED_HISTORY_MODES.includes(meta.historyMode)) {
6458
- debugLog("indice", `rollout de codex ${item.sessionId.slice(0, 8)} en modo ${meta.historyMode}: no se lista`);
6510
+ debugLog("index", `codex rollout ${item.sessionId.slice(0, 8)} in ${meta.historyMode} mode: not listed`);
6459
6511
  return null;
6460
6512
  }
6461
6513
  if (meta.cwd.length === 0) return null;
@@ -6698,7 +6750,7 @@ var CodexRolloutSink = class {
6698
6750
  }
6699
6751
  this.warnedForeignMeta = true;
6700
6752
  console.warn(
6701
- `[codex] el archivo de la sesion ${this.sessionId.slice(0, 8)} dice ser ${meta.id.slice(0, 8)}; se sigue leyendo igual.`
6753
+ `[codex] the file of session ${this.sessionId.slice(0, 8)} says it's ${meta.id.slice(0, 8)}; reading it anyway.`
6702
6754
  );
6703
6755
  }
6704
6756
  consumeEvent(kind, payload, lineNumber, at, events, pendingContent) {
@@ -6846,7 +6898,7 @@ var CodexSessionFollower = class {
6846
6898
  get label() {
6847
6899
  const filePath = this.jsonl.filePath;
6848
6900
  if (filePath !== null) return filePath;
6849
- return this.sessionId.length > 0 ? `codex:${this.sessionId.slice(0, 8)}` : "codex:(sin sesion)";
6901
+ return this.sessionId.length > 0 ? `codex:${this.sessionId.slice(0, 8)}` : "codex:(no session)";
6850
6902
  }
6851
6903
  async start() {
6852
6904
  await this.lookup();
@@ -7043,15 +7095,21 @@ var CODEX_CAPABILITIES = {
7043
7095
  // Sin estado publicado no hay espera que mirar: manda el candado de 10.8.
7044
7096
  waitingBlocksSubmit: false
7045
7097
  };
7046
- var CODEX_INPUT = {
7047
- imageReference: "bare-path-paste",
7048
- pieceGapMs: 400,
7049
- pasteMarkers: true,
7050
- enterSeparately: true,
7051
- interruptPresses: 1,
7052
- // `@` abre el buscador de la TUI: el transcript se nombra entre comillas y lo lee el agente.
7053
- transcriptReference: "quoted-path"
7054
- };
7098
+ function codexInput(platform) {
7099
+ const windows = platform === "win32";
7100
+ return {
7101
+ imageReference: "bare-path-paste",
7102
+ pieceGapMs: 400,
7103
+ pasteMarkers: true,
7104
+ enterSeparately: true,
7105
+ interruptPresses: 1,
7106
+ // `@` abre el buscador de la TUI: el transcript se nombra entre comillas y lo lee el agente.
7107
+ transcriptReference: "quoted-path",
7108
+ endBeforeSubmit: windows,
7109
+ readyAfterLaunchMs: windows ? 3e3 : 0
7110
+ };
7111
+ }
7112
+ var CODEX_INPUT = codexInput(process.platform);
7055
7113
  function createCodexAdapter() {
7056
7114
  const discovery = new CodexSessionDiscovery();
7057
7115
  return {
@@ -7077,7 +7135,7 @@ function createCodexAdapter() {
7077
7135
  if (resumeSessionId === null) {
7078
7136
  return { file: input.location.file, args: [...input.location.prefixArgs], session: { kind: "discover" } };
7079
7137
  }
7080
- if (!isUuid(resumeSessionId)) throw new Error("Id de sesion de Codex invalido.");
7138
+ if (!isUuid(resumeSessionId)) throw new Error("Invalid Codex session id.");
7081
7139
  return {
7082
7140
  file: input.location.file,
7083
7141
  args: [...input.location.prefixArgs, "resume", resumeSessionId],
@@ -7286,7 +7344,7 @@ var DbChangeSignal = class {
7286
7344
  try {
7287
7345
  listener();
7288
7346
  } catch (error) {
7289
- console.warn("[opencode] un aviso de cambio de la base fallo:", error);
7347
+ console.warn("[opencode] a database change listener failed:", error);
7290
7348
  }
7291
7349
  }
7292
7350
  }
@@ -7840,7 +7898,7 @@ var OpenCodeSessionFollower = class {
7840
7898
  }
7841
7899
  get label() {
7842
7900
  const base = this.dbFile ?? "opencode";
7843
- return `${base}#${this.sessionId.length > 0 ? this.sessionId : "(sin sesion)"}`;
7901
+ return `${base}#${this.sessionId.length > 0 ? this.sessionId : "(no session)"}`;
7844
7902
  }
7845
7903
  /** Precarga el catalogo, para que la primera lectura no espere 93 ms de JSON. Nunca lanza. */
7846
7904
  async start() {
@@ -7955,7 +8013,7 @@ var OpenCodeSessionFollower = class {
7955
8013
  this.delivered = next;
7956
8014
  const diff = diffEvents(previous, next);
7957
8015
  if (diff === null) {
7958
- debugLog("conversacion", `opencode: evento fuera de orden o desaparecido en ${this.sessionId}, rehago`);
8016
+ debugLog("conversation", `opencode: out-of-order or vanished event in ${this.sessionId}, rebuilding`);
7959
8017
  return { ...emptyResult(), reset: true };
7960
8018
  }
7961
8019
  return { reset: false, added: diff.added, turns: diff.turns, plans: [], parts: diff.parts, usageChanged };
@@ -8120,7 +8178,7 @@ function createOpenCodeHistory(deps) {
8120
8178
  const readable = () => db.status() === "ok" || db.status() === "error";
8121
8179
  const sessionRow = (ref) => {
8122
8180
  const row = db.get(OPENCODE_SQL.sessionById, ref);
8123
- if (db.status() !== "ok") throw new Error(`la base de OpenCode no se pudo leer (${db.status()})`);
8181
+ if (db.status() !== "ok") throw new Error(`the OpenCode database couldn't be read (${db.status()})`);
8124
8182
  return row;
8125
8183
  };
8126
8184
  const unreadable = (ref, error) => {
@@ -8132,12 +8190,12 @@ function createOpenCodeHistory(deps) {
8132
8190
  if (row === void 0 || row.parent_id !== null) return null;
8133
8191
  const defaultTitle = DEFAULT_TITLE_PATTERN.test(row.title);
8134
8192
  const hasMessages = db.get(OPENCODE_SQL.sessionHasMessages, row.id) !== void 0;
8135
- if (db.status() !== "ok") throw new Error(`la base de OpenCode no se pudo leer (${db.status()})`);
8193
+ if (db.status() !== "ok") throw new Error(`the OpenCode database couldn't be read (${db.status()})`);
8136
8194
  if (!hasMessages) {
8137
8195
  if (defaultTitle) return null;
8138
8196
  if (!warnedWithoutMessages) {
8139
8197
  warnedWithoutMessages = true;
8140
- warn("[opencode] hay sesiones sin filas en la tabla message: su conversacion puede verse vacia. Si pasa con una sesion con mensajes, OpenCode cambio donde los guarda.");
8198
+ warn("[opencode] some sessions have no rows in the message table: their conversation may look empty. If it happens with a session that has messages, OpenCode changed where it stores them.");
8141
8199
  }
8142
8200
  }
8143
8201
  let title;
@@ -8192,7 +8250,7 @@ function createOpenCodeHistory(deps) {
8192
8250
  stamps = new Map(rows.map((row) => [row.id, row.time_updated]));
8193
8251
  return rows.map(itemOf2);
8194
8252
  } catch (error) {
8195
- warn(`[opencode] no se pudo listar el historial: ${error instanceof Error ? error.message : String(error)}`);
8253
+ warn(`[opencode] couldn't list the history: ${error instanceof Error ? error.message : String(error)}`);
8196
8254
  return null;
8197
8255
  }
8198
8256
  },
@@ -8510,11 +8568,11 @@ function loopbackBase(url) {
8510
8568
  try {
8511
8569
  parsed = new URL(url);
8512
8570
  } catch {
8513
- throw new Error("La URL del servidor de OpenCode no es valida.");
8571
+ throw new Error("The OpenCode server URL isn't valid.");
8514
8572
  }
8515
8573
  const bare = parsed.pathname === "/" && parsed.search === "" && parsed.hash === "" && parsed.username === "" && parsed.password === "";
8516
8574
  if (parsed.protocol !== "http:" || parsed.hostname !== "127.0.0.1" || parsed.port === "" || !bare) {
8517
- throw new Error("El servidor de OpenCode tiene que escuchar en http://127.0.0.1.");
8575
+ throw new Error("The OpenCode server has to listen on http://127.0.0.1.");
8518
8576
  }
8519
8577
  return `http://127.0.0.1:${parsed.port}`;
8520
8578
  }
@@ -8539,7 +8597,7 @@ var OpenCodeServeClient = class {
8539
8597
  const body = asRecord4(await this.request("POST", "/session", directory, {}));
8540
8598
  const id = body?.["id"];
8541
8599
  if (typeof id !== "string" || !OPENCODE_SESSION_ID_PATTERN.test(id)) {
8542
- throw new ServeRequestError("El servidor de OpenCode devolvio un id de sesion con una forma inesperada.", null);
8600
+ throw new ServeRequestError("The OpenCode server returned a session id with an unexpected shape.", null);
8543
8601
  }
8544
8602
  return id;
8545
8603
  }
@@ -8577,7 +8635,7 @@ var OpenCodeServeClient = class {
8577
8635
  }
8578
8636
  /** Corta lo que la sesion este haciendo (D13). */
8579
8637
  async abort(directory, sessionId) {
8580
- if (!OPENCODE_SESSION_ID_PATTERN.test(sessionId)) throw new Error("Id de sesion de OpenCode invalido.");
8638
+ if (!OPENCODE_SESSION_ID_PATTERN.test(sessionId)) throw new Error("Invalid OpenCode session id.");
8581
8639
  await this.request("POST", `/session/${sessionId}/abort`, directory, {});
8582
8640
  }
8583
8641
  /**
@@ -8609,7 +8667,7 @@ var OpenCodeServeClient = class {
8609
8667
  });
8610
8668
  this.checkResponse(response, "/global/event");
8611
8669
  const body = response.body;
8612
- if (body === null) throw new ServeRequestError("El flujo de eventos llego sin cuerpo.", response.status);
8670
+ if (body === null) throw new ServeRequestError("The event stream arrived without a body.", response.status);
8613
8671
  failures = 0;
8614
8672
  try {
8615
8673
  onReconnect();
@@ -8633,7 +8691,7 @@ var OpenCodeServeClient = class {
8633
8691
  }
8634
8692
  } catch (error) {
8635
8693
  if (closed) return;
8636
- debugLog("opencode-serve", `flujo de eventos cortado: ${error instanceof Error ? error.name : "error"}`);
8694
+ debugLog("opencode-serve", `event stream cut: ${error instanceof Error ? error.name : "error"}`);
8637
8695
  }
8638
8696
  if (closed) return;
8639
8697
  const delays = this.reconnectDelaysMs;
@@ -8653,13 +8711,13 @@ var OpenCodeServeClient = class {
8653
8711
  };
8654
8712
  }
8655
8713
  checkResponse(response, route) {
8656
- if (response.status === 401) throw new ServeAuthError("El servidor de OpenCode rechazo la contrasena.");
8714
+ if (response.status === 401) throw new ServeAuthError("The OpenCode server rejected the password.");
8657
8715
  const type = response.headers.get("content-type") ?? "";
8658
8716
  if (type.includes("text/html")) {
8659
- throw new ServeRouteError(`Esta version de OpenCode no tiene la ruta ${route}.`);
8717
+ throw new ServeRouteError(`This OpenCode version doesn't have the route ${route}.`);
8660
8718
  }
8661
8719
  if (!response.ok) {
8662
- throw new ServeRequestError(`El servidor de OpenCode respondio ${response.status} en ${route}.`, response.status);
8720
+ throw new ServeRequestError(`The OpenCode server answered ${response.status} on ${route}.`, response.status);
8663
8721
  }
8664
8722
  }
8665
8723
  async request(method, route, directory, body) {
@@ -8684,7 +8742,7 @@ var OpenCodeServeClient = class {
8684
8742
  try {
8685
8743
  return JSON.parse(text);
8686
8744
  } catch {
8687
- throw new ServeRequestError(`El servidor de OpenCode respondio algo que no es JSON en ${route}.`, response.status);
8745
+ throw new ServeRequestError(`The OpenCode server answered something that isn't JSON on ${route}.`, response.status);
8688
8746
  }
8689
8747
  }
8690
8748
  };
@@ -8722,21 +8780,21 @@ function reason(error) {
8722
8780
  function launchWithServe(deps, input) {
8723
8781
  const { resumeSessionId } = input;
8724
8782
  if (resumeSessionId !== null && !OPENCODE_SESSION_ID_PATTERN.test(resumeSessionId)) {
8725
- throw new Error("Id de sesion de OpenCode invalido.");
8783
+ throw new Error("Invalid OpenCode session id.");
8726
8784
  }
8727
8785
  return (async () => {
8728
8786
  let endpoint;
8729
8787
  try {
8730
8788
  endpoint = await deps.ensure();
8731
8789
  } catch (error) {
8732
- throw new Error(`No se pudo arrancar el servidor de OpenCode: ${reason(error)}`);
8790
+ throw new Error(`Couldn't start the OpenCode server: ${reason(error)}`);
8733
8791
  }
8734
8792
  let sessionId = resumeSessionId;
8735
8793
  if (sessionId === null) {
8736
8794
  try {
8737
8795
  sessionId = await deps.createSession(endpoint, input.cwd);
8738
8796
  } catch (error) {
8739
- throw new Error(`El servidor de OpenCode no creo la sesion: ${reason(error)}`);
8797
+ throw new Error(`The OpenCode server didn't create the session: ${reason(error)}`);
8740
8798
  }
8741
8799
  }
8742
8800
  deps.track(endpoint, sessionId, input.cwd);
@@ -8828,7 +8886,7 @@ var OpenCodeServeProcess = class {
8828
8886
  * apaga igual al cumplirse.
8829
8887
  */
8830
8888
  ensure() {
8831
- if (this.disposed) return Promise.reject(new ServeStartError("la app se esta apagando"));
8889
+ if (this.disposed) return Promise.reject(new ServeStartError("the app is shutting down"));
8832
8890
  const endpoint = this.running?.endpoint ?? null;
8833
8891
  if (endpoint !== null) {
8834
8892
  this.scheduleIdleStop();
@@ -8878,11 +8936,11 @@ var OpenCodeServeProcess = class {
8878
8936
  }
8879
8937
  async start() {
8880
8938
  await mkdir4(this.cwd, { recursive: true });
8881
- if (this.disposed) throw new ServeStartError("la app se esta apagando");
8939
+ if (this.disposed) throw new ServeStartError("the app is shutting down");
8882
8940
  const env = this.environment();
8883
8941
  const username = env["OPENCODE_SERVER_USERNAME"] || OPENCODE_SERVE_DEFAULT_USERNAME;
8884
8942
  const args = serveArgs(this.options.location);
8885
- debugLog("opencode-serve", "lanzando el serve");
8943
+ debugLog("opencode-serve", "launching serve");
8886
8944
  let child;
8887
8945
  try {
8888
8946
  child = this.spawn(this.options.location.file, args, {
@@ -8929,7 +8987,7 @@ var OpenCodeServeProcess = class {
8929
8987
  const timer = this.timers.setTimeout(
8930
8988
  () => {
8931
8989
  const ms = this.startTimeoutMs;
8932
- fail(`no dijo en que puerto escucha en ${ms >= 1e3 ? `${Math.round(ms / 1e3)} s` : `${ms} ms`}`);
8990
+ fail(`didn't say which port it listens on within ${ms >= 1e3 ? `${Math.round(ms / 1e3)} s` : `${ms} ms`}`);
8933
8991
  },
8934
8992
  this.startTimeoutMs
8935
8993
  );
@@ -8940,13 +8998,13 @@ var OpenCodeServeProcess = class {
8940
8998
  settled = true;
8941
8999
  this.timers.clearTimeout(timer);
8942
9000
  running.endpoint = { url, username, password: this.password };
8943
- debugLog("opencode-serve", `escuchando en ${url}`);
9001
+ debugLog("opencode-serve", `listening on ${url}`);
8944
9002
  this.scheduleIdleStop();
8945
9003
  resolve(running.endpoint);
8946
9004
  return;
8947
9005
  }
8948
9006
  const other = matchListeningLine(line);
8949
- if (other !== null) fail(`escucha en ${other.scheme}://${other.host} y no en http://${OPENCODE_SERVE_HOST}`);
9007
+ if (other !== null) fail(`listens on ${other.scheme}://${other.host} and not on http://${OPENCODE_SERVE_HOST}`);
8950
9008
  };
8951
9009
  const read = (stream) => {
8952
9010
  if (stream === null) return;
@@ -8964,16 +9022,16 @@ var OpenCodeServeProcess = class {
8964
9022
  child.once("error", (error) => fail(error.message));
8965
9023
  child.once("close", (code, signal) => {
8966
9024
  if (this.running === running) this.running = null;
8967
- const how = signal ?? `codigo ${code ?? "?"}`;
9025
+ const how = signal ?? `code ${code ?? "?"}`;
8968
9026
  if (!settled) {
8969
9027
  const tail = running.tail.trim();
8970
- fail(`salio antes de escuchar (${how})${tail.length > 0 ? `: ${tail}` : ""}`);
9028
+ fail(`exited before listening (${how})${tail.length > 0 ? `: ${tail}` : ""}`);
8971
9029
  return;
8972
9030
  }
8973
9031
  const endpoint = running.endpoint;
8974
9032
  if (endpoint === null) return;
8975
9033
  const exit = { requested: running.stopRequested };
8976
- debugLog("opencode-serve", `termino (${how}${exit.requested ? "" : ", sin que la app lo pidiera"})`);
9034
+ debugLog("opencode-serve", `exited (${how}${exit.requested ? "" : ", without the app asking"})`);
8977
9035
  if (this.running === null) this.cancelIdleStop();
8978
9036
  for (const listener of [...this.exitListeners]) {
8979
9037
  try {
@@ -9004,7 +9062,7 @@ var OpenCodeServeProcess = class {
9004
9062
  this.idleTimer = this.timers.setTimeout(() => {
9005
9063
  this.idleTimer = null;
9006
9064
  if (this.retained > 0 || this.disposed) return;
9007
- debugLog("opencode-serve", "sin pestanas de OpenCode: se apaga");
9065
+ debugLog("opencode-serve", "no OpenCode tabs: shutting down");
9008
9066
  this.stop();
9009
9067
  }, this.idleStopMs);
9010
9068
  }
@@ -9057,6 +9115,7 @@ var PASTE_START = "\x1B[200~";
9057
9115
  var PASTE_END = "\x1B[201~";
9058
9116
  var SHIFT_TAB = "\x1B[Z";
9059
9117
  var SUBMIT = "\r";
9118
+ var END_KEY = "\x1B[F";
9060
9119
  var INTERRUPT = "\x1B";
9061
9120
  var MAX_SUBMIT_CHARS = 2e5;
9062
9121
  function sanitizeForPaste(text) {
@@ -9115,53 +9174,47 @@ function buildModeKeys(from, to, cycle = PERMISSION_MODE_CYCLE) {
9115
9174
  if (distance === null) return null;
9116
9175
  return Array.from({ length: distance }, () => SHIFT_TAB);
9117
9176
  }
9118
- var MODE_PENDING_CONFIRMATION_MESSAGE = "Hay una confirmacion pendiente en la CLI: cambiar de modo ahora la aprobaria. Contestala en la solapa CLI.";
9119
- function pendingSubmitMessage(label, waitingFor) {
9120
- return waitingFor === "permission prompt" ? `${label} esta esperando que autorices una herramienta: el Enter del mensaje la aprobaria. Contestala en la solapa CLI.` : `${label} esta esperando una respuesta: el Enter del mensaje la contestaria. Contestala en la solapa CLI.`;
9177
+ var MODE_PENDING_CONFIRMATION_TEXT = serverText("modePendingConfirmation");
9178
+ function pendingSubmitText(label, waitingFor) {
9179
+ return waitingFor === "permission prompt" ? serverText("pendingSubmitPermission", { label }) : serverText("pendingSubmitAnswer", { label });
9121
9180
  }
9122
- var ANSWER_NOT_PENDING_MESSAGE = "Esa pregunta ya no esta esperando respuesta.";
9123
- var ANSWER_INVALID_MESSAGE = "La respuesta no corresponde a la pregunta.";
9124
- var ANSWER_NO_FREE_TEXT_MESSAGE = "Esta pregunta no acepta respuesta escrita: elegi una de las opciones.";
9125
- function answerFailureMessage(outcome) {
9181
+ var ANSWER_NOT_PENDING_TEXT = serverText("answerNotPending");
9182
+ var ANSWER_INVALID_TEXT = serverText("answerInvalid");
9183
+ var ANSWER_NO_FREE_TEXT_TEXT = serverText("answerNoFreeText");
9184
+ function answerFailureText(outcome) {
9126
9185
  switch (outcome) {
9127
9186
  case "answered":
9128
9187
  return null;
9129
9188
  case "not-pending":
9130
- return ANSWER_NOT_PENDING_MESSAGE;
9189
+ return ANSWER_NOT_PENDING_TEXT;
9131
9190
  case "invalid":
9132
- return ANSWER_INVALID_MESSAGE;
9191
+ return ANSWER_INVALID_TEXT;
9133
9192
  case "no-free-text":
9134
- return ANSWER_NO_FREE_TEXT_MESSAGE;
9193
+ return ANSWER_NO_FREE_TEXT_TEXT;
9135
9194
  }
9136
9195
  }
9137
- function waitingSubmitMessage(label) {
9138
- return `${label} esta esperando una respuesta: contestala antes de mandar otro mensaje.`;
9196
+ function waitingSubmitText(label) {
9197
+ return serverText("waitingSubmit", { label });
9139
9198
  }
9140
9199
  function submitRefusal(input) {
9141
9200
  if (input.approvesPendingOnCycle && input.waitingFor !== null) {
9142
- return pendingSubmitMessage(input.label, input.waitingFor);
9201
+ return pendingSubmitText(input.label, input.waitingFor);
9143
9202
  }
9144
9203
  if (input.waitingBlocksSubmit === true && input.activity === "waiting") {
9145
- return waitingSubmitMessage(input.label);
9146
- }
9147
- if (input.blind && input.openToolCall) {
9148
- return `${input.label} tiene una herramienta sin resultado: puede estar pidiendo una aprobacion. Contestala en la solapa CLI.`;
9204
+ return waitingSubmitText(input.label);
9149
9205
  }
9206
+ if (input.blind && input.openToolCall) return serverText("openToolSubmit", { label: input.label });
9150
9207
  return null;
9151
9208
  }
9152
9209
  function planModeChange(input) {
9153
9210
  const { cycle, current, target, waitingFor } = input;
9154
9211
  if (current === target) return { kind: "none" };
9155
9212
  if (cycle.approvesPendingOnCycle && waitingFor !== null) {
9156
- return { kind: "refused", reason: "pending-confirmation", message: MODE_PENDING_CONFIRMATION_MESSAGE };
9213
+ return { kind: "refused", reason: "pending-confirmation", text: MODE_PENDING_CONFIRMATION_TEXT };
9157
9214
  }
9158
9215
  const keys = buildModeKeys(current, target, cycle.modes);
9159
9216
  if (keys === null || keys.length === 0) {
9160
- return {
9161
- kind: "refused",
9162
- reason: "unreachable",
9163
- message: `No se puede llegar a ese modo desde el actual. Cambialo con ${cycle.keyLabel} en la solapa CLI.`
9164
- };
9217
+ return { kind: "refused", reason: "unreachable", text: serverText("modeUnreachable", { key: cycle.keyLabel }) };
9165
9218
  }
9166
9219
  return { kind: "keys", keys };
9167
9220
  }
@@ -9183,6 +9236,7 @@ function buildSubmissionWrites(text, imagePaths, shape, options = {}) {
9183
9236
  const send = options.send ?? true;
9184
9237
  const style = shape.imageReference;
9185
9238
  if (imagePaths.length > 0 && style === null) return null;
9239
+ const submit = shape.endBeforeSubmit === true ? `${END_KEY}${SUBMIT}` : SUBMIT;
9186
9240
  if (style !== "bare-path-paste") {
9187
9241
  const payload = joinedPayload(
9188
9242
  text,
@@ -9190,9 +9244,9 @@ function buildSubmissionWrites(text, imagePaths, shape, options = {}) {
9190
9244
  );
9191
9245
  if (payload === null) return null;
9192
9246
  if (shape.enterSeparately === true) {
9193
- return send ? [pasted(payload, shape.pasteMarkers), SUBMIT] : [pasted(payload, shape.pasteMarkers)];
9247
+ return send ? [pasted(payload, shape.pasteMarkers), submit] : [pasted(payload, shape.pasteMarkers)];
9194
9248
  }
9195
- return [`${pasted(payload, shape.pasteMarkers)}${send ? SUBMIT : ""}`];
9249
+ return [`${pasted(payload, shape.pasteMarkers)}${send ? submit : ""}`];
9196
9250
  }
9197
9251
  const pieces = [];
9198
9252
  for (const imagePath of imagePaths) {
@@ -9202,9 +9256,13 @@ function buildSubmissionWrites(text, imagePaths, shape, options = {}) {
9202
9256
  const clean = sanitizeForPaste(text).trim().slice(0, MAX_SUBMIT_CHARS);
9203
9257
  if (clean.length > 0) pieces.push(pasted(clean, shape.pasteMarkers));
9204
9258
  if (pieces.length === 0) return null;
9205
- if (send) pieces.push(SUBMIT);
9259
+ if (send) pieces.push(submit);
9206
9260
  return pieces;
9207
9261
  }
9262
+ function submitStartDelayMs(launchedAt, now, readyAfterLaunchMs) {
9263
+ if (launchedAt === null || readyAfterLaunchMs <= 0) return 0;
9264
+ return Math.min(readyAfterLaunchMs, Math.max(0, launchedAt + readyAfterLaunchMs - now));
9265
+ }
9208
9266
 
9209
9267
  // packages/server/src/agents/opencode/serve-questions.ts
9210
9268
  var OPENCODE_FREE_ANSWER_MAX_CHARS = 2e3;
@@ -9535,7 +9593,7 @@ var OpenCodeServeStatus = class {
9535
9593
  }
9536
9594
  ok = true;
9537
9595
  } catch (error) {
9538
- debugLog("opencode-serve", `la foto del estado fallo: ${error instanceof Error ? error.name : "error"}`);
9596
+ debugLog("opencode-serve", `the status snapshot failed: ${error instanceof Error ? error.name : "error"}`);
9539
9597
  } finally {
9540
9598
  if (this.snapshots.get(directory) === current) this.snapshots.delete(directory);
9541
9599
  }
@@ -9769,11 +9827,11 @@ function createOpenCodeAdapter(options = {}) {
9769
9827
  * existe hasta la primera consulta, asi que se mira el archivo.
9770
9828
  */
9771
9829
  startupHistoryNote(cliAvailable) {
9772
- if (env["OPENCODE_DB"] === ":memory:") return "en memoria (no hay nada que leer)";
9830
+ if (env["OPENCODE_DB"] === ":memory:") return "in memory (nothing to read)";
9773
9831
  if (paths.dbFile === null) return null;
9774
9832
  const present = existsSync2(paths.dbFile);
9775
- if ("unavailable" in sqlite()) return present || cliAvailable ? `no se lee: ${sqliteUnavailableText(OPENCODE_LABEL)}` : null;
9776
- return present ? `${paths.dbFile} (solo lectura)` : null;
9833
+ if ("unavailable" in sqlite()) return present || cliAvailable ? `not read: ${sqliteUnavailableText(OPENCODE_LABEL)}` : null;
9834
+ return present ? `${paths.dbFile} (read-only)` : null;
9777
9835
  },
9778
9836
  // `opencode.json` no se abre (regla 2.1).
9779
9837
  defaults: () => Promise.resolve(null),
@@ -9808,7 +9866,7 @@ var AgentRegistry = class {
9808
9866
  constructor(adapters) {
9809
9867
  for (const adapter of adapters) {
9810
9868
  if (this.agents.has(adapter.id)) {
9811
- throw new Error(`Adaptador registrado dos veces: ${adapter.id}`);
9869
+ throw new Error(`Adapter registered twice: ${adapter.id}`);
9812
9870
  }
9813
9871
  this.agents.set(adapter.id, { adapter, location: null });
9814
9872
  }
@@ -9842,7 +9900,7 @@ var AgentRegistry = class {
9842
9900
  try {
9843
9901
  await adapter.prepare?.();
9844
9902
  } catch (error) {
9845
- console.warn(`[agentes] ${adapter.label} no pudo preparar lo suyo: ${String(error)}`);
9903
+ console.warn(`[agents] ${adapter.label} couldn't prepare its files: ${String(error)}`);
9846
9904
  }
9847
9905
  })
9848
9906
  );
@@ -9887,7 +9945,7 @@ var AgentRegistry = class {
9887
9945
  /** El adaptador aunque su CLI no este instalada: el historial se lee igual. */
9888
9946
  adapter(id) {
9889
9947
  const entry = this.agents.get(id);
9890
- if (entry === void 0) throw new Error(`No hay adaptador para ${id}`);
9948
+ if (entry === void 0) throw new Error(`No adapter for ${id}`);
9891
9949
  return entry.adapter;
9892
9950
  }
9893
9951
  all() {
@@ -9960,7 +10018,7 @@ var AgentRegistry = class {
9960
10018
  const result = adapter.dispose();
9961
10019
  if (result instanceof Promise) pending.push(result);
9962
10020
  } catch (error) {
9963
- console.warn(`[agentes] ${adapter.label} no se pudo soltar: ${String(error)}`);
10021
+ console.warn(`[agents] ${adapter.label} couldn't be released: ${String(error)}`);
9964
10022
  }
9965
10023
  }
9966
10024
  return Promise.allSettled(pending).then(() => void 0);
@@ -10053,8 +10111,8 @@ var ConversationHub = class extends EventEmitter {
10053
10111
  this.startFollowTicker(terminalId, entry);
10054
10112
  await entry.follower.start();
10055
10113
  debugLog(
10056
- "conversacion",
10057
- `sigo ${path28.basename(entry.follower.label)} de ${terminalId.slice(0, 8)}`
10114
+ "conversation",
10115
+ `following ${path28.basename(entry.follower.label)} for ${terminalId.slice(0, 8)}`
10058
10116
  );
10059
10117
  entry = this.entries.get(terminalId) ?? entry;
10060
10118
  }
@@ -10199,8 +10257,8 @@ var ConversationHub = class extends EventEmitter {
10199
10257
  this.watchStatus(terminalId, next, descriptor.sessionId);
10200
10258
  this.startFollowTicker(terminalId, next);
10201
10259
  debugLog(
10202
- "conversacion",
10203
- `${terminalId.slice(0, 8)} cambio de sesion: sigo ${path28.basename(next.follower.label)}`
10260
+ "conversation",
10261
+ `${terminalId.slice(0, 8)} changed session: following ${path28.basename(next.follower.label)}`
10204
10262
  );
10205
10263
  await next.follower.start();
10206
10264
  next.primed = true;
@@ -10291,7 +10349,7 @@ var ConversationHub = class extends EventEmitter {
10291
10349
  entry.refs -= 1;
10292
10350
  if (entry.refs > 0) return;
10293
10351
  this.release(terminalId, entry);
10294
- debugLog("conversacion", `dejo de seguir ${terminalId.slice(0, 8)}`);
10352
+ debugLog("conversation", `stopped following ${terminalId.slice(0, 8)}`);
10295
10353
  }
10296
10354
  /** La pestana se cerro: se suelta sin importar cuantos la miraban. */
10297
10355
  drop(terminalId) {
@@ -10495,7 +10553,7 @@ var ConversationHub = class extends EventEmitter {
10495
10553
  try {
10496
10554
  result = await entry.follower.poll();
10497
10555
  } catch (error) {
10498
- console.warn(`[conversacion] no se pudo leer ${entry.follower.label}:`, error);
10556
+ console.warn(`[conversation] couldn't read ${entry.follower.label}:`, error);
10499
10557
  return;
10500
10558
  }
10501
10559
  this.refreshOpenToolCall(terminalId, entry);
@@ -10559,6 +10617,54 @@ function openBrowser(url) {
10559
10617
  }
10560
10618
  }
10561
10619
 
10620
+ // packages/server/src/pty-helper.ts
10621
+ import { chmodSync, statSync } from "node:fs";
10622
+ import { createRequire } from "node:module";
10623
+ import path29 from "node:path";
10624
+ function helperCandidates(packageDir, platform, arch) {
10625
+ return [
10626
+ path29.join(packageDir, "build", "Release", "spawn-helper"),
10627
+ path29.join(packageDir, "build", "Debug", "spawn-helper"),
10628
+ path29.join(packageDir, "prebuilds", `${platform}-${arch}`, "spawn-helper")
10629
+ ];
10630
+ }
10631
+ function locateNodePty() {
10632
+ try {
10633
+ const require2 = createRequire(import.meta.url);
10634
+ return path29.resolve(path29.dirname(require2.resolve("node-pty")), "..");
10635
+ } catch {
10636
+ return null;
10637
+ }
10638
+ }
10639
+ function ensurePtyHelperExecutable(options = {}) {
10640
+ const platform = options.platform ?? process.platform;
10641
+ if (platform === "win32") return [];
10642
+ const packageDir = options.packageDir === void 0 ? locateNodePty() : options.packageDir;
10643
+ if (packageDir === null) return [];
10644
+ const warn = options.warn ?? ((message) => console.warn(message));
10645
+ const fixed = [];
10646
+ for (const file of helperCandidates(packageDir, platform, options.arch ?? process.arch)) {
10647
+ let mode;
10648
+ try {
10649
+ const stats = statSync(file);
10650
+ if (!stats.isFile()) continue;
10651
+ mode = stats.mode;
10652
+ } catch {
10653
+ continue;
10654
+ }
10655
+ if ((mode & 73) === 73) continue;
10656
+ try {
10657
+ chmodSync(file, mode & 511 | 493);
10658
+ fixed.push(file);
10659
+ } catch {
10660
+ warn(
10661
+ `[pty] ${file} is not executable and couldn't be fixed: no tab will be able to start a CLI. Run: chmod +x "${file}"`
10662
+ );
10663
+ }
10664
+ }
10665
+ return fixed;
10666
+ }
10667
+
10562
10668
  // packages/server/src/security.ts
10563
10669
  import { randomBytes as randomBytes2, timingSafeEqual } from "node:crypto";
10564
10670
  var LOOPBACK_HOST = "127.0.0.1";
@@ -10651,7 +10757,10 @@ import chokidar from "chokidar";
10651
10757
  // packages/server/src/git-repo.ts
10652
10758
  import { execFile as execFile3 } from "node:child_process";
10653
10759
  import { readFile as readFile9 } from "node:fs/promises";
10654
- import path29 from "node:path";
10760
+ import path30 from "node:path";
10761
+ function gitOutputText(output, command) {
10762
+ return output.length > 0 ? serverText("raw", { text: output }) : serverText("gitCommandFailed", { command });
10763
+ }
10655
10764
  var MAX_CHANGES = 2e3;
10656
10765
  var MAX_DIFF_LINES = 4e3;
10657
10766
  var MAX_DIFF_BYTES = 512 * 1024;
@@ -10662,7 +10771,7 @@ function isMissingBinary(error) {
10662
10771
  }
10663
10772
  var GitMissingError = class extends Error {
10664
10773
  constructor() {
10665
- super("No se encontro git en el PATH.");
10774
+ super("git wasn't found in the PATH.");
10666
10775
  this.name = "GitMissingError";
10667
10776
  }
10668
10777
  };
@@ -10709,7 +10818,7 @@ async function resolveRepo(cwd) {
10709
10818
  const result = await runGit(cwd, ["rev-parse", "--show-toplevel"]);
10710
10819
  if (result.code === 0) {
10711
10820
  const root = result.stdout.trim();
10712
- if (root.length > 0) return { state: "ready", root: path29.resolve(root), message: null };
10821
+ if (root.length > 0) return { state: "ready", root: path30.resolve(root), message: null };
10713
10822
  }
10714
10823
  const stderr = result.stderr.trim();
10715
10824
  if (/not a git repository|no es un repositorio/i.test(stderr)) {
@@ -10719,13 +10828,13 @@ async function resolveRepo(cwd) {
10719
10828
  return {
10720
10829
  state: "error",
10721
10830
  root: "",
10722
- message: "No se pudo ejecutar git para leer el estado del repositorio."
10831
+ message: serverText("gitRunFailed")
10723
10832
  };
10724
10833
  }
10725
10834
  return {
10726
10835
  state: "error",
10727
10836
  root: "",
10728
- message: stderr.slice(0, 400) || "git rev-parse fallo."
10837
+ message: gitOutputText(stderr.slice(0, 400), "rev-parse")
10729
10838
  };
10730
10839
  }
10731
10840
  async function findRepoRoot(cwd) {
@@ -10736,7 +10845,7 @@ async function findGitDir(cwd) {
10736
10845
  const result = await runGit(cwd, ["rev-parse", "--absolute-git-dir"]);
10737
10846
  if (result.code !== 0) return null;
10738
10847
  const dir = result.stdout.trim();
10739
- return dir.length > 0 ? path29.resolve(dir) : null;
10848
+ return dir.length > 0 ? path30.resolve(dir) : null;
10740
10849
  }
10741
10850
  function letterToKind(letter) {
10742
10851
  switch (letter) {
@@ -10839,7 +10948,7 @@ function parseWorktrees(raw, currentRoot) {
10839
10948
  worktrees.push({
10840
10949
  path: current.path,
10841
10950
  branch: current.branch,
10842
- isCurrent: path29.resolve(current.path) === path29.resolve(currentRoot)
10951
+ isCurrent: path30.resolve(current.path) === path30.resolve(currentRoot)
10843
10952
  });
10844
10953
  current = null;
10845
10954
  };
@@ -10881,13 +10990,13 @@ async function readStatus2(cwd) {
10881
10990
  return {
10882
10991
  ...base,
10883
10992
  state: "git-missing",
10884
- message: "No se encontro git en el PATH. El panel de cambios necesita git instalado; el resto de la app funciona igual."
10993
+ message: serverText("gitMissing")
10885
10994
  };
10886
10995
  }
10887
10996
  throw error;
10888
10997
  }
10889
10998
  if (lookup.state === "not-a-repo") {
10890
- return { ...base, message: "Esta carpeta no esta dentro de un repositorio git." };
10999
+ return { ...base, message: serverText("gitNotRepo") };
10891
11000
  }
10892
11001
  if (lookup.state === "error") {
10893
11002
  return { ...base, state: "error", message: lookup.message };
@@ -10902,7 +11011,7 @@ async function readStatus2(cwd) {
10902
11011
  ...base,
10903
11012
  state: "error",
10904
11013
  repoRoot: repoRoot2,
10905
- message: statusResult.stderr.trim().slice(0, 400) || "git status fallo."
11014
+ message: gitOutputText(statusResult.stderr.trim().slice(0, 400), "status")
10906
11015
  };
10907
11016
  }
10908
11017
  const parsed = parsePorcelainV2(statusResult.stdout);
@@ -10990,7 +11099,7 @@ async function readUntrackedAsDiff(absolutePath, relativePath) {
10990
11099
  lines: [],
10991
11100
  truncated: false,
10992
11101
  binary: false,
10993
- message: "No se pudo leer el archivo."
11102
+ message: serverText("fileReadFailed")
10994
11103
  };
10995
11104
  }
10996
11105
  if (looksBinary(content)) {
@@ -11000,7 +11109,7 @@ async function readUntrackedAsDiff(absolutePath, relativePath) {
11000
11109
  lines: [],
11001
11110
  truncated: false,
11002
11111
  binary: true,
11003
- message: "Archivo binario sin seguimiento."
11112
+ message: serverText("diffBinaryUntracked")
11004
11113
  };
11005
11114
  }
11006
11115
  const truncatedByBytes = content.length > MAX_DIFF_BYTES;
@@ -11024,13 +11133,13 @@ async function readUntrackedAsDiff(absolutePath, relativePath) {
11024
11133
  lines,
11025
11134
  truncated,
11026
11135
  binary: false,
11027
- message: lines.length === 0 ? "Archivo nuevo y vacio." : null
11136
+ message: lines.length === 0 ? serverText("diffNewEmpty") : null
11028
11137
  };
11029
11138
  }
11030
11139
  async function readDiff(options) {
11031
11140
  const { repoRoot: repoRoot2, relativePath, staged, untracked } = options;
11032
11141
  if (untracked && !staged) {
11033
- return readUntrackedAsDiff(path29.join(repoRoot2, relativePath), relativePath);
11142
+ return readUntrackedAsDiff(path30.join(repoRoot2, relativePath), relativePath);
11034
11143
  }
11035
11144
  const args = [
11036
11145
  "diff",
@@ -11051,7 +11160,7 @@ async function readDiff(options) {
11051
11160
  lines: [],
11052
11161
  truncated: false,
11053
11162
  binary: false,
11054
- message: result.stderr.trim().slice(0, 300) || "git diff fallo."
11163
+ message: gitOutputText(result.stderr.trim().slice(0, 300), "diff")
11055
11164
  };
11056
11165
  }
11057
11166
  const capped = result.stdout.length > MAX_DIFF_BYTES;
@@ -11062,7 +11171,7 @@ async function readDiff(options) {
11062
11171
  lines: parsed.lines,
11063
11172
  truncated: parsed.truncated || capped,
11064
11173
  binary: parsed.binary,
11065
- message: parsed.lines.length === 0 ? staged ? "No hay cambios preparados para este archivo." : "No hay cambios sin preparar para este archivo." : null
11174
+ message: parsed.lines.length === 0 ? staged ? serverText("diffNoStaged") : serverText("diffNoUnstaged") : null
11066
11175
  };
11067
11176
  }
11068
11177
  async function filterIgnored(repoRoot2, relativePaths) {
@@ -11165,7 +11274,7 @@ var RepoHub = class extends EventEmitter2 {
11165
11274
  if (entry.pollTimer !== null) clearTimeout(entry.pollTimer);
11166
11275
  void entry.watcher?.close();
11167
11276
  this.entries.delete(terminalId);
11168
- debugLog("git", `dejo de mirar el repo de ${terminalId.slice(0, 8)}`);
11277
+ debugLog("git", `stopped watching the repo of ${terminalId.slice(0, 8)}`);
11169
11278
  }
11170
11279
  /**
11171
11280
  * Observa el `.git` del worktree.
@@ -11235,7 +11344,7 @@ var RepoHub = class extends EventEmitter2 {
11235
11344
  entry.status = status;
11236
11345
  if (!options.silent) this.emit("status", terminalId, status);
11237
11346
  } catch (error) {
11238
- console.warn(`[git] no se pudo leer el estado de ${entry.cwd}:`, error);
11347
+ console.warn(`[git] couldn't read the status of ${entry.cwd}:`, error);
11239
11348
  } finally {
11240
11349
  entry.reading = false;
11241
11350
  if (this.entries.get(terminalId) === entry) {
@@ -11250,7 +11359,7 @@ var RepoHub = class extends EventEmitter2 {
11250
11359
  // packages/server/src/memory-hub.ts
11251
11360
  import { EventEmitter as EventEmitter3 } from "node:events";
11252
11361
  import { homedir as homedir10 } from "node:os";
11253
- import path31 from "node:path";
11362
+ import path32 from "node:path";
11254
11363
  import chokidar2 from "chokidar";
11255
11364
 
11256
11365
  // packages/server/src/memory-bridge.ts
@@ -11267,12 +11376,8 @@ import {
11267
11376
  unlink as unlink3
11268
11377
  } from "node:fs/promises";
11269
11378
  import { homedir as homedir9 } from "node:os";
11270
- import path30 from "node:path";
11271
- var MemoryBridgeError = class extends Error {
11272
- constructor(message) {
11273
- super(message);
11274
- this.name = "MemoryBridgeError";
11275
- }
11379
+ import path31 from "node:path";
11380
+ var MemoryBridgeError = class extends ServerTextError {
11276
11381
  };
11277
11382
  var MAX_LISTED_NOTES = 500;
11278
11383
  var MAX_NOTE_BYTES = 256 * 1024;
@@ -11289,9 +11394,9 @@ var GITIGNORE_MEMORY_LINE = `${MEMORY_DIR}/`;
11289
11394
  var BOM2 = Buffer.from([239, 187, 191]);
11290
11395
  async function inspectMemory(cwd, home = homedir9()) {
11291
11396
  await assertMemoryDirInside(cwd);
11292
- const folder = path30.join(cwd, MEMORY_DIR);
11397
+ const folder = path31.join(cwd, MEMORY_DIR);
11293
11398
  const folderExists = await isDirectory2(folder);
11294
- const indexExists = folderExists && await isFile(path30.join(folder, MEMORY_INDEX_FILE));
11399
+ const indexExists = folderExists && await isFile(path31.join(folder, MEMORY_INDEX_FILE));
11295
11400
  const noteNames = folderExists ? await listNoteFiles(folder) : [];
11296
11401
  const notes = await describeNotes(folder, noteNames);
11297
11402
  const files = await Promise.all(
@@ -11323,8 +11428,8 @@ async function installMemory(cwd, options, home = homedir9()) {
11323
11428
  }
11324
11429
  async function importNativeMemory(cwd, home = homedir9()) {
11325
11430
  await assertMemoryDirInside(cwd);
11326
- if (!await isDirectory2(path30.join(cwd, MEMORY_DIR))) {
11327
- throw new MemoryBridgeError("Instal\xE1 el puente primero: falta la carpeta .agents/memory.");
11431
+ if (!await isDirectory2(path31.join(cwd, MEMORY_DIR))) {
11432
+ throw new MemoryBridgeError(serverText("memoryNotInstalled"));
11328
11433
  }
11329
11434
  const plan = new PlanBuilder(cwd);
11330
11435
  if (await plan.current(INDEX_REL) === null) {
@@ -11337,7 +11442,7 @@ async function importNativeMemory(cwd, home = homedir9()) {
11337
11442
  }
11338
11443
  async function readMemoryNote(cwd, name) {
11339
11444
  if (!isNoteName(name)) {
11340
- throw new InvalidPathError("Ese nombre no es una nota de la memoria.");
11445
+ throw new InvalidPathError(serverText("memoryNoteName"));
11341
11446
  }
11342
11447
  const absolute = await resolveInside(cwd, `${MEMORY_DIR}/${name}`);
11343
11448
  try {
@@ -11358,7 +11463,7 @@ function isNoteName(name) {
11358
11463
  if (name.length === 0 || name.length > 255) return false;
11359
11464
  if (name.includes("\0")) return false;
11360
11465
  if (name === "." || name === "..") return false;
11361
- if (path30.basename(name) !== name) return false;
11466
+ if (path31.basename(name) !== name) return false;
11362
11467
  if (/[\\/:*?"<>|\u0000-\u001f]/.test(name)) return false;
11363
11468
  return name.toLowerCase().endsWith(".md");
11364
11469
  }
@@ -11381,19 +11486,21 @@ var PlanBuilder = class {
11381
11486
  if (planned !== void 0) return planned.content;
11382
11487
  return readIfFile(await guard(this.cwd, rel), rel);
11383
11488
  }
11384
- put(rel, original, content, action, preview) {
11489
+ put(rel, original, content, action, preview, copiedFrom = null) {
11385
11490
  const planned = this.files.get(rel);
11386
11491
  if (planned !== void 0) {
11387
11492
  planned.content = content;
11388
- planned.preview.push(preview);
11493
+ if (preview.length > 0) planned.preview.push(preview);
11494
+ planned.copiedFrom ??= copiedFrom;
11389
11495
  return;
11390
11496
  }
11391
- this.files.set(rel, { rel, original, content, action, preview: [preview], target: null });
11497
+ const previews = preview.length > 0 ? [preview] : [];
11498
+ this.files.set(rel, { rel, original, content, action, preview: previews, copiedFrom, target: null });
11392
11499
  }
11393
11500
  /** Nombres de nota que habria en la carpeta, en minusculas. */
11394
11501
  async noteNames() {
11395
11502
  const names = /* @__PURE__ */ new Map();
11396
- for (const name of await listNoteFiles(path30.join(this.cwd, MEMORY_DIR))) {
11503
+ for (const name of await listNoteFiles(path31.join(this.cwd, MEMORY_DIR))) {
11397
11504
  names.set(name.toLowerCase(), name);
11398
11505
  }
11399
11506
  for (const rel of this.files.keys()) {
@@ -11407,7 +11514,8 @@ var PlanBuilder = class {
11407
11514
  return [...this.files.values()].map((file) => ({
11408
11515
  file: file.rel,
11409
11516
  action: file.action,
11410
- preview: file.preview.join("\n")
11517
+ preview: file.preview.join("\n"),
11518
+ copiedFrom: file.copiedFrom
11411
11519
  }));
11412
11520
  }
11413
11521
  /**
@@ -11437,16 +11545,12 @@ var PlanBuilder = class {
11437
11545
  const target = await realpath2(absolute);
11438
11546
  const info = await stat15(target);
11439
11547
  if (info.nlink > 1) {
11440
- throw new MemoryBridgeError(
11441
- `${file.rel} tiene enlaces duros: la app no lo modifica, porque escribirlo cortaria el enlace. Agreg\xE1 el bloque a mano.`
11442
- );
11548
+ throw new MemoryBridgeError(serverText("memoryHardLinks", { file: file.rel }));
11443
11549
  }
11444
11550
  const key = process.platform === "win32" ? target.toLowerCase() : target;
11445
11551
  const other = seen.get(key);
11446
11552
  if (other !== void 0) {
11447
- throw new MemoryBridgeError(
11448
- `${other} y ${file.rel} son el mismo archivo (un enlace). Eleg\xED uno solo.`
11449
- );
11553
+ throw new MemoryBridgeError(serverText("memorySameFile", { other, file: file.rel }));
11450
11554
  }
11451
11555
  seen.set(key, file.rel);
11452
11556
  file.target = target;
@@ -11458,10 +11562,10 @@ var PlanBuilder = class {
11458
11562
  );
11459
11563
  if (pending.length === 0) return;
11460
11564
  if (pending.some((file) => file.rel.startsWith(`${MEMORY_DIR}/`))) {
11461
- await mkdir5(path30.join(this.cwd, MEMORY_DIR), { recursive: true });
11565
+ await mkdir5(path31.join(this.cwd, MEMORY_DIR), { recursive: true });
11462
11566
  }
11463
11567
  for (const file of pending) {
11464
- if (file.target === null) throw new Error(`Plan sin validar: ${file.rel}`);
11568
+ if (file.target === null) throw new Error(`Unvalidated plan: ${file.rel}`);
11465
11569
  await writeAtomic(file.target, file.content, file.original === null);
11466
11570
  }
11467
11571
  }
@@ -11469,7 +11573,7 @@ var PlanBuilder = class {
11469
11573
  async function buildInstallPlan(cwd, options, home) {
11470
11574
  await assertMemoryDirInside(cwd);
11471
11575
  if (options.instructionFiles.length === 0) {
11472
- throw new MemoryBridgeError("Eleg\xED al menos un archivo de instrucciones.");
11576
+ throw new MemoryBridgeError(serverText("memoryChooseFile"));
11473
11577
  }
11474
11578
  const { state: git, main: main2 } = await readGit(cwd);
11475
11579
  const plan = new PlanBuilder(cwd);
@@ -11479,7 +11583,7 @@ async function buildInstallPlan(cwd, options, home) {
11479
11583
  const mainIndexPath = source === null || mainFolder === null ? null : await existingInside(source.root, `${source.memoryRel}/${MEMORY_INDEX_FILE}`, "file");
11480
11584
  const mainIndex = mainIndexPath === null ? null : await readIfFile(mainIndexPath, INDEX_REL);
11481
11585
  if (mainIndex !== null && mainIndexPath !== null) {
11482
- plan.put(INDEX_REL, null, mainIndex, "copy", `Copia de ${mainIndexPath}`);
11586
+ plan.put(INDEX_REL, null, mainIndex, "copy", "", mainIndexPath);
11483
11587
  } else {
11484
11588
  plan.put(
11485
11589
  INDEX_REL,
@@ -11498,7 +11602,7 @@ async function buildInstallPlan(cwd, options, home) {
11498
11602
  if (notePath === null) continue;
11499
11603
  const content = await readIfFile(notePath, name);
11500
11604
  if (content === null) continue;
11501
- plan.put(`${MEMORY_DIR}/${name}`, null, content, "copy", `Copia de ${notePath}`);
11605
+ plan.put(`${MEMORY_DIR}/${name}`, null, content, "copy", "", notePath);
11502
11606
  }
11503
11607
  }
11504
11608
  if (options.importNative) await planNativeImport(plan, cwd, home);
@@ -11515,9 +11619,7 @@ async function buildInstallPlan(cwd, options, home) {
11515
11619
  assertUtf8(name, current);
11516
11620
  const scan = scanBlock(current);
11517
11621
  if (scan.brokenBlock) {
11518
- throw new MemoryBridgeError(
11519
- `${name} tiene las marcas del bloque de memoria mal formadas (una sin su pareja, o el bloque repetido). Arreglalo a mano: la app no toca ese archivo mientras tanto.`
11520
- );
11622
+ throw new MemoryBridgeError(serverText("memoryBrokenBlock", { file: name }));
11521
11623
  }
11522
11624
  const eol = dominantEol(current);
11523
11625
  if (!scan.hasBlock) {
@@ -11533,9 +11635,7 @@ async function buildInstallPlan(cwd, options, home) {
11533
11635
  }
11534
11636
  if (options.gitMode === "ignore") {
11535
11637
  if (git.kind === "error") {
11536
- throw new MemoryBridgeError(
11537
- `No se pudo consultar git (${git.message}). Sin eso no se sabe si hay que tocar .gitignore.`
11538
- );
11638
+ throw new MemoryBridgeError(serverText("memoryGitFailed", { detail: git.message }));
11539
11639
  }
11540
11640
  if (git.kind === "repo") {
11541
11641
  const wanted = [];
@@ -11573,7 +11673,7 @@ async function planNativeImport(plan, cwd, home) {
11573
11673
  const local = await plan.noteNames();
11574
11674
  for (const name of await listNoteFiles(folder)) {
11575
11675
  const key = name.toLowerCase();
11576
- const content = await readIfFile(path30.join(folder, name), name);
11676
+ const content = await readIfFile(path31.join(folder, name), name);
11577
11677
  if (content === null) continue;
11578
11678
  const already = chosen.get(key);
11579
11679
  if (already !== void 0) {
@@ -11587,7 +11687,7 @@ async function planNativeImport(plan, cwd, home) {
11587
11687
  if (existing !== null && !existing.equals(content)) skipped.push(name);
11588
11688
  continue;
11589
11689
  }
11590
- plan.put(`${MEMORY_DIR}/${name}`, null, content, "copy", `Copia de ${path30.join(folder, name)}`);
11690
+ plan.put(`${MEMORY_DIR}/${name}`, null, content, "copy", "", path31.join(folder, name));
11591
11691
  copied.push(name);
11592
11692
  }
11593
11693
  }
@@ -11597,7 +11697,7 @@ async function planNativeImport(plan, cwd, home) {
11597
11697
  const known = new Set(linkTargets(index.toString("utf8")));
11598
11698
  const additions = [];
11599
11699
  for (const folder of folders) {
11600
- const nativeIndex = await readIfFile(path30.join(folder, MEMORY_INDEX_FILE), MEMORY_INDEX_FILE);
11700
+ const nativeIndex = await readIfFile(path31.join(folder, MEMORY_INDEX_FILE), MEMORY_INDEX_FILE);
11601
11701
  if (nativeIndex === null) continue;
11602
11702
  for (const line of nativeIndex.toString("utf8").replace(/^\uFEFF/, "").split(/\r?\n/)) {
11603
11703
  const target = linkTargets(line)[0];
@@ -11697,9 +11797,7 @@ function isUtf8Compatible(content) {
11697
11797
  }
11698
11798
  function assertUtf8(rel, content) {
11699
11799
  if (isUtf8Compatible(content)) return;
11700
- throw new MemoryBridgeError(
11701
- `${rel} no est\xE1 en UTF-8 (parece UTF-16, lo que deja \`>\` en Windows PowerShell 5.1). Guardalo como UTF-8: la app no lo toca mientras tanto.`
11702
- );
11800
+ throw new MemoryBridgeError(serverText("memoryNotUtf8", { file: rel }));
11703
11801
  }
11704
11802
  function isBlockCurrent(content, scan, name) {
11705
11803
  if (!scan.hasBlock) return false;
@@ -11781,14 +11879,14 @@ async function inspectInstructionFile(cwd, name) {
11781
11879
  };
11782
11880
  }
11783
11881
  function problemText(file) {
11784
- if (file.problem === "outside") return `${file.name} apunta fuera del proyecto`;
11785
- if (file.problem === "encoding") return `${file.name} no est\xE1 en UTF-8`;
11882
+ if (file.problem === "outside") return serverText("memoryViaOutside", { file: file.name });
11883
+ if (file.problem === "encoding") return serverText("memoryViaEncoding", { file: file.name });
11786
11884
  return null;
11787
11885
  }
11788
11886
  function computeReach(folderExists, indexExists, files) {
11789
11887
  const agents = files.find((file) => file.name === "AGENTS.md");
11790
11888
  const claude = files.find((file) => file.name === "CLAUDE.md");
11791
- const missing = !folderExists ? "falta la carpeta .agents/memory" : !indexExists ? "falta .agents/memory/MEMORY.md" : null;
11889
+ const missing = !folderExists ? serverText("memoryViaNoFolder") : !indexExists ? serverText("memoryViaNoIndex") : null;
11792
11890
  const entry = (agent, reaches, via) => ({
11793
11891
  agent,
11794
11892
  label: MEMORY_AGENT_LABELS[agent],
@@ -11799,21 +11897,21 @@ function computeReach(folderExists, indexExists, files) {
11799
11897
  const claudeHasBlock = claude?.hasBlock === true;
11800
11898
  const agentsProblem = agents === void 0 ? null : problemText(agents);
11801
11899
  const claudeProblem = claude === void 0 ? null : problemText(claude);
11802
- const viaAgents = agentsHasBlock ? "AGENTS.md" : agentsProblem ?? "falta el bloque en AGENTS.md";
11900
+ const viaAgents = agentsHasBlock ? serverText("memoryViaFile", { file: "AGENTS.md" }) : agentsProblem ?? serverText("memoryViaNoBlock", { file: "AGENTS.md" });
11803
11901
  let openCode;
11804
- if (agentsHasBlock) openCode = { reaches: true, via: "AGENTS.md" };
11902
+ if (agentsHasBlock) openCode = { reaches: true, via: serverText("memoryViaFile", { file: "AGENTS.md" }) };
11805
11903
  else if (agents?.exists !== true && claudeHasBlock) {
11806
- openCode = { reaches: true, via: "CLAUDE.md (respaldo)" };
11904
+ openCode = { reaches: true, via: serverText("memoryViaFallback") };
11807
11905
  } else if (agents?.exists === true) {
11808
11906
  openCode = { reaches: false, via: viaAgents };
11809
11907
  } else {
11810
- openCode = { reaches: false, via: "falta el bloque en AGENTS.md o en CLAUDE.md" };
11908
+ openCode = { reaches: false, via: serverText("memoryViaNoBlockEither") };
11811
11909
  }
11812
11910
  return [
11813
11911
  entry(
11814
11912
  "claude-code",
11815
11913
  claudeHasBlock,
11816
- claudeHasBlock ? "CLAUDE.md" : claudeProblem ?? "falta el bloque en CLAUDE.md"
11914
+ claudeHasBlock ? serverText("memoryViaFile", { file: "CLAUDE.md" }) : claudeProblem ?? serverText("memoryViaNoBlock", { file: "CLAUDE.md" })
11817
11915
  ),
11818
11916
  entry("codex", agentsHasBlock, viaAgents),
11819
11917
  entry("antigravity", agentsHasBlock, viaAgents),
@@ -11824,7 +11922,7 @@ async function describeNotes(folder, names) {
11824
11922
  const stated = [];
11825
11923
  for (const name of names) {
11826
11924
  try {
11827
- const info = await stat15(path30.join(folder, name));
11925
+ const info = await stat15(path31.join(folder, name));
11828
11926
  if (info.isFile()) stated.push({ name, sizeBytes: info.size, modifiedAt: info.mtimeMs });
11829
11927
  } catch {
11830
11928
  }
@@ -11832,7 +11930,7 @@ async function describeNotes(folder, names) {
11832
11930
  stated.sort((a, b) => b.modifiedAt - a.modifiedAt || a.name.localeCompare(b.name));
11833
11931
  const notes = [];
11834
11932
  for (const item of stated.slice(0, MAX_LISTED_NOTES)) {
11835
- const head = await readHead(path30.join(folder, item.name));
11933
+ const head = await readHead(path31.join(folder, item.name));
11836
11934
  notes.push({ ...item, ...describeNoteText(head, item.name) });
11837
11935
  }
11838
11936
  return notes;
@@ -11890,31 +11988,31 @@ async function countNativeMemory(cwd, home, sharedNames) {
11890
11988
  return { available: names.size, pending };
11891
11989
  }
11892
11990
  async function nativeMemoryFolders(cwd, home) {
11893
- const root = path30.join(home, ".claude", "projects");
11991
+ const root = path31.join(home, ".claude", "projects");
11894
11992
  const slug = projectSlugFor(cwd).toLowerCase();
11895
11993
  try {
11896
11994
  const entries = await readdir9(root, { withFileTypes: true });
11897
- return entries.filter((entry) => entry.isDirectory() && entry.name.toLowerCase() === slug).map((entry) => entry.name).sort().map((name) => path30.join(root, name, "memory"));
11995
+ return entries.filter((entry) => entry.isDirectory() && entry.name.toLowerCase() === slug).map((entry) => entry.name).sort().map((name) => path31.join(root, name, "memory"));
11898
11996
  } catch {
11899
11997
  return [];
11900
11998
  }
11901
11999
  }
11902
12000
  function buildGlobalFragments(home) {
11903
12001
  const windows = process.platform === "win32";
11904
- const globalFile = path30.join(home, ...GLOBAL_MEMORY_RELATIVE.split("/"));
12002
+ const globalFile = path31.join(home, ...GLOBAL_MEMORY_RELATIVE.split("/"));
11905
12003
  const hardLink = (link) => windows ? `mklink /H "${link}" "${globalFile}"` : `ln "${globalFile}" "${link}"`;
11906
- const hardLinkNote = `Enlace duro${windows ? ", en cmd" : ""}. \`~/${GLOBAL_MEMORY_RELATIVE}\` tiene que existir antes, y el archivo de la CLI no: si ya existe, pas\xE1 lo que tenga a \`global.md\` y borralo. Si tu editor guarda reemplazando el archivo, el enlace se corta: en ese caso copi\xE1 el contenido.`;
11907
- const codexTarget = path30.join(home, ".codex", "AGENTS.md");
11908
- const geminiTarget = path30.join(home, ".gemini", "GEMINI.md");
12004
+ const hardLinkNote = windows ? serverText("memoryNoteHardLinkCmd", { path: GLOBAL_MEMORY_RELATIVE }) : serverText("memoryNoteHardLink", { path: GLOBAL_MEMORY_RELATIVE });
12005
+ const codexTarget = path31.join(home, ".codex", "AGENTS.md");
12006
+ const geminiTarget = path31.join(home, ".gemini", "GEMINI.md");
11909
12007
  const homeSlashes = home.replace(/\\/g, "/").replace(/\/+$/, "");
11910
12008
  return [
11911
12009
  {
11912
12010
  agent: "claude-code",
11913
12011
  label: MEMORY_AGENT_LABELS["claude-code"],
11914
- target: path30.join(home, ".claude", "CLAUDE.md"),
12012
+ target: path31.join(home, ".claude", "CLAUDE.md"),
11915
12013
  kind: "line",
11916
12014
  text: `@~/${GLOBAL_MEMORY_RELATIVE}`,
11917
- note: "Agreg\xE1 esta l\xEDnea al final. Claude Code importa el archivo al arrancar."
12015
+ note: serverText("memoryNoteClaude")
11918
12016
  },
11919
12017
  {
11920
12018
  agent: "codex",
@@ -11935,10 +12033,10 @@ function buildGlobalFragments(home) {
11935
12033
  {
11936
12034
  agent: "opencode",
11937
12035
  label: MEMORY_AGENT_LABELS.opencode,
11938
- target: path30.join(home, ".config", "opencode", "opencode.json"),
12036
+ target: path31.join(home, ".config", "opencode", "opencode.json"),
11939
12037
  kind: "json",
11940
12038
  text: `"instructions": ["${homeSlashes}/${GLOBAL_MEMORY_RELATIVE}"]`,
11941
- note: "Dentro del objeto ra\xEDz. Ruta absoluta: OpenCode no expande `~` en esta clave (sin verificar)."
12039
+ note: serverText("memoryNoteOpenCode")
11942
12040
  }
11943
12041
  ];
11944
12042
  }
@@ -11960,7 +12058,7 @@ function runGit2(cwd, args) {
11960
12058
  }
11961
12059
  resolve({
11962
12060
  kind: "spawn-failed",
11963
- message: code === "ENOENT" ? "No se encontr\xF3 git en el PATH." : error.message
12061
+ text: code === "ENOENT" ? serverText("gitNotFound") : serverText("raw", { text: error.message })
11964
12062
  });
11965
12063
  }
11966
12064
  );
@@ -11969,29 +12067,31 @@ function runGit2(cwd, args) {
11969
12067
  async function readGit(cwd) {
11970
12068
  const fail = (message) => ({ state: { kind: "error", message }, main: null });
11971
12069
  const result = await runGit2(cwd, ["rev-parse", "--show-toplevel", "--git-dir", "--git-common-dir"]);
11972
- if (result.kind === "spawn-failed") return fail(result.message);
12070
+ if (result.kind === "spawn-failed") return fail(result.text);
11973
12071
  if (result.code !== 0) {
11974
12072
  const stderr = result.stderr.trim();
11975
12073
  if (/not a git repository|no es un repositorio/i.test(stderr)) {
11976
12074
  return { state: { kind: "not-repo" }, main: null };
11977
12075
  }
11978
- return fail(stderr.slice(0, 400) || "git rev-parse fall\xF3.");
12076
+ return fail(
12077
+ stderr.length > 0 ? serverText("raw", { text: stderr.slice(0, 400) }) : serverText("gitCommandFailed", { command: "rev-parse" })
12078
+ );
11979
12079
  }
11980
12080
  const [toplevel, gitDir, commonDir] = result.stdout.split(/\r?\n/).map((line) => line.trim());
11981
- if (!toplevel || !gitDir || !commonDir) return fail("Respuesta inesperada de git rev-parse.");
12081
+ if (!toplevel || !gitDir || !commonDir) return fail(serverText("gitUnexpected", { command: "rev-parse" }));
11982
12082
  const ignored = await checkIgnored(cwd, INDEX_REL);
11983
- if (typeof ignored === "string") return fail(ignored);
11984
- const absoluteGitDir = path30.resolve(cwd, gitDir);
11985
- const absoluteCommonDir = path30.resolve(cwd, commonDir);
12083
+ if (typeof ignored !== "boolean") return fail(ignored);
12084
+ const absoluteGitDir = path31.resolve(cwd, gitDir);
12085
+ const absoluteCommonDir = path31.resolve(cwd, commonDir);
11986
12086
  let worktree = null;
11987
12087
  let main2 = null;
11988
12088
  if (!await samePath(absoluteGitDir, absoluteCommonDir)) {
11989
- if (path30.basename(absoluteCommonDir).toLowerCase() === ".git") {
11990
- const mainRoot = path30.dirname(absoluteCommonDir);
11991
- const inner = path30.relative(path30.resolve(toplevel), path30.resolve(cwd));
11992
- const sameFolder = inner.length === 0 || inner.startsWith("..") || path30.isAbsolute(inner);
11993
- const mainPath = sameFolder ? mainRoot : path30.join(mainRoot, inner);
11994
- const memoryRel = sameFolder ? MEMORY_DIR : [...inner.split(path30.sep), MEMORY_DIR].join("/");
12089
+ if (path31.basename(absoluteCommonDir).toLowerCase() === ".git") {
12090
+ const mainRoot = path31.dirname(absoluteCommonDir);
12091
+ const inner = path31.relative(path31.resolve(toplevel), path31.resolve(cwd));
12092
+ const sameFolder = inner.length === 0 || inner.startsWith("..") || path31.isAbsolute(inner);
12093
+ const mainPath = sameFolder ? mainRoot : path31.join(mainRoot, inner);
12094
+ const memoryRel = sameFolder ? MEMORY_DIR : [...inner.split(path31.sep), MEMORY_DIR].join("/");
11995
12095
  main2 = { root: mainRoot, memoryRel };
11996
12096
  worktree = {
11997
12097
  mainPath,
@@ -12005,17 +12105,18 @@ async function readGit(cwd) {
12005
12105
  }
12006
12106
  async function checkIgnored(cwd, rel) {
12007
12107
  const result = await runGit2(cwd, ["check-ignore", "-q", "--", rel]);
12008
- if (result.kind === "spawn-failed") return result.message;
12108
+ if (result.kind === "spawn-failed") return result.text;
12009
12109
  if (result.code === 0) return true;
12010
12110
  if (result.code === 1) return false;
12011
- return result.stderr.trim().slice(0, 400) || "git check-ignore fall\xF3.";
12111
+ const stderr = result.stderr.trim().slice(0, 400);
12112
+ return stderr.length > 0 ? serverText("raw", { text: stderr }) : serverText("gitCommandFailed", { command: "check-ignore" });
12012
12113
  }
12013
12114
  async function samePath(a, b) {
12014
12115
  const resolve = async (value) => {
12015
12116
  try {
12016
12117
  return await realpath2(value);
12017
12118
  } catch {
12018
- return path30.resolve(value);
12119
+ return path31.resolve(value);
12019
12120
  }
12020
12121
  };
12021
12122
  const [left, right] = await Promise.all([resolve(a), resolve(b)]);
@@ -12025,10 +12126,8 @@ async function guard(cwd, rel) {
12025
12126
  try {
12026
12127
  return await resolveInside(cwd, rel);
12027
12128
  } catch (error) {
12028
- if (error instanceof InvalidPathError && !/ya no existe/.test(error.message)) {
12029
- throw new InvalidPathError(
12030
- `${rel} apunta fuera del proyecto (un enlace o una junction): la app no escribe ah\xED.`
12031
- );
12129
+ if (error instanceof InvalidPathError && error.text.key !== "tabDirGone") {
12130
+ throw new InvalidPathError(serverText("memoryLinkOutside", { path: rel }));
12032
12131
  }
12033
12132
  throw error;
12034
12133
  }
@@ -12060,7 +12159,7 @@ async function listNoteFiles(folder) {
12060
12159
  async function readIfFile(absolute, label) {
12061
12160
  try {
12062
12161
  const info = await stat15(absolute);
12063
- if (!info.isFile()) throw new MemoryBridgeError(`${label} existe pero no es un archivo.`);
12162
+ if (!info.isFile()) throw new MemoryBridgeError(serverText("memoryNotAFile", { label }));
12064
12163
  return await readFile10(absolute);
12065
12164
  } catch (error) {
12066
12165
  if (isNotFound(error)) return null;
@@ -12108,9 +12207,7 @@ async function writeAtomic(target, content, mustNotExist) {
12108
12207
  await handle.close();
12109
12208
  }
12110
12209
  if (mustNotExist && await exists2(target)) {
12111
- throw new MemoryBridgeError(
12112
- `${path30.basename(target)} apareci\xF3 mientras se instalaba. Volv\xE9 a mirar los cambios.`
12113
- );
12210
+ throw new MemoryBridgeError(serverText("memoryAppeared", { file: path31.basename(target) }));
12114
12211
  }
12115
12212
  await renameWithRetry(temporary, target);
12116
12213
  } catch (error) {
@@ -12121,9 +12218,9 @@ async function writeAtomic(target, content, mustNotExist) {
12121
12218
  async function createTemporary(target) {
12122
12219
  for (let attempt = 1; ; attempt += 1) {
12123
12220
  const suffix = randomBytes3(6).toString("hex");
12124
- const temporary = path30.join(
12125
- path30.dirname(target),
12126
- `.${path30.basename(target)}.${process.pid}.${suffix}.tmp`
12221
+ const temporary = path31.join(
12222
+ path31.dirname(target),
12223
+ `.${path31.basename(target)}.${process.pid}.${suffix}.tmp`
12127
12224
  );
12128
12225
  try {
12129
12226
  return { temporary, handle: await open6(temporary, "wx") };
@@ -12159,7 +12256,7 @@ async function renameWithRetry(from, to) {
12159
12256
  var WATCH_DEBOUNCE_MS2 = 300;
12160
12257
  var UnknownTerminalError = class extends Error {
12161
12258
  constructor() {
12162
- super("La terminal ya no existe.");
12259
+ super("The terminal no longer exists.");
12163
12260
  this.name = "UnknownTerminalError";
12164
12261
  }
12165
12262
  };
@@ -12293,7 +12390,7 @@ var MemoryHub = class extends EventEmitter3 {
12293
12390
  async dispose(entry) {
12294
12391
  if (entry.debounceTimer !== null) clearTimeout(entry.debounceTimer);
12295
12392
  this.watches.delete(entry.key);
12296
- debugLog("memory", `dejo de mirar la memoria de ${entry.cwd}`);
12393
+ debugLog("memory", `stopped watching the memory of ${entry.cwd}`);
12297
12394
  await entry.watcher?.close().catch(() => void 0);
12298
12395
  }
12299
12396
  /**
@@ -12334,14 +12431,14 @@ var MemoryHub = class extends EventEmitter3 {
12334
12431
  entry.lastJson = json;
12335
12432
  for (const terminalId of entry.subscribers.keys()) this.emit("status", terminalId, status);
12336
12433
  } catch (error) {
12337
- console.warn(`[memoria] no se pudo leer el estado de ${entry.cwd}:`, error);
12434
+ console.warn(`[memory] couldn't read the status of ${entry.cwd}:`, error);
12338
12435
  }
12339
12436
  }
12340
12437
  async inspectQuietly(cwd) {
12341
12438
  try {
12342
12439
  return await inspectMemory(cwd, this.home);
12343
12440
  } catch (error) {
12344
- console.warn(`[memoria] no se pudo releer el estado de ${cwd}:`, error);
12441
+ console.warn(`[memory] couldn't reread the status of ${cwd}:`, error);
12345
12442
  return null;
12346
12443
  }
12347
12444
  }
@@ -12361,13 +12458,13 @@ var MemoryHub = class extends EventEmitter3 {
12361
12458
  }
12362
12459
  };
12363
12460
  function keyFor(cwd) {
12364
- const resolved = path31.resolve(cwd);
12461
+ const resolved = path32.resolve(cwd);
12365
12462
  return process.platform === "win32" ? resolved.toLowerCase() : resolved;
12366
12463
  }
12367
12464
  function isRelevantPath(cwd, candidate) {
12368
- const relative = path31.relative(cwd, candidate);
12465
+ const relative = path32.relative(cwd, candidate);
12369
12466
  if (relative.length === 0) return true;
12370
- if (relative.startsWith("..") || path31.isAbsolute(relative)) return false;
12467
+ if (relative.startsWith("..") || path32.isAbsolute(relative)) return false;
12371
12468
  const same = (a, b) => process.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
12372
12469
  const parts = relative.split(/[\\/]/);
12373
12470
  const [first = "", second = "", third = ""] = parts;
@@ -12387,7 +12484,7 @@ function isRelevantPath(cwd, candidate) {
12387
12484
  import { createHash, randomUUID as randomUUID2 } from "node:crypto";
12388
12485
  import { mkdir as mkdir6, readdir as readdir10, rm as rm2, stat as stat16, writeFile as writeFile3 } from "node:fs/promises";
12389
12486
  import { tmpdir as tmpdir4 } from "node:os";
12390
- import path32 from "node:path";
12487
+ import path33 from "node:path";
12391
12488
 
12392
12489
  // packages/server/src/vault/markdown.ts
12393
12490
  var pad2 = (value) => String(value).padStart(2, "0");
@@ -12591,8 +12688,8 @@ var HEADER_TITLE_CHARS = 200;
12591
12688
  var HEADER_CWD_CHARS = 1e3;
12592
12689
  var QUESTION_LABEL_CHARS = 200;
12593
12690
  var TEXT_CHAR_STEPS = [4e3, 2e3, 1e3, 500, 250];
12594
- var HANDOFF_EMPTY_MESSAGE = "Esa conversaci\xF3n no tiene mensajes que continuar.";
12595
- var HANDOFF_NO_TRANSCRIPT_MESSAGE = "Esa conversaci\xF3n no tiene mensajes que continuar: esa CLI no dej\xF3 transcript legible.";
12691
+ var HANDOFF_EMPTY_TEXT = serverText("handoffEmpty");
12692
+ var HANDOFF_NO_TRANSCRIPT_TEXT = serverText("handoffNoTranscript");
12596
12693
  function opensTurn(event) {
12597
12694
  return event.role === "user" && event.parts.some((part) => part.kind === "text" && part.text.trim().length > 0);
12598
12695
  }
@@ -12649,36 +12746,46 @@ function renderText(text, truncated, maxChars) {
12649
12746
  const clean = sanitizeForPaste(text).replace(/\s+$/u, "");
12650
12747
  if (clean.trim().length === 0) return null;
12651
12748
  const { text: kept, cut: cut2 } = cutChars(clean, maxChars);
12652
- return cut2 || truncated ? `${kept}\u2026 (recortado)` : kept;
12749
+ return cut2 || truncated ? `${kept}\u2026 (truncated)` : kept;
12750
+ }
12751
+ var imagesText = (count) => count === 1 ? "[image not included]" : `[${count} images not included]`;
12752
+ function noticeLine(part) {
12753
+ switch (part.notice) {
12754
+ case "compacted":
12755
+ return part.detail === "auto" ? "Context compacted automatically" : "Context compacted";
12756
+ case "interrupted":
12757
+ return "Interrupted";
12758
+ case "error":
12759
+ return part.detail.length > 0 ? `The CLI returned an error: ${part.detail}` : "The CLI returned an error";
12760
+ }
12653
12761
  }
12654
- var imagesText = (count) => count === 1 ? "[imagen no incluida]" : `[${count} im\xE1genes no incluidas]`;
12655
12762
  function renderResult(result) {
12656
12763
  const { text, cut: cut2 } = cutChars(oneLine2(result.text), HANDOFF_TOOL_RESULT_CHARS);
12657
12764
  const marks = [];
12658
12765
  if (result.isError) marks.push("error");
12659
- if (cut2 || result.truncated) marks.push("recortado");
12766
+ if (cut2 || result.truncated) marks.push("truncated");
12660
12767
  let body = cut2 ? `${text}\u2026` : text;
12661
12768
  if (result.imageCount > 0) body = body.length > 0 ? `${body} ${imagesText(result.imageCount)}` : imagesText(result.imageCount);
12662
- if (body.length === 0) body = "sin texto";
12769
+ if (body.length === 0) body = "no text";
12663
12770
  return marks.length > 0 ? `${marks.join(", ")}: ${body}` : body;
12664
12771
  }
12665
12772
  function renderToolLine(part, result) {
12666
12773
  const name = cutChars(oneLine2(part.name), 80).text;
12667
12774
  const { text: input, cut: cut2 } = cutChars(oneLine2(part.input), HANDOFF_TOOL_INPUT_CHARS);
12668
- const head = `- ${inlineCode2(name.length > 0 ? name : "herramienta")}`;
12775
+ const head = `- ${inlineCode2(name.length > 0 ? name : "tool")}`;
12669
12776
  const withInput = input.length > 0 ? `${head}: ${inlineCode2(cut2 || part.truncated ? `${input}\u2026` : input)}` : head;
12670
- return `${withInput} \u2192 ${result !== void 0 ? renderResult(result) : "sin resultado"}`;
12777
+ return `${withInput} \u2192 ${result !== void 0 ? renderResult(result) : "no result"}`;
12671
12778
  }
12672
12779
  function renderQuestionLine(part, result) {
12673
12780
  const labels = part.questions.map((item) => {
12674
12781
  const label = oneLine2(item.question).length > 0 ? oneLine2(item.question) : oneLine2(item.header);
12675
12782
  return `"${cutChars(label, QUESTION_LABEL_CHARS).text}"`;
12676
12783
  }).join(", ");
12677
- const title = part.questions.length === 1 ? `- Pregunta ${labels}` : `- Preguntas ${labels}`;
12678
- if (result === void 0) return `${title}: sin responder`;
12784
+ const title = part.questions.length === 1 ? `- Question ${labels}` : `- Questions ${labels}`;
12785
+ if (result === void 0) return `${title}: unanswered`;
12679
12786
  const { text, cut: cut2 } = cutChars(oneLine2(result.text), HANDOFF_TOOL_RESULT_CHARS);
12680
12787
  const answer = `"${cut2 ? `${text}\u2026` : text}"`;
12681
- return `${title}: ${result.isError ? "no respondida" : "respondida"} ${answer}`;
12788
+ return `${title}: ${result.isError ? "not answered" : "answered"} ${answer}`;
12682
12789
  }
12683
12790
  function renderRequestBody(event, textChars) {
12684
12791
  const blocks = [];
@@ -12694,7 +12801,7 @@ function renderRequestBody(event, textChars) {
12694
12801
  }
12695
12802
  function renderTurn(turn, textChars) {
12696
12803
  const [request, ...rest] = turn.events;
12697
- if (request === void 0) return `## Turno ${turn.number} \xB7 usuario`;
12804
+ if (request === void 0) return `## Turn ${turn.number} \xB7 user`;
12698
12805
  const results = /* @__PURE__ */ new Map();
12699
12806
  const calls = /* @__PURE__ */ new Set();
12700
12807
  for (const event of turn.events) {
@@ -12703,7 +12810,7 @@ function renderTurn(turn, textChars) {
12703
12810
  if (part.kind === "tool-call" || part.kind === "question") calls.add(part.toolUseId);
12704
12811
  }
12705
12812
  }
12706
- const heading = `## Turno ${turn.number} \xB7 usuario${request.queued ? " (enviado mientras trabajaba)" : ""}`;
12813
+ const heading = `## Turn ${turn.number} \xB7 user${request.queued ? " (sent while the agent was working)" : ""}`;
12707
12814
  const userBlocks = [heading, ...renderRequestBody(request, textChars)];
12708
12815
  const answer = [];
12709
12816
  let listOpen = false;
@@ -12731,13 +12838,13 @@ ${line}`;
12731
12838
  pushLine(renderQuestionLine(part, results.get(part.toolUseId)));
12732
12839
  return;
12733
12840
  case "tool-result":
12734
- if (!calls.has(part.toolUseId)) pushLine(`- Resultado de una herramienta anterior \u2192 ${renderResult(part)}`);
12841
+ if (!calls.has(part.toolUseId)) pushLine(`- Result of an earlier tool call \u2192 ${renderResult(part)}`);
12735
12842
  return;
12736
12843
  case "image":
12737
- pushLine(event.role === "user" ? `- ${imagesText(1)} (del usuario)` : `- ${imagesText(1)}`);
12844
+ pushLine(event.role === "user" ? `- ${imagesText(1)} (from the user)` : `- ${imagesText(1)}`);
12738
12845
  return;
12739
12846
  case "notice":
12740
- pushLine(`- Aviso: ${oneLine2(noticeText(part))}`);
12847
+ pushLine(`- Notice: ${oneLine2(noticeLine(part))}`);
12741
12848
  return;
12742
12849
  case "thinking":
12743
12850
  return;
@@ -12750,24 +12857,25 @@ ${line}`;
12750
12857
  for (const part of event.parts) renderAnswerPart(event, part);
12751
12858
  }
12752
12859
  const blocks = [userBlocks.join("\n\n")];
12753
- if (answer.length > 0) blocks.push([`## Turno ${turn.number} \xB7 asistente`, ...answer].join("\n\n"));
12860
+ if (answer.length > 0) blocks.push([`## Turn ${turn.number} \xB7 assistant`, ...answer].join("\n\n"));
12754
12861
  return blocks.join("\n\n");
12755
12862
  }
12756
12863
  function renderOpening(turn, textChars) {
12757
12864
  const request = turn.events[0];
12758
12865
  const body = request === void 0 ? [] : renderRequestBody(request, textChars);
12759
- return ["## Pedido inicial", ...body].join("\n\n");
12866
+ return ["## Initial request", ...body].join("\n\n");
12760
12867
  }
12761
12868
  function coverageSentence(included, total, complete) {
12762
12869
  let what;
12870
+ const last = included === 1 ? "the last one" : `the last ${included}`;
12763
12871
  if (!complete) {
12764
- what = `Incluye ${included === 1 ? "el \xFAltimo" : `los \xFAltimos ${included}`} de m\xE1s de ${total} turnos, numerados desde el m\xE1s viejo que se ley\xF3.`;
12872
+ what = `Includes ${last} of more than ${total} turns, numbered from the oldest one that was read.`;
12765
12873
  } else if (included >= total) {
12766
- what = total === 1 ? "Incluye el \xFAnico turno." : `Incluye los ${total} turnos.`;
12874
+ what = total === 1 ? "Includes the only turn." : `Includes all ${total} turns.`;
12767
12875
  } else {
12768
- what = `Incluye ${included === 1 ? "el \xFAltimo" : `los \xFAltimos ${included}`} de ${total} turnos.`;
12876
+ what = `Includes ${last} of ${total} turns.`;
12769
12877
  }
12770
- return `${what} Los resultados de herramientas est\xE1n recortados y las im\xE1genes no se incluyen.`;
12878
+ return `${what} Tool results are truncated and images are not included.`;
12771
12879
  }
12772
12880
  function renderHeader(header, included, selection) {
12773
12881
  const label = cutChars(oneLine2(header.sourceLabel), 80).text;
@@ -12775,14 +12883,15 @@ function renderHeader(header, included, selection) {
12775
12883
  const cwd = cutChars(sanitizeForPaste(header.cwd).replace(/[\n\t]/g, " ").trim(), HEADER_CWD_CHARS).text;
12776
12884
  const agent = cutChars(oneLine2(header.agent), 80).text;
12777
12885
  const sessionId = cutChars(oneLine2(header.sessionId), 200).text;
12886
+ const named = title.length > 0 && title !== UNTITLED_SESSION_TITLE ? title : "untitled";
12778
12887
  const facts = [
12779
- `- Proyecto: ${cwd.trim().length > 0 ? inlineCode2(cwd) : "desconocido"}`,
12780
- `- Conversaci\xF3n: ${title.length > 0 ? title : UNTITLED_SESSION_TITLE} (${agent}, ${sessionId})`
12888
+ `- Project: ${cwd.trim().length > 0 ? inlineCode2(cwd) : "unknown"}`,
12889
+ `- Conversation: ${named} (${agent}, ${sessionId})`
12781
12890
  ];
12782
- if (header.lastAt !== null && header.lastAt > 0) facts.push(`- \xDAltima respuesta: ${formatStamp(header.lastAt)}`);
12891
+ if (header.lastAt !== null && header.lastAt > 0) facts.push(`- Last reply: ${formatStamp(header.lastAt)}`);
12783
12892
  facts.push(`- ${coverageSentence(included, selection.totalTurns, selection.complete)}`);
12784
- if (header.partial) facts.push("- Historial parcial: la conversaci\xF3n original no se pudo leer entera.");
12785
- return `# Continuaci\xF3n de una conversaci\xF3n con ${label}
12893
+ if (header.partial) facts.push("- Partial history: the original conversation couldn't be read in full.");
12894
+ return `# Continuation of a conversation with ${label}
12786
12895
 
12787
12896
  ${facts.join("\n")}`;
12788
12897
  }
@@ -12808,7 +12917,7 @@ function elide(block, maxBytes, maxLines) {
12808
12917
  const lines = block.split("\n");
12809
12918
  const heading = lines[0] ?? "";
12810
12919
  const body = lines.slice(1);
12811
- const marker = (omitted2) => `[\u2026 ${omitted2} l\xEDneas omitidas para que el transcript entre en su tope \u2026]`;
12920
+ const marker = (omitted2) => `[\u2026 ${omitted2} lines omitted to keep the transcript within its limit \u2026]`;
12812
12921
  const markerBytes = Buffer.byteLength(marker(body.length), "utf8") + 1;
12813
12922
  const available = Math.max(0, maxBytes - Buffer.byteLength(heading, "utf8") - 1 - markerBytes);
12814
12923
  const availableLines = Math.max(0, maxLines - 3);
@@ -12880,25 +12989,25 @@ function quote(text) {
12880
12989
  function buildContinuationMessage(input) {
12881
12990
  const label = oneLine2(input.sourceLabel);
12882
12991
  const lines = [
12883
- `Esto contin\xFAa una conversaci\xF3n que empez\xF3 con otro asistente (${label}).`,
12884
- input.includedTurns === 1 ? `El transcript del \xFAltimo turno est\xE1 en ${input.reference}.` : `El transcript de los \xFAltimos ${input.includedTurns} turnos est\xE1 en ${input.reference}.`
12992
+ `This continues a conversation that started with another assistant (${label}).`,
12993
+ input.includedTurns === 1 ? `The transcript of the last turn is in ${input.reference}.` : `The transcript of the last ${input.includedTurns} turns is in ${input.reference}.`
12885
12994
  ];
12886
12995
  if (input.lastRequest !== null && sanitizeForPaste(input.lastRequest).trim().length > 0) {
12887
- lines.push("Leelo entero y segu\xED desde el \xFAltimo pedido, que fue:", "", quote(input.lastRequest));
12996
+ lines.push("Read all of it and continue from the last request, which was:", "", quote(input.lastRequest));
12888
12997
  } else {
12889
- lines.push("Leelo entero y segu\xED desde donde qued\xF3.");
12998
+ lines.push("Read all of it and continue from where it left off.");
12890
12999
  }
12891
13000
  return sanitizeForPaste(lines.join("\n"));
12892
13001
  }
12893
- function transcriptReferenceFor(path47, style) {
12894
- return style === "at-quoted" ? fileReference(path47, "at-quoted") : `"${path47}"`;
13002
+ function transcriptReferenceFor(path48, style) {
13003
+ return style === "at-quoted" ? fileReference(path48, "at-quoted") : `"${path48}"`;
12895
13004
  }
12896
13005
  function planTranscript(input) {
12897
13006
  const turns = splitTurns(input.events);
12898
13007
  if (turns.length === 0) {
12899
13008
  return {
12900
13009
  ok: false,
12901
- message: input.state === "no-transcript" ? HANDOFF_NO_TRANSCRIPT_MESSAGE : HANDOFF_EMPTY_MESSAGE
13010
+ text: input.state === "no-transcript" ? HANDOFF_NO_TRANSCRIPT_TEXT : HANDOFF_EMPTY_TEXT
12902
13011
  };
12903
13012
  }
12904
13013
  const complete = input.complete ?? true;
@@ -12919,6 +13028,102 @@ function planTranscript(input) {
12919
13028
  };
12920
13029
  }
12921
13030
 
13031
+ // packages/server/src/attachments.ts
13032
+ var MAX_NAME_CHARS = 60;
13033
+ var MAX_DISPLAY_CHARS = 100;
13034
+ var BLOCKED_EXTENSIONS = /* @__PURE__ */ new Set([
13035
+ "exe",
13036
+ "dll",
13037
+ "msi",
13038
+ "msp",
13039
+ "scr",
13040
+ "com",
13041
+ "pif",
13042
+ "cpl",
13043
+ "sys",
13044
+ "lnk",
13045
+ "hta",
13046
+ "jar",
13047
+ "app",
13048
+ "dmg"
13049
+ ]);
13050
+ var AT_ATTACHABLE_EXTENSIONS = /* @__PURE__ */ new Set([
13051
+ "txt",
13052
+ "log",
13053
+ "md",
13054
+ "markdown",
13055
+ "json",
13056
+ "jsonl",
13057
+ "csv",
13058
+ "tsv",
13059
+ "xml",
13060
+ "html",
13061
+ "htm",
13062
+ "css",
13063
+ "yaml",
13064
+ "yml",
13065
+ "ini",
13066
+ "conf",
13067
+ "cfg",
13068
+ "toml",
13069
+ "sql",
13070
+ "ps1",
13071
+ "sh",
13072
+ "bat",
13073
+ "cmd",
13074
+ "py",
13075
+ "js",
13076
+ "mjs",
13077
+ "cjs",
13078
+ "ts",
13079
+ "tsx",
13080
+ "jsx",
13081
+ "cs",
13082
+ "java",
13083
+ "go",
13084
+ "rs",
13085
+ "rb",
13086
+ "php",
13087
+ "c",
13088
+ "h",
13089
+ "cpp",
13090
+ "pdf"
13091
+ ]);
13092
+ function extensionOf(name) {
13093
+ const match = /\.([A-Za-z0-9]{1,10})$/.exec(name);
13094
+ return match?.[1]?.toLowerCase() ?? "";
13095
+ }
13096
+ function isBlockedExtension(name) {
13097
+ return BLOCKED_EXTENSIONS.has(extensionOf(name));
13098
+ }
13099
+ function safeFileName(hint) {
13100
+ const last = hint.split(/[\\/]/).pop() ?? "";
13101
+ const clean = last.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").replace(/[^A-Za-z0-9._-]+/g, "_").replace(/_{2,}/g, "_").replace(/\.{2,}/g, ".").replace(/^[._-]+/, "").replace(/[._-]+$/, "");
13102
+ if (clean.length === 0) return "archivo";
13103
+ if (clean.length <= MAX_NAME_CHARS) return clean;
13104
+ const extension = extensionOf(clean);
13105
+ if (extension.length === 0) return clean.slice(0, MAX_NAME_CHARS);
13106
+ const stem = clean.slice(0, clean.length - extension.length - 1);
13107
+ return `${stem.slice(0, MAX_NAME_CHARS - extension.length - 1)}.${extension}`;
13108
+ }
13109
+ function attachmentReference(filePath, style) {
13110
+ const effective = style === "at-quoted" && AT_ATTACHABLE_EXTENSIONS.has(extensionOf(filePath)) ? "at-quoted" : "quoted-path";
13111
+ return transcriptReferenceFor(filePath, effective);
13112
+ }
13113
+ function formatAttachmentBytes(bytes) {
13114
+ if (bytes < 1024) return `${bytes} B`;
13115
+ if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
13116
+ return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
13117
+ }
13118
+ function attachmentLine(originalName, bytes, reference) {
13119
+ const display = Array.from(originalName).map((char) => (char.codePointAt(0) ?? 0) < 32 || char === "\x7F" || char === '"' ? " " : char).join("").replace(/\s+/g, " ").trim().slice(0, MAX_DISPLAY_CHARS);
13120
+ const label = display.length > 0 ? display : "unnamed";
13121
+ return `Attached file (${label}, ${formatAttachmentBytes(bytes)}): ${reference}`;
13122
+ }
13123
+ function textWithAttachments(text, lines) {
13124
+ return lines.length === 0 ? text : [...lines, text].filter((piece) => piece.trim().length > 0).join("\n");
13125
+ }
13126
+
12922
13127
  // packages/server/src/image-signature.ts
12923
13128
  var FORMATS = [
12924
13129
  {
@@ -12954,10 +13159,14 @@ var EXTENSIONS = /* @__PURE__ */ new Map([
12954
13159
  ]);
12955
13160
  var MAX_IMAGE_BYTES3 = MAX_SUBMIT_IMAGE_BYTES;
12956
13161
  var MAX_IMAGES_PER_SUBMIT = MAX_SUBMIT_IMAGES;
13162
+ var MAX_FILE_BYTES = MAX_SUBMIT_FILE_BYTES;
13163
+ var MAX_FILES_PER_SUBMIT = MAX_SUBMIT_FILES;
12957
13164
  var STALE_AFTER_MS = 24 * 60 * 60 * 1e3;
12958
13165
  var DIRECTORY_MODE = 448;
12959
13166
  var FILE_MODE = 384;
12960
- var PasteImageError = class extends Error {
13167
+ var PasteImageError = class extends ServerTextError {
13168
+ };
13169
+ var PasteFileError = class extends ServerTextError {
12961
13170
  };
12962
13171
  var MAX_TEXT_BYTES = HANDOFF_MAX_BYTES + 4 * 1024;
12963
13172
  var PasteTextError = class extends Error {
@@ -12966,7 +13175,7 @@ var PasteStore = class {
12966
13175
  root;
12967
13176
  counter = 0;
12968
13177
  constructor(root) {
12969
- this.root = root ?? path32.join(tmpdir4(), "agent-workbench", "pasted");
13178
+ this.root = root ?? path33.join(tmpdir4(), "agent-workbench", "pasted");
12970
13179
  }
12971
13180
  /**
12972
13181
  * Guarda una imagen y devuelve su ruta.
@@ -12977,24 +13186,27 @@ var PasteStore = class {
12977
13186
  async save(terminalId, mediaType, base64) {
12978
13187
  const declared = EXTENSIONS.get(mediaType);
12979
13188
  if (declared === void 0) {
12980
- throw new PasteImageError(`Tipo de imagen no admitido: ${mediaType}`);
13189
+ throw new PasteImageError(serverText("imageTypeUnsupported", { type: mediaType }));
12981
13190
  }
12982
13191
  const bytes = Buffer.from(base64, "base64");
12983
- if (bytes.length === 0) throw new PasteImageError("La imagen llego vacia.");
13192
+ if (bytes.length === 0) throw new PasteImageError(serverText("imageEmpty"));
12984
13193
  if (bytes.length > MAX_IMAGE_BYTES3) {
12985
13194
  throw new PasteImageError(
12986
- `La imagen pesa ${Math.round(bytes.length / 1024 / 1024)} MB; el maximo son ${MAX_IMAGE_BYTES3 / 1024 / 1024} MB.`
13195
+ serverText("imageTooLarge", {
13196
+ size: Math.round(bytes.length / 1024 / 1024),
13197
+ max: MAX_IMAGE_BYTES3 / 1024 / 1024
13198
+ })
12987
13199
  );
12988
13200
  }
12989
13201
  const signature = detectImageFormat(bytes);
12990
13202
  if (signature === null) {
12991
- throw new PasteImageError("El contenido no es una imagen de un formato conocido.");
13203
+ throw new PasteImageError(serverText("imageUnknownFormat"));
12992
13204
  }
12993
- const directory = path32.join(this.root, safeSegment(terminalId));
13205
+ const directory = path33.join(this.root, safeSegment(terminalId));
12994
13206
  await mkdir6(directory, { recursive: true, mode: DIRECTORY_MODE });
12995
13207
  this.counter += 1;
12996
13208
  const name = `pegada-${this.counter}-${randomUUID2().slice(0, 8)}.${signature.ext}`;
12997
- const file = path32.join(directory, name);
13209
+ const file = path33.join(directory, name);
12998
13210
  await writeFile3(file, bytes, { mode: FILE_MODE });
12999
13211
  return { path: file, bytes: bytes.length };
13000
13212
  }
@@ -13010,19 +13222,53 @@ var PasteStore = class {
13010
13222
  async saveText(terminalId, baseName, content) {
13011
13223
  const bytes = Buffer.from(content, "utf8");
13012
13224
  if (bytes.length > MAX_TEXT_BYTES) {
13013
- throw new PasteTextError(`El texto pesa ${bytes.length} bytes; el maximo son ${MAX_TEXT_BYTES}.`);
13225
+ throw new PasteTextError(`The text is ${bytes.length} bytes; the maximum is ${MAX_TEXT_BYTES}.`);
13014
13226
  }
13015
- const directory = path32.join(this.root, safeSegment(terminalId));
13227
+ const directory = path33.join(this.root, safeSegment(terminalId));
13016
13228
  await mkdir6(directory, { recursive: true, mode: DIRECTORY_MODE });
13017
13229
  this.counter += 1;
13018
13230
  const name = `${baseName}-${this.counter}-${randomUUID2().slice(0, 8)}.md`;
13019
- const file = path32.join(directory, name);
13231
+ const file = path33.join(directory, name);
13020
13232
  await writeFile3(file, bytes, { mode: FILE_MODE });
13021
13233
  return { path: file, bytes: bytes.length };
13022
13234
  }
13235
+ /**
13236
+ * Guarda un archivo adjunto del cuadro de escritura (hito 33, §6.21).
13237
+ *
13238
+ * Mismas reglas que una imagen, con una diferencia: un documento no tiene una
13239
+ * firma que comprobar, asi que lo que se cuida es el nombre. El cliente manda
13240
+ * una pista y `safeFileName` la reduce a `[A-Za-z0-9._-]`; va **detras** del
13241
+ * prefijo que pone el servidor —`adjunto-<n>-<8 hex>-`—, asi que ni un nombre
13242
+ * reservado de Windows ni un `..` llegan a ser el nombre del archivo. Lo que
13243
+ * el sistema ejecuta con un doble clic no se guarda.
13244
+ */
13245
+ async saveAttachment(terminalId, nameHint, base64) {
13246
+ const safe = safeFileName(nameHint);
13247
+ if (isBlockedExtension(safe)) {
13248
+ throw new PasteFileError(serverText("attachmentExecutable", { name: safe }));
13249
+ }
13250
+ const bytes = Buffer.from(base64, "base64");
13251
+ if (bytes.length === 0) throw new PasteFileError(serverText("attachmentEmpty", { name: safe }));
13252
+ if (bytes.length > MAX_FILE_BYTES) {
13253
+ throw new PasteFileError(
13254
+ serverText("attachmentTooLarge", {
13255
+ name: safe,
13256
+ size: Math.round(bytes.length / 1024 / 1024),
13257
+ max: MAX_FILE_BYTES / 1024 / 1024
13258
+ })
13259
+ );
13260
+ }
13261
+ const directory = path33.join(this.root, safeSegment(terminalId));
13262
+ await mkdir6(directory, { recursive: true, mode: DIRECTORY_MODE });
13263
+ this.counter += 1;
13264
+ const name = `adjunto-${this.counter}-${randomUUID2().slice(0, 8)}-${safe}`;
13265
+ const file = path33.join(directory, name);
13266
+ await writeFile3(file, bytes, { mode: FILE_MODE });
13267
+ return { path: file, bytes: bytes.length, name };
13268
+ }
13023
13269
  /** Borra lo de una pestana. Se llama al cerrarla. */
13024
13270
  async clearTerminal(terminalId) {
13025
- await rm2(path32.join(this.root, safeSegment(terminalId)), {
13271
+ await rm2(path33.join(this.root, safeSegment(terminalId)), {
13026
13272
  recursive: true,
13027
13273
  force: true
13028
13274
  }).catch(() => {
@@ -13044,7 +13290,7 @@ var PasteStore = class {
13044
13290
  }
13045
13291
  await Promise.all(
13046
13292
  entries.map(async (entry) => {
13047
- const full = path32.join(this.root, entry);
13293
+ const full = path33.join(this.root, entry);
13048
13294
  try {
13049
13295
  const info = await stat16(full);
13050
13296
  if (now - info.mtimeMs > STALE_AFTER_MS) {
@@ -13064,7 +13310,7 @@ function safeSegment(value) {
13064
13310
  // packages/server/src/session-index.ts
13065
13311
  import { EventEmitter as EventEmitter4 } from "node:events";
13066
13312
  import { mkdir as mkdir7, readFile as readFile11, stat as stat17, writeFile as writeFile4 } from "node:fs/promises";
13067
- import path33 from "node:path";
13313
+ import path34 from "node:path";
13068
13314
  var CACHE_VERSION = 4;
13069
13315
  var vaultPairKey = (agent, sessionId) => `${agent}/${sessionId}`;
13070
13316
  function vaultOnlySessions(natives, vault, listState, ready) {
@@ -13163,7 +13409,7 @@ function migrateEntryV3(filePath, value) {
13163
13409
  return {
13164
13410
  agent: "claude-code",
13165
13411
  ref: filePath,
13166
- group: path33.basename(path33.dirname(filePath)),
13412
+ group: path34.basename(path34.dirname(filePath)),
13167
13413
  mtimeMs,
13168
13414
  sizeBytes,
13169
13415
  cwd,
@@ -13444,7 +13690,7 @@ var SessionIndex = class extends EventEmitter4 {
13444
13690
  await mkdir7(appConfigDir(), { recursive: true });
13445
13691
  await writeFile4(sessionIndexCachePath(), JSON.stringify(this.cache), "utf8");
13446
13692
  } catch (error) {
13447
- console.warn("[indice] no se pudo guardar la cache:", error);
13693
+ console.warn("[index] couldn't save the cache:", error);
13448
13694
  }
13449
13695
  }
13450
13696
  /**
@@ -13467,7 +13713,7 @@ var SessionIndex = class extends EventEmitter4 {
13467
13713
  try {
13468
13714
  return { items: await source.history.list(), threw: false };
13469
13715
  } catch (error) {
13470
- console.warn(`[indice] no se pudo listar el historial de ${source.agent}:`, error);
13716
+ console.warn(`[index] couldn't list the history of ${source.agent}:`, error);
13471
13717
  return { items: null, threw: true };
13472
13718
  }
13473
13719
  })
@@ -13654,7 +13900,7 @@ var SessionIndex = class extends EventEmitter4 {
13654
13900
 
13655
13901
  // packages/server/src/settings-store.ts
13656
13902
  import { mkdir as mkdir9, readFile as readFile12 } from "node:fs/promises";
13657
- import path35 from "node:path";
13903
+ import path36 from "node:path";
13658
13904
 
13659
13905
  // packages/server/src/vault/write.ts
13660
13906
  import { randomBytes as randomBytes4 } from "node:crypto";
@@ -13662,15 +13908,15 @@ import { mkdir as mkdir8, rename as rename4, rm as rm3, stat as stat18, writeFil
13662
13908
 
13663
13909
  // packages/server/src/vault/paths.ts
13664
13910
  import { createHash as createHash2 } from "node:crypto";
13665
- import path34 from "node:path";
13911
+ import path35 from "node:path";
13666
13912
  function defaultVaultDir() {
13667
- return path34.join(appConfigDir(), "vault");
13913
+ return path35.join(appConfigDir(), "vault");
13668
13914
  }
13669
13915
  function vaultMarkerFile(dir) {
13670
- return path34.join(dir, "vault.json");
13916
+ return path35.join(dir, "vault.json");
13671
13917
  }
13672
13918
  function sessionsRoot2(dir) {
13673
- return path34.join(dir, "sessions");
13919
+ return path35.join(dir, "sessions");
13674
13920
  }
13675
13921
  function isSafeSessionId(sessionId) {
13676
13922
  return isVaultSessionId(sessionId);
@@ -13679,46 +13925,46 @@ function isSessionAgentId(value) {
13679
13925
  return SESSION_AGENT_IDS.includes(value);
13680
13926
  }
13681
13927
  function assertSessionKey(agent, sessionId) {
13682
- if (!isSessionAgentId(agent)) throw new Error(`Fuente desconocida para la copia: ${JSON.stringify(agent)}`);
13683
- if (!isSafeSessionId(sessionId)) throw new Error(`Id de sesion que no se copia: ${JSON.stringify(sessionId)}`);
13928
+ if (!isSessionAgentId(agent)) throw new Error(`Unknown source for the local copy: ${JSON.stringify(agent)}`);
13929
+ if (!isSafeSessionId(sessionId)) throw new Error(`Session id that isn't copied: ${JSON.stringify(sessionId)}`);
13684
13930
  }
13685
13931
  function agentSessionsDir(dir, agent) {
13686
- if (!isSessionAgentId(agent)) throw new Error(`Fuente desconocida para la copia: ${JSON.stringify(agent)}`);
13687
- return path34.join(sessionsRoot2(dir), agent);
13932
+ if (!isSessionAgentId(agent)) throw new Error(`Unknown source for the local copy: ${JSON.stringify(agent)}`);
13933
+ return path35.join(sessionsRoot2(dir), agent);
13688
13934
  }
13689
13935
  function sessionFile(dir, agent, sessionId) {
13690
13936
  assertSessionKey(agent, sessionId);
13691
- return path34.join(sessionsRoot2(dir), agent, `${sessionId}.jsonl`);
13937
+ return path35.join(sessionsRoot2(dir), agent, `${sessionId}.jsonl`);
13692
13938
  }
13693
13939
  function assetsDir(dir, agent, sessionId) {
13694
13940
  assertSessionKey(agent, sessionId);
13695
- return path34.join(sessionsRoot2(dir), agent, `${sessionId}.assets`);
13941
+ return path35.join(sessionsRoot2(dir), agent, `${sessionId}.assets`);
13696
13942
  }
13697
13943
  function assetFile(dir, agent, sessionId, asset) {
13698
- if (!VAULT_ASSET_NAME_PATTERN.test(asset)) throw new Error(`Nombre de asset invalido: ${JSON.stringify(asset)}`);
13699
- return path34.join(assetsDir(dir, agent, sessionId), asset);
13944
+ if (!VAULT_ASSET_NAME_PATTERN.test(asset)) throw new Error(`Invalid asset name: ${JSON.stringify(asset)}`);
13945
+ return path35.join(assetsDir(dir, agent, sessionId), asset);
13700
13946
  }
13701
13947
  function projectFolderName(cwd, platform) {
13702
13948
  const hash = createHash2("sha256").update(normalizeCwdKey(cwd, platform)).digest("hex").slice(0, 8);
13703
- const base = (platform === "win32" ? path34.win32 : path34.posix).basename(cwd);
13949
+ const base = (platform === "win32" ? path35.win32 : path35.posix).basename(cwd);
13704
13950
  const name = base.length === 0 ? "proyecto" : sanitizeFileName(base, 60);
13705
13951
  return `${name}-${hash}`;
13706
13952
  }
13707
13953
  function memoryDir(dir, cwd, platform) {
13708
- return path34.join(dir, "memory", projectFolderName(cwd, platform));
13954
+ return path35.join(dir, "memory", projectFolderName(cwd, platform));
13709
13955
  }
13710
13956
  function projectExportDir(dir, cwd, platform) {
13711
- return path34.join(dir, "export", projectFolderName(cwd, platform));
13957
+ return path35.join(dir, "export", projectFolderName(cwd, platform));
13712
13958
  }
13713
13959
  function projectExportDirFor(dir, project, platform) {
13714
13960
  if (project.cwd.length > 0) return projectExportDir(dir, project.cwd, platform);
13715
13961
  const hash = createHash2("sha256").update(project.key).digest("hex").slice(0, 8);
13716
13962
  const name = project.fallbackName.length > 0 ? sanitizeFileName(project.fallbackName, 60) : "proyecto";
13717
- return path34.join(dir, "export", `${name}-${hash}`);
13963
+ return path35.join(dir, "export", `${name}-${hash}`);
13718
13964
  }
13719
13965
  function sessionExportFile(dir, agent, sessionId) {
13720
13966
  assertSessionKey(agent, sessionId);
13721
- return path34.join(dir, "export", "sesiones", `${agent}-${sessionId}.md`);
13967
+ return path35.join(dir, "export", "sesiones", `${agent}-${sessionId}.md`);
13722
13968
  }
13723
13969
  var RESERVED_WINDOWS_NAME = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])$/i;
13724
13970
  function sanitizeFileName(text, max = 80) {
@@ -13825,7 +14071,7 @@ function defaultAppSettings() {
13825
14071
  function isAbsoluteDir(dir, platform) {
13826
14072
  if (dir.length === 0 || dir.includes("\0")) return false;
13827
14073
  if (platform === "win32") return /^[A-Za-z]:[\\/]/.test(dir) || /^\\\\[^\\/]+[\\/][^\\/]+/.test(dir);
13828
- return path35.posix.isAbsolute(dir);
14074
+ return path36.posix.isAbsolute(dir);
13829
14075
  }
13830
14076
  function clampToolResultMaxChars(value) {
13831
14077
  if (typeof value !== "number" || !Number.isFinite(value)) return VAULT_TOOL_RESULT_DEFAULT_CHARS;
@@ -13842,7 +14088,7 @@ function parseAppSettings(value, platform, warn = () => void 0) {
13842
14088
  if (typeof dir === "string" && isAbsoluteDir(dir, platform)) {
13843
14089
  settings2.vault.dir = dir;
13844
14090
  } else if (dir !== null && dir !== void 0) {
13845
- warn(`[ajustes] la carpeta de la copia ${JSON.stringify(dir)} no es una ruta absoluta: se usa la de por defecto`);
14091
+ warn(`[settings] the local copy folder ${JSON.stringify(dir)} isn't an absolute path: using the default one`);
13846
14092
  }
13847
14093
  settings2.vault.toolResultMaxChars = clampToolResultMaxChars(vault["toolResultMaxChars"]);
13848
14094
  return settings2;
@@ -13876,13 +14122,13 @@ var SettingsStore = class {
13876
14122
  try {
13877
14123
  parsed = JSON.parse(raw);
13878
14124
  } catch {
13879
- this.log.warn("[ajustes] settings.json no es JSON valido: se usan los valores por defecto");
14125
+ this.log.warn("[settings] settings.json isn't valid JSON: using the defaults");
13880
14126
  this.current = frozen(defaultAppSettings());
13881
14127
  return;
13882
14128
  }
13883
14129
  const settings2 = parseAppSettings(parsed, this.platform, (message) => this.log.warn(message));
13884
14130
  if (settings2 === null) {
13885
- this.log.warn("[ajustes] settings.json es de otra version: se usan los valores por defecto");
14131
+ this.log.warn("[settings] settings.json is from another version: using the defaults");
13886
14132
  }
13887
14133
  this.current = frozen(settings2 ?? defaultAppSettings());
13888
14134
  }
@@ -13901,14 +14147,14 @@ var SettingsStore = class {
13901
14147
  if (vault.enabled !== void 0) next.vault.enabled = vault.enabled;
13902
14148
  if (vault.dir !== void 0) {
13903
14149
  if (vault.dir !== null && !isAbsoluteDir(vault.dir, this.platform)) {
13904
- throw new Error(`La carpeta de la copia tiene que ser una ruta absoluta: ${JSON.stringify(vault.dir)}`);
14150
+ throw new Error(`The local copy folder has to be an absolute path: ${JSON.stringify(vault.dir)}`);
13905
14151
  }
13906
14152
  next.vault.dir = vault.dir;
13907
14153
  }
13908
14154
  if (vault.toolResultMaxChars !== void 0) {
13909
14155
  next.vault.toolResultMaxChars = clampToolResultMaxChars(vault.toolResultMaxChars);
13910
14156
  }
13911
- await mkdir9(path35.dirname(this.filePath), { recursive: true });
14157
+ await mkdir9(path36.dirname(this.filePath), { recursive: true });
13912
14158
  await writeFileAtomic(this.filePath, `${JSON.stringify(next, null, 2)}
13913
14159
  `);
13914
14160
  this.current = frozen(next);
@@ -13924,39 +14170,48 @@ var STATUS_LINE_LABEL = "Status line";
13924
14170
  function statusLineStartupText(state) {
13925
14171
  switch (state) {
13926
14172
  case "active":
13927
- return "configurada";
14173
+ return "configured";
13928
14174
  case "missing":
13929
- return "sin configurar (sin estado ni medidor; se configura desde el medidor)";
14175
+ return "not configured (no status or meter; set it up from the meter)";
13930
14176
  case "other-command":
13931
- return "hay otra configurada (sin estado ni medidor)";
14177
+ return "another one is configured (no status or meter)";
13932
14178
  case "disabled":
13933
- return "desactivada con enabled: false";
14179
+ return "disabled with enabled: false";
13934
14180
  case "unreadable":
13935
- return "no pude leer su settings.json";
14181
+ return "couldn't read its settings.json";
13936
14182
  }
13937
14183
  }
13938
- var AVAILABLE_AGENTS_LABEL = "CLIs disponibles";
13939
- var HISTORY_LABEL = "Historial";
14184
+ var AVAILABLE_AGENTS_LABEL = "Available CLIs";
14185
+ var HISTORY_LABEL = "History";
14186
+ function missingText(text) {
14187
+ if (text.key !== "cliMissing") {
14188
+ const raw = text.params?.["text"];
14189
+ return typeof raw === "string" ? raw : text.key;
14190
+ }
14191
+ const command = String(text.params?.["command"] ?? "");
14192
+ const url = String(text.params?.["url"] ?? "");
14193
+ return `The "${command}" command wasn't found in the PATH. Agent Workbench uses the CLI you already have installed: it doesn't include or download it. Install it from ${url} and start again.`;
14194
+ }
13940
14195
  function startupAgentLines(agents) {
13941
14196
  const available = agents.filter((agent) => agent.resolvedPath !== null);
13942
14197
  const lines = [
13943
- ` ${AVAILABLE_AGENTS_LABEL} ${available.length === 0 ? "ninguna" : available.map((agent) => agent.id).join(", ")}`
14198
+ ` ${AVAILABLE_AGENTS_LABEL} ${available.length === 0 ? "none" : available.map((agent) => agent.id).join(", ")}`
13944
14199
  ];
13945
14200
  for (const agent of available) {
13946
14201
  const title = available.length === 1 ? "CLI " : `CLI (${agent.label}) `;
13947
- lines.push(` ${title}${agent.version ?? "version desconocida"}`);
13948
- lines.push(` Binario ${agent.resolvedPath ?? ""}`);
14202
+ lines.push(` ${title}${agent.version ?? "unknown version"}`);
14203
+ lines.push(` Binary ${agent.resolvedPath ?? ""}`);
13949
14204
  const note = agent.historyNote ?? null;
13950
- if (note !== null) lines.push(` ${HISTORY_LABEL} ${note}`);
14205
+ if (note !== null) lines.push(` ${HISTORY_LABEL} ${note}`);
13951
14206
  const statusLine = agent.statusLine ?? null;
13952
14207
  if (statusLine !== null) lines.push(` ${STATUS_LINE_LABEL} ${statusLineStartupText(statusLine)}`);
13953
14208
  }
13954
14209
  const first = agents[0];
13955
14210
  if (available.length === 0 && first !== void 0) {
13956
- lines.push(" CLI NO ENCONTRADA");
13957
- if (first.missingMessage !== null) lines.push("", ` ${first.missingMessage}`);
14211
+ lines.push(" CLI NOT FOUND");
14212
+ if (first.missingMessage !== null) lines.push("", ` ${missingText(first.missingMessage)}`);
13958
14213
  const others = agents.slice(1).map((agent) => agent.label);
13959
- if (others.length > 0) lines.push(` Tambien funciona con: ${others.join(", ")}`);
14214
+ if (others.length > 0) lines.push(` Also works with: ${others.join(", ")}`);
13960
14215
  }
13961
14216
  for (const agent of agents) {
13962
14217
  const note = agent.historyNote ?? null;
@@ -13968,10 +14223,10 @@ function startupAgentLines(agents) {
13968
14223
  // packages/server/src/notes-store.ts
13969
14224
  import { randomUUID as randomUUID3 } from "node:crypto";
13970
14225
  import { mkdir as mkdir10, readFile as readFile13, rename as rename5, rm as rm4, writeFile as writeFile6 } from "node:fs/promises";
13971
- import path36 from "node:path";
14226
+ import path37 from "node:path";
13972
14227
  var STATE_VERSION2 = 1;
13973
14228
  var WRITE_DEBOUNCE_MS2 = 500;
13974
- var NotesError = class extends Error {
14229
+ var NotesError = class extends ServerTextError {
13975
14230
  };
13976
14231
  function parseState2(raw) {
13977
14232
  try {
@@ -14031,10 +14286,10 @@ var NotesStore = class {
14031
14286
  * Un id repetido no crea nada; el cliente ya tiene esa nota.
14032
14287
  */
14033
14288
  create(noteId, now = Date.now()) {
14034
- if (!isValidNoteId(noteId)) throw new NotesError("El id de la nota no es valido.");
14035
- if (this.get(noteId) !== null) throw new NotesError("Esa nota ya existe.");
14289
+ if (!isValidNoteId(noteId)) throw new NotesError(serverText("noteIdInvalid"));
14290
+ if (this.get(noteId) !== null) throw new NotesError(serverText("noteExists"));
14036
14291
  if (this.notes.length >= MAX_NOTES) {
14037
- throw new NotesError(`Hasta ${MAX_NOTES} notas abiertas. Cerra alguna primero.`);
14292
+ throw new NotesError(serverText("notesMax", { max: MAX_NOTES }));
14038
14293
  }
14039
14294
  const note = { noteId, text: "", images: [], createdAt: now, updatedAt: now };
14040
14295
  this.notes.push(note);
@@ -14044,9 +14299,9 @@ var NotesStore = class {
14044
14299
  /** Devuelve true si el texto cambio de verdad. */
14045
14300
  update(noteId, text, now = Date.now()) {
14046
14301
  const note = this.get(noteId);
14047
- if (note === null) throw new NotesError("La nota ya no existe.");
14302
+ if (note === null) throw new NotesError(serverText("noteGone"));
14048
14303
  if (text.length > MAX_NOTE_TEXT_CHARS) {
14049
- throw new NotesError(`Una nota admite hasta ${MAX_NOTE_TEXT_CHARS} caracteres.`);
14304
+ throw new NotesError(serverText("noteTooLong", { max: MAX_NOTE_TEXT_CHARS }));
14050
14305
  }
14051
14306
  if (note.text === text) return false;
14052
14307
  note.text = text;
@@ -14073,23 +14328,26 @@ var NotesStore = class {
14073
14328
  */
14074
14329
  async addImage(noteId, declaredType, base64, now = Date.now()) {
14075
14330
  const note = this.get(noteId);
14076
- if (note === null) throw new NotesError("La nota ya no existe.");
14331
+ if (note === null) throw new NotesError(serverText("noteGone"));
14077
14332
  if (note.images.length >= MAX_NOTE_IMAGES) {
14078
- throw new NotesError(`Hasta ${MAX_NOTE_IMAGES} imagenes por nota.`);
14333
+ throw new NotesError(serverText("noteImagesMax", { max: MAX_NOTE_IMAGES }));
14079
14334
  }
14080
14335
  if (!IMAGE_MEDIA_TYPES.has(declaredType)) {
14081
- throw new NotesError(`Tipo de imagen no admitido: ${declaredType}`);
14336
+ throw new NotesError(serverText("imageTypeUnsupported", { type: declaredType }));
14082
14337
  }
14083
14338
  const bytes = Buffer.from(base64, "base64");
14084
- if (bytes.length === 0) throw new NotesError("La imagen llego vacia.");
14339
+ if (bytes.length === 0) throw new NotesError(serverText("imageEmpty"));
14085
14340
  if (bytes.length > MAX_SUBMIT_IMAGE_BYTES) {
14086
14341
  throw new NotesError(
14087
- `La imagen pesa ${Math.round(bytes.length / 1024 / 1024)} MB; el maximo son ${MAX_SUBMIT_IMAGE_BYTES / 1024 / 1024} MB.`
14342
+ serverText("imageTooLarge", {
14343
+ size: Math.round(bytes.length / 1024 / 1024),
14344
+ max: MAX_SUBMIT_IMAGE_BYTES / 1024 / 1024
14345
+ })
14088
14346
  );
14089
14347
  }
14090
14348
  const format = detectImageFormat(bytes);
14091
14349
  if (format === null) {
14092
- throw new NotesError("El contenido no es una imagen de un formato conocido.");
14350
+ throw new NotesError(serverText("imageUnknownFormat"));
14093
14351
  }
14094
14352
  const image = {
14095
14353
  imageId: randomUUID3(),
@@ -14169,7 +14427,7 @@ var NotesStore = class {
14169
14427
  }
14170
14428
  imagePath(image) {
14171
14429
  const safe = image.imageId.replace(/[^a-zA-Z0-9-]/g, "");
14172
- return path36.join(this.imagesDir, `${safe}.${extensionFor(image.mediaType)}`);
14430
+ return path37.join(this.imagesDir, `${safe}.${extensionFor(image.mediaType)}`);
14173
14431
  }
14174
14432
  async unlinkImage(image) {
14175
14433
  await rm4(this.imagePath(image), { force: true }).catch(() => {
@@ -14187,8 +14445,8 @@ var NotesStore = class {
14187
14445
  async writeNow() {
14188
14446
  this.dirty = false;
14189
14447
  try {
14190
- await mkdir10(path36.dirname(this.statePath), { recursive: true });
14191
- const temporary = path36.join(path36.dirname(this.statePath), `notes.${process.pid}.tmp`);
14448
+ await mkdir10(path37.dirname(this.statePath), { recursive: true });
14449
+ const temporary = path37.join(path37.dirname(this.statePath), `notes.${process.pid}.tmp`);
14192
14450
  await writeFile6(
14193
14451
  temporary,
14194
14452
  JSON.stringify({ version: STATE_VERSION2, notes: this.notes }, null, 2),
@@ -14196,7 +14454,7 @@ var NotesStore = class {
14196
14454
  );
14197
14455
  await rename5(temporary, this.statePath);
14198
14456
  } catch (error) {
14199
- console.warn("[notas] no se pudieron guardar:", error);
14457
+ console.warn("[notes] couldn't save the notes:", error);
14200
14458
  }
14201
14459
  }
14202
14460
  };
@@ -14204,7 +14462,7 @@ var NotesStore = class {
14204
14462
  // packages/server/src/shell-locator.ts
14205
14463
  import { access as access2 } from "node:fs/promises";
14206
14464
  import { constants as fsConstants2 } from "node:fs";
14207
- import path37 from "node:path";
14465
+ import path38 from "node:path";
14208
14466
  async function isExecutable(candidate) {
14209
14467
  try {
14210
14468
  await access2(candidate, process.platform === "win32" ? fsConstants2.R_OK : fsConstants2.X_OK);
@@ -14216,7 +14474,7 @@ async function isExecutable(candidate) {
14216
14474
  function windowsPowerShellPath() {
14217
14475
  const systemRoot = process.env["SystemRoot"] ?? process.env["windir"];
14218
14476
  if (systemRoot === void 0 || systemRoot.length === 0) return null;
14219
- return path37.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
14477
+ return path38.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
14220
14478
  }
14221
14479
  async function locateWindowsShell() {
14222
14480
  const pwsh = await findInPath("pwsh");
@@ -14236,7 +14494,7 @@ async function locateWindowsShell() {
14236
14494
  async function locatePosixShell() {
14237
14495
  const preferred = process.env["SHELL"];
14238
14496
  if (preferred !== void 0 && preferred.length > 0 && await isExecutable(preferred)) {
14239
- return { file: preferred, args: [], name: path37.basename(preferred) };
14497
+ return { file: preferred, args: [], name: path38.basename(preferred) };
14240
14498
  }
14241
14499
  if (await isExecutable("/bin/bash")) return { file: "/bin/bash", args: [], name: "bash" };
14242
14500
  if (await isExecutable("/bin/sh")) return { file: "/bin/sh", args: [], name: "sh" };
@@ -14283,7 +14541,7 @@ function watchSessions(index, hub, agents, options = {}) {
14283
14541
  try {
14284
14542
  stops.push(watch((filePath) => onFileEvent(agent, root, filePath)));
14285
14543
  } catch (error) {
14286
- console.warn("[watcher] no se pudo observar el historial:", error);
14544
+ console.warn("[watcher] couldn't watch the history:", error);
14287
14545
  }
14288
14546
  };
14289
14547
  const watchRoot = (agent, root, announceExisting) => {
@@ -14298,7 +14556,7 @@ function watchSessions(index, hub, agents, options = {}) {
14298
14556
  ...root.ignore !== void 0 ? { ignored: root.ignore } : {}
14299
14557
  });
14300
14558
  } catch (error) {
14301
- console.warn("[watcher] no se pudo observar el historial:", error);
14559
+ console.warn("[watcher] couldn't watch the history:", error);
14302
14560
  return;
14303
14561
  }
14304
14562
  watcher.on("add", onRootEvent);
@@ -14347,7 +14605,7 @@ function watchSessions(index, hub, agents, options = {}) {
14347
14605
  try {
14348
14606
  stop();
14349
14607
  } catch (error) {
14350
- console.warn("[watcher] no se pudo dejar de observar el historial:", error);
14608
+ console.warn("[watcher] couldn't stop watching the history:", error);
14351
14609
  }
14352
14610
  }
14353
14611
  };
@@ -14595,12 +14853,11 @@ function wakeActionFor(state) {
14595
14853
  }
14596
14854
 
14597
14855
  // packages/server/src/terminal-open-error.ts
14598
- var TerminalOpenError = class extends Error {
14599
- constructor(code, message, detail) {
14600
- super(message);
14856
+ var TerminalOpenError = class extends ServerTextError {
14857
+ constructor(code, text, detail) {
14858
+ super(text);
14601
14859
  this.code = code;
14602
14860
  this.detail = detail;
14603
- this.name = "TerminalOpenError";
14604
14861
  }
14605
14862
  code;
14606
14863
  detail;
@@ -14612,7 +14869,7 @@ async function resolveLaunchPlan(launch) {
14612
14869
  if (error instanceof TerminalOpenError) throw error;
14613
14870
  throw new TerminalOpenError(
14614
14871
  "spawn-failed",
14615
- "No se pudo abrir la terminal.",
14872
+ serverText("terminalOpenFailed"),
14616
14873
  error instanceof Error ? error.message : String(error)
14617
14874
  );
14618
14875
  }
@@ -14620,7 +14877,7 @@ async function resolveLaunchPlan(launch) {
14620
14877
 
14621
14878
  // packages/server/src/workspace-store.ts
14622
14879
  import { mkdir as mkdir11, readFile as readFile14, writeFile as writeFile7 } from "node:fs/promises";
14623
- import path38 from "node:path";
14880
+ import path39 from "node:path";
14624
14881
  var STATE_VERSION3 = 1;
14625
14882
  var WRITE_DEBOUNCE_MS3 = 400;
14626
14883
  var LEGACY_TAB_AGENT = "claude-code";
@@ -14670,7 +14927,7 @@ function readTab(tab, missingAgent) {
14670
14927
  agent = asLiteral(tab["agent"], AGENT_IDS);
14671
14928
  if (agent === null) {
14672
14929
  console.warn(
14673
- `[workspace] no se restaura la pestana de ${cwd}: CLI desconocida ${JSON.stringify(tab["agent"])}. Se conserva en el archivo.`
14930
+ `[workspace] the tab of ${cwd} isn't restored: unknown CLI ${JSON.stringify(tab["agent"])}. It's kept in the file.`
14674
14931
  );
14675
14932
  return { kind: "foreign", raw: tab };
14676
14933
  }
@@ -14774,12 +15031,12 @@ var WorkspaceStore = class {
14774
15031
  try {
14775
15032
  await mkdir11(appConfigDir(), { recursive: true });
14776
15033
  const target = workspaceStatePath();
14777
- const temporary = path38.join(path38.dirname(target), `workspace.${process.pid}.tmp`);
15034
+ const temporary = path39.join(path39.dirname(target), `workspace.${process.pid}.tmp`);
14778
15035
  await writeFile7(temporary, JSON.stringify(serializeState(state), null, 2), "utf8");
14779
15036
  const { rename: rename6 } = await import("node:fs/promises");
14780
15037
  await rename6(temporary, target);
14781
15038
  } catch (error) {
14782
- console.warn("[workspace] no se pudo guardar el estado:", error);
15039
+ console.warn("[workspace] couldn't save the state:", error);
14783
15040
  }
14784
15041
  }
14785
15042
  };
@@ -14839,7 +15096,7 @@ var TerminalRegistry = class extends EventEmitter5 {
14839
15096
  if (registered === null || registered.location === null) {
14840
15097
  throw new TerminalOpenError(
14841
15098
  "cli-not-found",
14842
- "La CLI no esta instalada o no se encontro en el PATH."
15099
+ serverText("cliNotFound")
14843
15100
  );
14844
15101
  }
14845
15102
  return {
@@ -14850,7 +15107,7 @@ var TerminalRegistry = class extends EventEmitter5 {
14850
15107
  if (this.shell === null) {
14851
15108
  throw new TerminalOpenError(
14852
15109
  "shell-not-found",
14853
- "No se encontro ninguna consola del sistema para abrir."
15110
+ serverText("shellNotFound")
14854
15111
  );
14855
15112
  }
14856
15113
  return { kind: "shell", shell: this.shell };
@@ -14872,7 +15129,7 @@ var TerminalRegistry = class extends EventEmitter5 {
14872
15129
  if (kind === "agent" && options.agent !== void 0 && this.agents.get(options.agent) === null) {
14873
15130
  throw new TerminalOpenError(
14874
15131
  "agent-unsupported",
14875
- "Este servidor no sabe lanzar esa CLI.",
15132
+ serverText("agentUnknown"),
14876
15133
  options.agent
14877
15134
  );
14878
15135
  }
@@ -14889,7 +15146,7 @@ var TerminalRegistry = class extends EventEmitter5 {
14889
15146
  if (this.terminals.size >= MAX_TERMINALS) {
14890
15147
  throw new TerminalOpenError(
14891
15148
  "too-many-terminals",
14892
- `No se pueden abrir mas de ${MAX_TERMINALS} pestanas a la vez.`
15149
+ serverText("tooManyTabs", { max: MAX_TERMINALS })
14893
15150
  );
14894
15151
  }
14895
15152
  await this.assertDirectory(options.cwd);
@@ -14926,7 +15183,7 @@ var TerminalRegistry = class extends EventEmitter5 {
14926
15183
  try {
14927
15184
  const spawned = await this.spawn(entry, { resume: resumed });
14928
15185
  if (!spawned) {
14929
- throw new TerminalOpenError("spawn-failed", "La pesta\xF1a se cerr\xF3 mientras se abr\xEDa.");
15186
+ throw new TerminalOpenError("spawn-failed", serverText("tabClosedWhileOpening"));
14930
15187
  }
14931
15188
  } catch (error) {
14932
15189
  if (this.terminals.get(terminalId) === entry) this.terminals.delete(terminalId);
@@ -14959,7 +15216,7 @@ var TerminalRegistry = class extends EventEmitter5 {
14959
15216
  entry.launching = true;
14960
15217
  try {
14961
15218
  if (action === "restart" && !await this.endSessionForRestart(entry)) {
14962
- throw new TerminalOpenError("spawn-failed", "No se pudo cerrar la CLI de la pesta\xF1a para relanzarla.");
15219
+ throw new TerminalOpenError("spawn-failed", serverText("relaunchCloseFailed"));
14963
15220
  }
14964
15221
  if (this.terminals.get(terminalId) !== entry) return null;
14965
15222
  await this.assertDirectory(entry.descriptor.cwd);
@@ -14997,7 +15254,7 @@ var TerminalRegistry = class extends EventEmitter5 {
14997
15254
  resolve(entry.session !== session);
14998
15255
  }, RESTART_EXIT_TIMEOUT_MS);
14999
15256
  this.on("exit", onExit);
15000
- debugLog("registro", `relanzando ${terminalId.slice(0, 8)}: su servidor se cerro`);
15257
+ debugLog("registry", `relaunching ${terminalId.slice(0, 8)}: its server closed`);
15001
15258
  session.dispose();
15002
15259
  });
15003
15260
  }
@@ -15007,10 +15264,10 @@ var TerminalRegistry = class extends EventEmitter5 {
15007
15264
  try {
15008
15265
  info = await stat20(cwd);
15009
15266
  } catch {
15010
- throw new TerminalOpenError("invalid-cwd", `El directorio no existe: ${cwd}`);
15267
+ throw new TerminalOpenError("invalid-cwd", serverText("cwdMissing", { cwd }));
15011
15268
  }
15012
15269
  if (!info.isDirectory()) {
15013
- throw new TerminalOpenError("invalid-cwd", `No es un directorio: ${cwd}`);
15270
+ throw new TerminalOpenError("invalid-cwd", serverText("cwdNotDir", { cwd }));
15014
15271
  }
15015
15272
  }
15016
15273
  /**
@@ -15069,7 +15326,7 @@ var TerminalRegistry = class extends EventEmitter5 {
15069
15326
  const listenerCount = target?.listeners.size ?? 0;
15070
15327
  debugLog(
15071
15328
  "pty",
15072
- `salida ${chunk.length} bytes de ${terminalId.slice(0, 8)}, ${listenerCount} oyentes`
15329
+ `output ${chunk.length} bytes from ${terminalId.slice(0, 8)}, ${listenerCount} listeners`
15073
15330
  );
15074
15331
  if (target !== void 0) {
15075
15332
  for (const listener of target.listeners) listener(terminalId, chunk);
@@ -15098,7 +15355,7 @@ var TerminalRegistry = class extends EventEmitter5 {
15098
15355
  } catch (error) {
15099
15356
  throw new TerminalOpenError(
15100
15357
  "spawn-failed",
15101
- "No se pudo abrir la terminal.",
15358
+ serverText("terminalOpenFailed"),
15102
15359
  error instanceof Error ? error.message : String(error)
15103
15360
  );
15104
15361
  }
@@ -15134,7 +15391,7 @@ var TerminalRegistry = class extends EventEmitter5 {
15134
15391
  finished = true;
15135
15392
  const target = this.terminals.get(terminalId);
15136
15393
  if (target !== void 0 && hook !== null) target.launchHook.forget(hook);
15137
- debugLog("registro", `dialogo de reanudar en ${terminalId.slice(0, 8)}: ${outcome}`);
15394
+ debugLog("registry", `resume dialog in ${terminalId.slice(0, 8)}: ${outcome}`);
15138
15395
  },
15139
15396
  reportSessionId: (discovered) => this.reportSessionId(terminalId, discovered)
15140
15397
  });
@@ -15256,7 +15513,7 @@ var TerminalRegistry = class extends EventEmitter5 {
15256
15513
  const { tab } = saved;
15257
15514
  if ((this.agents.get(tab.agent)?.location ?? null) === null) {
15258
15515
  console.warn(
15259
- `[workspace] no se restauro la pestana de ${tab.cwd}: su CLI no esta disponible. Se conserva para cuando vuelva.`
15516
+ `[workspace] the tab of ${tab.cwd} wasn't restored: its CLI isn't available. It's kept for when it's back.`
15260
15517
  );
15261
15518
  this.unavailableTabs.push({ position, tab });
15262
15519
  continue;
@@ -15266,7 +15523,7 @@ var TerminalRegistry = class extends EventEmitter5 {
15266
15523
  await this.assertDirectory(tab.cwd);
15267
15524
  } catch (error) {
15268
15525
  const reason2 = error instanceof Error ? error.message : String(error);
15269
- console.warn(`[workspace] no se restauro la pestana de ${tab.cwd}: ${reason2}`);
15526
+ console.warn(`[workspace] the tab of ${tab.cwd} wasn't restored: ${reason2}`);
15270
15527
  continue;
15271
15528
  }
15272
15529
  const terminalId = randomUUID4();
@@ -15347,8 +15604,8 @@ var TerminalRegistry = class extends EventEmitter5 {
15347
15604
  entry.listeners.add(listener);
15348
15605
  const replay = entry.buffer.read();
15349
15606
  debugLog(
15350
- "registro",
15351
- `attach ${terminalId.slice(0, 8)}: replay de ${replay.length} bytes, ${entry.listeners.size} oyentes`
15607
+ "registry",
15608
+ `attach ${terminalId.slice(0, 8)}: replay of ${replay.length} bytes, ${entry.listeners.size} listeners`
15352
15609
  );
15353
15610
  return { replay, truncated: entry.buffer.isTruncated() };
15354
15611
  }
@@ -15434,17 +15691,17 @@ import { WebSocketServer } from "ws";
15434
15691
  import { randomUUID as randomUUID5 } from "node:crypto";
15435
15692
  import { mkdir as mkdir12, readdir as readdir11, stat as stat21 } from "node:fs/promises";
15436
15693
  import { homedir as homedir11 } from "node:os";
15437
- import path39 from "node:path";
15694
+ import path40 from "node:path";
15438
15695
  var MAX_ENTRIES = 500;
15439
15696
  var MAX_PICKERS = 8;
15440
15697
  var SKIPPED = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "bin", "obj", "$RECYCLE.BIN"]);
15441
- var DirectoryPickerError = class extends Error {
15698
+ var DirectoryPickerError = class extends ServerTextError {
15442
15699
  };
15443
15700
  function isInsideProtected(target, dirs) {
15444
- const resolved = path39.resolve(target);
15701
+ const resolved = path40.resolve(target);
15445
15702
  return dirs.some((dir) => {
15446
- const protectedDir = path39.resolve(dir);
15447
- return resolved === protectedDir || resolved.startsWith(protectedDir + path39.sep);
15703
+ const protectedDir = path40.resolve(dir);
15704
+ return resolved === protectedDir || resolved.startsWith(protectedDir + path40.sep);
15448
15705
  });
15449
15706
  }
15450
15707
  async function listRoots() {
@@ -15481,7 +15738,7 @@ var DirectoryPickers = class {
15481
15738
  /** Abre un selector en el directorio del usuario. */
15482
15739
  async open() {
15483
15740
  if (this.pickers.size >= MAX_PICKERS) {
15484
- throw new DirectoryPickerError("Hay demasiados selectores abiertos.");
15741
+ throw new DirectoryPickerError(serverText("pickerTooMany"));
15485
15742
  }
15486
15743
  const picker = { id: randomUUID5(), current: homedir11(), entries: /* @__PURE__ */ new Set() };
15487
15744
  this.pickers.set(picker.id, picker);
@@ -15508,16 +15765,16 @@ var DirectoryPickers = class {
15508
15765
  async enter(pickerId, name) {
15509
15766
  const picker = this.require(pickerId);
15510
15767
  if (name === "..") {
15511
- const parent = path39.dirname(picker.current);
15768
+ const parent = path40.dirname(picker.current);
15512
15769
  if (parent !== picker.current) picker.current = parent;
15513
15770
  return this.listing(picker);
15514
15771
  }
15515
15772
  if (!picker.entries.has(name)) {
15516
- throw new DirectoryPickerError("Esa carpeta no esta en el listado actual.");
15773
+ throw new DirectoryPickerError(serverText("pickerNotListed"));
15517
15774
  }
15518
- const target = path39.join(picker.current, name);
15775
+ const target = path40.join(picker.current, name);
15519
15776
  if (isInsideProtected(target, this.protectedDirs)) {
15520
- throw new DirectoryPickerError("Esa carpeta es de la CLI y no se abre desde aca.");
15777
+ throw new DirectoryPickerError(serverText("pickerCliFolder"));
15521
15778
  }
15522
15779
  picker.current = target;
15523
15780
  return this.listing(picker);
@@ -15527,7 +15784,7 @@ var DirectoryPickers = class {
15527
15784
  const picker = this.require(pickerId);
15528
15785
  const roots = await this.knownRoots();
15529
15786
  const target = roots[index];
15530
- if (target === void 0) throw new DirectoryPickerError("Esa raiz no existe.");
15787
+ if (target === void 0) throw new DirectoryPickerError(serverText("pickerRootMissing"));
15531
15788
  picker.current = target;
15532
15789
  return this.listing(picker);
15533
15790
  }
@@ -15542,29 +15799,29 @@ var DirectoryPickers = class {
15542
15799
  const picker = this.require(pickerId);
15543
15800
  const clean = name.trim();
15544
15801
  if (clean.length === 0 || clean.length > 120) {
15545
- throw new DirectoryPickerError("El nombre de la carpeta no es valido.");
15802
+ throw new DirectoryPickerError(serverText("pickerNameInvalid"));
15546
15803
  }
15547
15804
  if (clean === "." || clean === "..") {
15548
- throw new DirectoryPickerError("El nombre de la carpeta no es valido.");
15805
+ throw new DirectoryPickerError(serverText("pickerNameInvalid"));
15549
15806
  }
15550
15807
  if (/[\\/:*?"<>|]/.test(clean)) {
15551
- throw new DirectoryPickerError("El nombre tiene caracteres que no se pueden usar.");
15808
+ throw new DirectoryPickerError(serverText("pickerNameChars"));
15552
15809
  }
15553
15810
  for (const character of clean) {
15554
15811
  if ((character.codePointAt(0) ?? 0) < 32) {
15555
- throw new DirectoryPickerError("El nombre tiene caracteres que no se pueden usar.");
15812
+ throw new DirectoryPickerError(serverText("pickerNameChars"));
15556
15813
  }
15557
15814
  }
15558
- const target = path39.join(picker.current, clean);
15815
+ const target = path40.join(picker.current, clean);
15559
15816
  if (isInsideProtected(target, this.protectedDirs)) {
15560
- throw new DirectoryPickerError("Ahi no se crean proyectos: es la carpeta de la CLI.");
15817
+ throw new DirectoryPickerError(serverText("pickerNoProjectsHere"));
15561
15818
  }
15562
15819
  try {
15563
15820
  await mkdir12(target);
15564
15821
  } catch (error) {
15565
15822
  const code = error.code;
15566
15823
  throw new DirectoryPickerError(
15567
- code === "EEXIST" ? "Ya hay una carpeta con ese nombre." : "No se pudo crear la carpeta ahi."
15824
+ code === "EEXIST" ? serverText("pickerFolderExists") : serverText("pickerCreateFailed")
15568
15825
  );
15569
15826
  }
15570
15827
  picker.current = target;
@@ -15572,7 +15829,7 @@ var DirectoryPickers = class {
15572
15829
  }
15573
15830
  require(pickerId) {
15574
15831
  const picker = this.pickers.get(pickerId);
15575
- if (picker === void 0) throw new DirectoryPickerError("Ese selector ya no esta abierto.");
15832
+ if (picker === void 0) throw new DirectoryPickerError(serverText("pickerClosed"));
15576
15833
  return picker;
15577
15834
  }
15578
15835
  async knownRoots() {
@@ -15592,7 +15849,7 @@ var DirectoryPickers = class {
15592
15849
  let truncated = false;
15593
15850
  try {
15594
15851
  const found = await readdir11(picker.current, { withFileTypes: true });
15595
- const dirs = found.filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => !SKIPPED.has(name)).filter((name) => !isInsideProtected(path39.join(picker.current, name), this.protectedDirs)).sort((a, b) => a.localeCompare(b));
15852
+ const dirs = found.filter((entry) => entry.isDirectory()).map((entry) => entry.name).filter((name) => !SKIPPED.has(name)).filter((name) => !isInsideProtected(path40.join(picker.current, name), this.protectedDirs)).sort((a, b) => a.localeCompare(b));
15596
15853
  truncated = dirs.length > MAX_ENTRIES;
15597
15854
  names = dirs.slice(0, MAX_ENTRIES);
15598
15855
  } catch {
@@ -15603,7 +15860,7 @@ var DirectoryPickers = class {
15603
15860
  path: picker.current,
15604
15861
  entries: names,
15605
15862
  roots,
15606
- canGoUp: path39.dirname(picker.current) !== picker.current,
15863
+ canGoUp: path40.dirname(picker.current) !== picker.current,
15607
15864
  truncated
15608
15865
  };
15609
15866
  }
@@ -15611,7 +15868,7 @@ var DirectoryPickers = class {
15611
15868
 
15612
15869
  // packages/server/src/file-browser.ts
15613
15870
  import { readFile as readFile15, readdir as readdir12, stat as stat22 } from "node:fs/promises";
15614
- import path40 from "node:path";
15871
+ import path41 from "node:path";
15615
15872
  var MAX_ENTRIES2 = 1e3;
15616
15873
  var SEARCH_MAX_RESULTS = 200;
15617
15874
  var SEARCH_MAX_DIRECTORIES = 4e3;
@@ -15674,7 +15931,7 @@ function languageFor(fileName) {
15674
15931
  const lower = fileName.toLowerCase();
15675
15932
  const byName = LANGUAGE_BY_NAME[lower];
15676
15933
  if (byName !== void 0) return byName;
15677
- const extension = path40.extname(lower);
15934
+ const extension = path41.extname(lower);
15678
15935
  return LANGUAGE_BY_EXTENSION[extension] ?? null;
15679
15936
  }
15680
15937
  function looksBinary2(buffer) {
@@ -15706,7 +15963,7 @@ async function listDirectory(cwd, relativePath, options = {}) {
15706
15963
  let sizeBytes = 0;
15707
15964
  let modifiedAt = 0;
15708
15965
  try {
15709
- const info = await stat22(path40.join(absolute, candidate.name));
15966
+ const info = await stat22(path41.join(absolute, candidate.name));
15710
15967
  sizeBytes = candidate.isDirectory ? 0 : info.size;
15711
15968
  modifiedAt = info.mtimeMs;
15712
15969
  } catch {
@@ -15739,7 +15996,7 @@ async function readLevel(absolute) {
15739
15996
  const always = ALWAYS_SKIP.has(dirent.name.toLowerCase());
15740
15997
  if (dirent.isSymbolicLink()) {
15741
15998
  try {
15742
- const target = await stat22(path40.join(absolute, dirent.name));
15999
+ const target = await stat22(path41.join(absolute, dirent.name));
15743
16000
  candidates.push({
15744
16001
  name: dirent.name,
15745
16002
  isDirectory: target.isDirectory(),
@@ -15768,7 +16025,7 @@ async function markIgnored(repoRoot2, levels) {
15768
16025
  for (const level of levels) {
15769
16026
  for (const entry of level.entries) {
15770
16027
  if (entry.hidden !== null) continue;
15771
- keys.set(entry, toPosixPath(path40.relative(repoRoot2, path40.join(level.absolute, entry.name))));
16028
+ keys.set(entry, toPosixPath(path41.relative(repoRoot2, path41.join(level.absolute, entry.name))));
15772
16029
  }
15773
16030
  }
15774
16031
  if (keys.size === 0) return;
@@ -15829,7 +16086,7 @@ async function searchFiles(cwd, query, options = {}) {
15829
16086
  let sizeBytes = 0;
15830
16087
  let modifiedAt = 0;
15831
16088
  try {
15832
- const info = await stat22(path40.join(level.absolute, candidate.name));
16089
+ const info = await stat22(path41.join(level.absolute, candidate.name));
15833
16090
  sizeBytes = candidate.isDirectory ? 0 : info.size;
15834
16091
  modifiedAt = info.mtimeMs;
15835
16092
  } catch {
@@ -15865,10 +16122,10 @@ async function readPreview(cwd, relativePath) {
15865
16122
  const absolute = await resolveInside(cwd, relativePath, { mustExist: true });
15866
16123
  const info = await stat22(absolute);
15867
16124
  if (info.isDirectory()) {
15868
- throw new Error("Es un directorio, no un archivo.");
16125
+ throw new Error("It's a directory, not a file.");
15869
16126
  }
15870
16127
  const content = await readFile15(absolute);
15871
- const fileName = path40.basename(absolute);
16128
+ const fileName = path41.basename(absolute);
15872
16129
  if (looksBinary2(content)) {
15873
16130
  return {
15874
16131
  path: relativePath,
@@ -15899,7 +16156,7 @@ function yieldToEventLoop() {
15899
16156
  }
15900
16157
  var GlobalSearchAbortedError = class extends Error {
15901
16158
  constructor() {
15902
- super("B\xFAsqueda cancelada.");
16159
+ super("Search cancelled.");
15903
16160
  this.name = "GlobalSearchAbortedError";
15904
16161
  }
15905
16162
  };
@@ -16179,13 +16436,13 @@ var MAX_POLLS = 5;
16179
16436
  var MAX_TOTAL_POLLS = 20;
16180
16437
  var UnsupportedHistoryError = class extends Error {
16181
16438
  constructor(label) {
16182
- super(`${label}: la fuente no declara que su seguidor respete los topes de la copia (wholeRead)`);
16439
+ super(`${label}: the source doesn't declare that its follower respects the local copy's limits (wholeRead)`);
16183
16440
  this.name = "UnsupportedHistoryError";
16184
16441
  }
16185
16442
  };
16186
16443
  var PagedHistoryError = class extends Error {
16187
16444
  constructor(label, events) {
16188
- super(`${label}: el seguidor pagino con maxEvents infinito (${events} eventos y hay mas)`);
16445
+ super(`${label}: the follower paged with infinite maxEvents (${events} ${events === 1 ? "event" : "events"} and more left)`);
16189
16446
  this.name = "PagedHistoryError";
16190
16447
  }
16191
16448
  };
@@ -16257,32 +16514,32 @@ async function readSourceEvents(history, target, maxEvents = HANDOFF_MAX_EVENTS)
16257
16514
  }
16258
16515
  var messageOf = (error) => error instanceof Error ? error.message : String(error);
16259
16516
  async function continueSession(deps, request) {
16260
- const failed = (message2, detail) => ({
16517
+ const failed = (text, detail) => ({
16261
16518
  ok: false,
16262
16519
  code: "continue-failed",
16263
- message: message2,
16520
+ text,
16264
16521
  ...detail !== void 0 ? { detail } : {}
16265
16522
  });
16266
16523
  const target = deps.agents.get(request.target);
16267
16524
  if (target === null) {
16268
- return { ok: false, code: "agent-unsupported", message: "Este servidor no sabe lanzar esa CLI.", detail: request.target };
16525
+ return { ok: false, code: "agent-unsupported", text: serverText("agentUnknown"), detail: request.target };
16269
16526
  }
16270
16527
  if (target.location === null) {
16271
- return { ok: false, code: "cli-not-found", message: `${target.adapter.label} no esta instalada o no se encontro en el PATH.` };
16528
+ return { ok: false, code: "cli-not-found", text: serverText("cliNotInstalled", { label: target.adapter.label }) };
16272
16529
  }
16273
16530
  if (request.agent === request.target) {
16274
- return failed("Esa conversaci\xF3n ya es de esa CLI: para seguirla, abrila desde la barra lateral.");
16531
+ return failed(serverText("continueSameCli"));
16275
16532
  }
16276
16533
  const summary = deps.index.find(request.agent, request.sessionId);
16277
- if (summary === null) return failed("Esa conversaci\xF3n no est\xE1 en el historial: si acaba de empezar, prob\xE1 de nuevo en unos segundos.");
16278
- if (summary.cwd.length === 0) return failed("Esa conversaci\xF3n no dice en qu\xE9 carpeta corri\xF3.");
16534
+ if (summary === null) return failed(serverText("continueNotInHistory"));
16535
+ if (summary.cwd.length === 0) return failed(serverText("continueNoFolder"));
16279
16536
  let source;
16280
16537
  try {
16281
16538
  source = await readEvents(deps, request.agent, summary);
16282
16539
  } catch (error) {
16283
- return failed("No se pudo leer esa conversaci\xF3n.", messageOf(error));
16540
+ return failed(serverText("continueReadFailed"), messageOf(error));
16284
16541
  }
16285
- if (source === null) return failed("No se encontr\xF3 el historial de esa conversaci\xF3n.");
16542
+ if (source === null) return failed(serverText("continueHistoryMissing"));
16286
16543
  const sourceLabel = isAgentId(request.agent) ? deps.agents.get(request.agent)?.adapter.label ?? request.agent : IMPORTED_AGENT_LABELS[request.agent];
16287
16544
  const plan = planTranscript({
16288
16545
  header: {
@@ -16298,27 +16555,27 @@ async function continueSession(deps, request) {
16298
16555
  state: source.state,
16299
16556
  complete: source.complete
16300
16557
  });
16301
- if (!plan.ok) return failed(plan.message);
16558
+ if (!plan.ok) return failed(plan.text);
16302
16559
  let descriptor;
16303
16560
  try {
16304
16561
  descriptor = await deps.registry.open({
16305
16562
  cwd: summary.cwd,
16306
16563
  agent: request.target,
16307
16564
  kind: "agent",
16308
- label: continuationLabel(summary.title)
16565
+ label: request.label ?? continuationLabel(summary.title)
16309
16566
  });
16310
16567
  } catch (error) {
16311
16568
  if (error instanceof TerminalOpenError) {
16312
- return { ok: false, code: error.code, message: error.message, ...error.detail !== void 0 ? { detail: error.detail } : {} };
16569
+ return { ok: false, code: error.code, text: error.text, ...error.detail !== void 0 ? { detail: error.detail } : {} };
16313
16570
  }
16314
- return failed("No se pudo abrir la pesta\xF1a de la continuaci\xF3n.", messageOf(error));
16571
+ return failed(serverText("continueTabFailed"), messageOf(error));
16315
16572
  }
16316
16573
  let saved;
16317
16574
  try {
16318
16575
  saved = await deps.pasteStore.saveText(descriptor.terminalId, "continuacion", plan.markdown);
16319
16576
  } catch (error) {
16320
16577
  deps.closeTab(descriptor.terminalId);
16321
- return failed("No se pudo guardar el transcript de la continuaci\xF3n.", messageOf(error));
16578
+ return failed(serverText("continueTranscriptFailed"), messageOf(error));
16322
16579
  }
16323
16580
  const message = buildContinuationMessage({
16324
16581
  sourceLabel,
@@ -16372,11 +16629,11 @@ function revealPath(absolutePath) {
16372
16629
  try {
16373
16630
  const child = spawn3(command, args, { stdio: "ignore", detached: true, windowsHide: true });
16374
16631
  child.on("error", (error) => {
16375
- console.warn("[archivos] no se pudo abrir la ruta:", error.message);
16632
+ console.warn("[files] couldn't open the path:", error.message);
16376
16633
  });
16377
16634
  child.unref();
16378
16635
  } catch (error) {
16379
- console.warn("[archivos] no se pudo abrir la ruta:", error);
16636
+ console.warn("[files] couldn't open the path:", error);
16380
16637
  }
16381
16638
  }
16382
16639
 
@@ -16408,7 +16665,9 @@ var TerminalWriteQueue = class {
16408
16665
  get interrupted() {
16409
16666
  return interrupted();
16410
16667
  },
16411
- writePieces: async (pieces, gapMs, write, guard2) => {
16668
+ writePieces: async (pieces, gapMs, write, guard2, writeOptions) => {
16669
+ const startAfterMs = writeOptions?.startAfterMs ?? 0;
16670
+ if (startAfterMs > 0) await this.wait(startAfterMs);
16412
16671
  for (const [index, piece] of pieces.entries()) {
16413
16672
  if (index > 0 && gapMs > 0) await this.wait(gapMs);
16414
16673
  if (interrupted()) return "interrupted";
@@ -16428,7 +16687,7 @@ var TerminalWriteQueue = class {
16428
16687
  await job(handle);
16429
16688
  }).catch((error) => {
16430
16689
  console.error(
16431
- `[escritura] fallo un envio a la terminal ${terminalId}:`,
16690
+ `[write] a send to terminal ${terminalId} failed:`,
16432
16691
  error instanceof Error ? error.message : error
16433
16692
  );
16434
16693
  }).finally(() => {
@@ -16457,12 +16716,12 @@ var TerminalWriteQueue = class {
16457
16716
  // packages/server/src/vault/service.ts
16458
16717
  import { mkdir as mkdir15, stat as stat26 } from "node:fs/promises";
16459
16718
  import { homedir as homedir12 } from "node:os";
16460
- import path44 from "node:path";
16719
+ import path45 from "node:path";
16461
16720
 
16462
16721
  // packages/server/src/vault/move.ts
16463
16722
  import { constants } from "node:fs";
16464
16723
  import { copyFile as copyFile2, lstat, mkdir as mkdir13, readdir as readdir13, rm as rm5, stat as stat23, utimes, writeFile as writeFile8 } from "node:fs/promises";
16465
- import path41 from "node:path";
16724
+ import path42 from "node:path";
16466
16725
  var MOVED_ENTRIES = ["vault.json", "sessions", "memory"];
16467
16726
  var OWN_TEMP_FILE = /\.\d+\.[0-9a-f]{12}\.tmp$/;
16468
16727
  async function copyTree(source, target, result) {
@@ -16475,12 +16734,12 @@ async function copyTree(source, target, result) {
16475
16734
  if (info.isDirectory()) {
16476
16735
  await mkdir13(target, { recursive: true });
16477
16736
  for (const name of await readdir13(source)) {
16478
- await copyTree(path41.join(source, name), path41.join(target, name), result);
16737
+ await copyTree(path42.join(source, name), path42.join(target, name), result);
16479
16738
  }
16480
16739
  return;
16481
16740
  }
16482
16741
  if (!info.isFile() || OWN_TEMP_FILE.test(source)) return;
16483
- await mkdir13(path41.dirname(target), { recursive: true });
16742
+ await mkdir13(path42.dirname(target), { recursive: true });
16484
16743
  try {
16485
16744
  await copyFile2(source, target, constants.COPYFILE_EXCL);
16486
16745
  } catch (error) {
@@ -16497,7 +16756,7 @@ async function moveVault(from, to) {
16497
16756
  const result = { copied: 0, skipped: 0 };
16498
16757
  await mkdir13(to, { recursive: true });
16499
16758
  for (const entry of MOVED_ENTRIES) {
16500
- await copyTree(path41.join(from, entry), path41.join(to, entry), result);
16759
+ await copyTree(path42.join(from, entry), path42.join(to, entry), result);
16501
16760
  }
16502
16761
  return result;
16503
16762
  }
@@ -16511,25 +16770,25 @@ function isSameOrInside(child, parent, platform) {
16511
16770
  }
16512
16771
  function checkVaultTarget(current, target, options) {
16513
16772
  if (!isAbsoluteDir(target, options.platform)) {
16514
- return { kind: "refused", message: "La carpeta de la copia tiene que ser una ruta absoluta." };
16773
+ return { kind: "refused", text: serverText("vaultTargetNotAbsolute") };
16515
16774
  }
16516
16775
  const isProtected = isInsideProtected(target, options.protectedDirs) || options.protectedDirs.some((dir) => isSameOrInside(target, dir, options.platform));
16517
16776
  if (isProtected) {
16518
- return { kind: "refused", message: "Esa carpeta es de una CLI: la copia no se guarda ah\xED." };
16777
+ return { kind: "refused", text: serverText("vaultTargetCliFolder") };
16519
16778
  }
16520
16779
  if (normalizeCwdKey(current, options.platform) === normalizeCwdKey(target, options.platform)) {
16521
16780
  return { kind: "same" };
16522
16781
  }
16523
16782
  if (isSameOrInside(target, current, options.platform)) {
16524
- return { kind: "refused", message: "La carpeta nueva no puede estar dentro de la copia actual." };
16783
+ return { kind: "refused", text: serverText("vaultTargetInsideCurrent") };
16525
16784
  }
16526
16785
  if (isSameOrInside(current, target, options.platform)) {
16527
- return { kind: "refused", message: "La copia actual no puede quedar dentro de la carpeta nueva." };
16786
+ return { kind: "refused", text: serverText("vaultCurrentInsideTarget") };
16528
16787
  }
16529
16788
  return { kind: "ok" };
16530
16789
  }
16531
16790
  async function canWriteInto(dir) {
16532
- const probe = temporaryPathFor(path41.join(dir, ".escritura"));
16791
+ const probe = temporaryPathFor(path42.join(dir, ".escritura"));
16533
16792
  try {
16534
16793
  await mkdir13(dir, { recursive: true });
16535
16794
  if (!(await stat23(dir)).isDirectory()) return false;
@@ -16576,7 +16835,7 @@ function buildHeader(input) {
16576
16835
  writtenAt: input.writtenAt
16577
16836
  };
16578
16837
  if (parseVaultHeader(JSON.parse(JSON.stringify(header))) === null) {
16579
- throw new Error(`La cabecera de ${input.agent}/${input.sessionId} no se podria leer despues`);
16838
+ throw new Error(`The header of ${input.agent}/${input.sessionId} couldn't be read back`);
16580
16839
  }
16581
16840
  return header;
16582
16841
  }
@@ -16620,7 +16879,7 @@ function serializeSession(input) {
16620
16879
  truncated: document.truncated
16621
16880
  };
16622
16881
  if (parseVaultBodyLine(line) === null) {
16623
- throw new Error(`Documento que no se podria leer despues: ${JSON.stringify(document.name)}`);
16882
+ throw new Error(`A document that couldn't be read back: ${JSON.stringify(document.name)}`);
16624
16883
  }
16625
16884
  bodyLines.push(JSON.stringify(line));
16626
16885
  }
@@ -16636,11 +16895,11 @@ function serializeSession(input) {
16636
16895
 
16637
16896
  // packages/server/src/vault/writer.ts
16638
16897
  import { readdir as readdir15, rm as rm6, stat as stat25 } from "node:fs/promises";
16639
- import path43 from "node:path";
16898
+ import path44 from "node:path";
16640
16899
 
16641
16900
  // packages/server/src/vault/memory-copy.ts
16642
16901
  import { mkdir as mkdir14, readdir as readdir14, readFile as readFile16, stat as stat24, utimes as utimes2 } from "node:fs/promises";
16643
- import path42 from "node:path";
16902
+ import path43 from "node:path";
16644
16903
  var MEMORY_FILE_MAX_BYTES = 1024 * 1024;
16645
16904
  var MEMORY_MAX_FILES = 500;
16646
16905
  var MEMORY_SOURCE_FILE = "source.json";
@@ -16706,7 +16965,7 @@ async function copyProjectMemory(dir, cwd, platform, deps = DEFAULT_WRITE_DEPS)
16706
16965
  await mkdir14(target, { recursive: true });
16707
16966
  };
16708
16967
  for (const note of notes) {
16709
- const destination = path42.join(target, note.name);
16968
+ const destination = path43.join(target, note.name);
16710
16969
  if (await isUpToDate(destination, note)) continue;
16711
16970
  let content;
16712
16971
  try {
@@ -16722,7 +16981,7 @@ async function copyProjectMemory(dir, cwd, platform, deps = DEFAULT_WRITE_DEPS)
16722
16981
  result.files += 1;
16723
16982
  result.bytes += content.length;
16724
16983
  }
16725
- const source = path42.join(target, MEMORY_SOURCE_FILE);
16984
+ const source = path43.join(target, MEMORY_SOURCE_FILE);
16726
16985
  if (result.files > 0 || !await exists3(source)) {
16727
16986
  await prepare();
16728
16987
  await writeFileAtomic(source, `${JSON.stringify({ cwd, copiedAt: deps.now() })}
@@ -16736,18 +16995,20 @@ var VAULT_WRITER_REVISION = 1;
16736
16995
  var CALM_MS = 6e4;
16737
16996
  var PASS_DEBOUNCE_MS = 5e3;
16738
16997
  var TEMP_MAX_AGE_MS = 60 * 60 * 1e3;
16739
- var ORIGIN_NOT_FOUND = "no se encontr\xF3 el origen";
16740
- var FOREIGN_FORMAT = "la copia que ya est\xE1 es de un formato m\xE1s nuevo";
16741
- var UNSAFE_SESSION_ID = "id de sesi\xF3n que no se puede copiar";
16742
- var MEASURE_FIRST_MESSAGE = "Med\xED primero cu\xE1nto ocupar\xEDa.";
16998
+ var ORIGIN_NOT_FOUND = serverText("vaultReasonOriginMissing");
16999
+ var FOREIGN_FORMAT = serverText("vaultReasonNewerFormat");
17000
+ var UNSAFE_SESSION_ID = serverText("vaultReasonUnsafeId");
17001
+ var MEASURE_FIRST_TEXT = serverText("vaultMeasureFirst");
16743
17002
  var PROGRESS_EVERY = 10;
16744
17003
  var MAX_FAILURE_REASONS = 5;
16745
17004
  var OWN_TEMP_FILE2 = /\.\d+\.[0-9a-f]{12}\.tmp$/;
16746
17005
  var VaultBusyError = class extends Error {
16747
17006
  constructor(activity) {
16748
- super(`La copia est\xE1 ocupada (${activity}).`);
17007
+ super(`The local copy is busy (${activity}).`);
17008
+ this.activity = activity;
16749
17009
  this.name = "VaultBusyError";
16750
17010
  }
17011
+ activity;
16751
17012
  };
16752
17013
  var REAL_TIMERS2 = {
16753
17014
  set: (run, ms) => {
@@ -16765,7 +17026,8 @@ function isNoSpace(error) {
16765
17026
  return error?.code === "ENOSPC";
16766
17027
  }
16767
17028
  function addReason(reasons, reason2) {
16768
- if (reasons.length < MAX_FAILURE_REASONS && !reasons.includes(reason2)) reasons.push(reason2);
17029
+ const same = JSON.stringify(reason2);
17030
+ if (reasons.length < MAX_FAILURE_REASONS && !reasons.some((known) => JSON.stringify(known) === same)) reasons.push(reason2);
16769
17031
  }
16770
17032
  function emptyMeasure(agent) {
16771
17033
  return {
@@ -16782,7 +17044,7 @@ function emptyMeasure(agent) {
16782
17044
  };
16783
17045
  }
16784
17046
  function enableRefusal(hasMeasurement, passSessions) {
16785
- return hasMeasurement || passSessions > 0 ? null : MEASURE_FIRST_MESSAGE;
17047
+ return hasMeasurement || passSessions > 0 ? null : MEASURE_FIRST_TEXT;
16786
17048
  }
16787
17049
  function vaultEventLimits(toolResultMaxChars) {
16788
17050
  return {
@@ -16870,7 +17132,7 @@ var VaultWriter = class {
16870
17132
  }
16871
17133
  const dir = this.options.catalog.getDir();
16872
17134
  if (dir === null) {
16873
- this.lastError = "La copia no est\xE1 cargada.";
17135
+ this.lastError = serverText("vaultNotLoaded");
16874
17136
  this.emit();
16875
17137
  return null;
16876
17138
  }
@@ -16927,7 +17189,7 @@ var VaultWriter = class {
16927
17189
  try {
16928
17190
  listener();
16929
17191
  } catch (error) {
16930
- this.log.warn("[copia] un oyente del escritor lanzo:", error);
17192
+ this.log.warn("[vault] a writer listener threw:", error);
16931
17193
  }
16932
17194
  }
16933
17195
  }
@@ -17050,14 +17312,14 @@ var VaultWriter = class {
17050
17312
  const message = messageOf2(error);
17051
17313
  if (isNoSpace(error)) {
17052
17314
  report.aborted = message;
17053
- thrown = `Disco lleno: la pasada se corto (${message})`;
17054
- this.log.warn(`[copia] ${thrown}`);
17315
+ thrown = serverText("vaultDiskFull", { detail: message });
17316
+ this.log.warn(`[vault] disk full: the pass stopped (${message})`);
17055
17317
  break;
17056
17318
  }
17057
17319
  report.failed += 1;
17058
- addReason(report.failureReasons, message);
17059
- thrown = `${agent}/${sessionId}: ${message}`;
17060
- this.log.warn(`[copia] no se pudo copiar ${thrown}`);
17320
+ addReason(report.failureReasons, serverText("raw", { text: message }));
17321
+ thrown = serverText("vaultSessionFailed", { agent, session: sessionId, detail: message });
17322
+ this.log.warn(`[vault] couldn't copy ${agent}/${sessionId}: ${message}`);
17061
17323
  }
17062
17324
  this.setProgress(position + 1, candidates.length);
17063
17325
  await nextTurn2();
@@ -17071,8 +17333,8 @@ var VaultWriter = class {
17071
17333
  report.memoryBytes += copied.bytes;
17072
17334
  } catch (error) {
17073
17335
  const message = messageOf2(error);
17074
- thrown = `memoria de ${project.cwd}: ${message}`;
17075
- this.log.warn(`[copia] no se pudo copiar la ${thrown}`);
17336
+ thrown = serverText("vaultMemoryFailed", { cwd: project.cwd, detail: message });
17337
+ this.log.warn(`[vault] couldn't copy the memory of ${project.cwd}: ${message}`);
17076
17338
  if (isNoSpace(error)) {
17077
17339
  report.aborted = message;
17078
17340
  break;
@@ -17119,7 +17381,7 @@ var VaultWriter = class {
17119
17381
  if (catalog.hasForeignFormat(agent, sessionId)) {
17120
17382
  if (!this.warnedForeign.has(key)) {
17121
17383
  this.warnedForeign.add(key);
17122
- this.log.warn(`[copia] ${key}: la copia en disco es de un formato mas nuevo; no se pisa`);
17384
+ this.log.warn(`[vault] ${key}: the copy on disk is in a newer format; it isn't overwritten`);
17123
17385
  }
17124
17386
  return { kind: "foreign" };
17125
17387
  }
@@ -17167,7 +17429,7 @@ var VaultWriter = class {
17167
17429
  const loaded = await whole.readImage(event.eventId, part.index, part.source);
17168
17430
  if (loaded !== null) images.set(keyOfImage, { kind: "loaded", data: Buffer.from(loaded.data, "base64") });
17169
17431
  } catch (error) {
17170
- this.log.warn(`[copia] ${whole.label}: no se pudo leer una imagen: ${messageOf2(error)}`);
17432
+ this.log.warn(`[vault] ${whole.label}: couldn't read an image: ${messageOf2(error)}`);
17171
17433
  }
17172
17434
  }
17173
17435
  }
@@ -17298,7 +17560,7 @@ var VaultWriter = class {
17298
17560
  row.images += read.serialized.assets.size;
17299
17561
  for (const bytes of read.serialized.assets.values()) row.imageBytes += bytes.length;
17300
17562
  } catch (error) {
17301
- failed(messageOf2(error));
17563
+ failed(serverText("raw", { text: messageOf2(error) }));
17302
17564
  }
17303
17565
  }
17304
17566
  /**
@@ -17313,17 +17575,17 @@ var VaultWriter = class {
17313
17575
  const folders = [dir];
17314
17576
  const subfolders = async (parent) => {
17315
17577
  try {
17316
- return (await readdir15(parent, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => path43.join(parent, entry.name));
17578
+ return (await readdir15(parent, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => path44.join(parent, entry.name));
17317
17579
  } catch {
17318
17580
  return [];
17319
17581
  }
17320
17582
  };
17321
17583
  for (const agentDir of await subfolders(sessionsRoot2(dir))) {
17322
- if (!SESSION_AGENT_IDS.includes(path43.basename(agentDir))) continue;
17584
+ if (!SESSION_AGENT_IDS.includes(path44.basename(agentDir))) continue;
17323
17585
  folders.push(agentDir);
17324
17586
  folders.push(...(await subfolders(agentDir)).filter((folder) => folder.endsWith(".assets")));
17325
17587
  }
17326
- folders.push(...await subfolders(path43.join(dir, "memory")));
17588
+ folders.push(...await subfolders(path44.join(dir, "memory")));
17327
17589
  for (const folder of folders) {
17328
17590
  let names;
17329
17591
  try {
@@ -17333,7 +17595,7 @@ var VaultWriter = class {
17333
17595
  }
17334
17596
  for (const name of names) {
17335
17597
  if (!OWN_TEMP_FILE2.test(name)) continue;
17336
- const file = path43.join(folder, name);
17598
+ const file = path44.join(folder, name);
17337
17599
  try {
17338
17600
  const info = await stat25(file);
17339
17601
  if (info.isFile() && now - info.mtimeMs > TEMP_MAX_AGE_MS) await rm6(file, { force: true });
@@ -17346,11 +17608,7 @@ var VaultWriter = class {
17346
17608
 
17347
17609
  // packages/server/src/vault/service.ts
17348
17610
  var STATUS_INTERVAL_MS = 500;
17349
- var VaultError = class extends Error {
17350
- constructor(message) {
17351
- super(message);
17352
- this.name = "VaultError";
17353
- }
17611
+ var VaultError = class extends ServerTextError {
17354
17612
  };
17355
17613
  var REAL_TIMERS3 = {
17356
17614
  set: (run, ms) => {
@@ -17362,8 +17620,15 @@ var REAL_TIMERS3 = {
17362
17620
  };
17363
17621
  var nextTurn3 = () => new Promise((resolve) => setImmediate(resolve));
17364
17622
  var messageOf3 = (error) => error instanceof Error ? error.message : String(error);
17365
- function busyMessage(error) {
17366
- return `${error.message} Prob\xE1 de nuevo cuando termine.`;
17623
+ function busyText(error) {
17624
+ switch (error.activity) {
17625
+ case "measuring":
17626
+ return serverText("vaultBusyMeasuring");
17627
+ case "moving":
17628
+ return serverText("vaultBusyMoving");
17629
+ default:
17630
+ return serverText("vaultBusyWriting");
17631
+ }
17367
17632
  }
17368
17633
  async function collect(lines) {
17369
17634
  const out = [];
@@ -17475,12 +17740,12 @@ var VaultService = class {
17475
17740
  /** La pasada en seco (§7.4). Lanza `VaultError` si hay algo en curso o el indice no termino. */
17476
17741
  async measure() {
17477
17742
  if (this.options.index.getStatus().state !== "ready") {
17478
- throw new VaultError("Todav\xEDa se est\xE1 leyendo el historial. Med\xED cuando termine.");
17743
+ throw new VaultError(serverText("vaultIndexNotReady"));
17479
17744
  }
17480
17745
  try {
17481
17746
  return await this.writer.measure();
17482
17747
  } catch (error) {
17483
- if (error instanceof VaultBusyError) throw new VaultError(busyMessage(error));
17748
+ if (error instanceof VaultBusyError) throw new VaultError(busyText(error));
17484
17749
  throw error;
17485
17750
  }
17486
17751
  }
@@ -17509,7 +17774,7 @@ var VaultService = class {
17509
17774
  /** Muda la copia a donde esta parado un selector de carpetas de quien la pide. */
17510
17775
  async setDirFromPicker(pickers, pickerId) {
17511
17776
  const target = pickers.currentPath(pickerId);
17512
- if (target === null) throw new VaultError("Ese selector ya no est\xE1 abierto.");
17777
+ if (target === null) throw new VaultError(serverText("pickerClosed"));
17513
17778
  await this.setDir(target);
17514
17779
  }
17515
17780
  /**
@@ -17520,13 +17785,13 @@ var VaultService = class {
17520
17785
  async setDir(target) {
17521
17786
  const { platform, agents, settings: settings2, catalog } = this.options;
17522
17787
  const current = this.dir();
17523
- const protectedDirs = [...agents.protectedDirs(), path44.join(this.options.homeDir ?? homedir12(), ".gemini")];
17788
+ const protectedDirs = [...agents.protectedDirs(), path45.join(this.options.homeDir ?? homedir12(), ".gemini")];
17524
17789
  const check = checkVaultTarget(current, target, { protectedDirs, platform });
17525
- if (check.kind === "refused") throw new VaultError(check.message);
17790
+ if (check.kind === "refused") throw new VaultError(check.text);
17526
17791
  if (check.kind === "same") return;
17527
17792
  try {
17528
17793
  await this.writer.exclusive("moving", async () => {
17529
- if (!await canWriteInto(target)) throw new VaultError("No se puede escribir en esa carpeta.");
17794
+ if (!await canWriteInto(target)) throw new VaultError(serverText("vaultCannotWrite"));
17530
17795
  const hadCopy = await stat26(vaultMarkerFile(current)).then(
17531
17796
  () => true,
17532
17797
  () => false
@@ -17538,9 +17803,9 @@ var VaultService = class {
17538
17803
  await catalog.load(this.dir());
17539
17804
  });
17540
17805
  } catch (error) {
17541
- if (error instanceof VaultBusyError) throw new VaultError(busyMessage(error));
17806
+ if (error instanceof VaultBusyError) throw new VaultError(busyText(error));
17542
17807
  if (error instanceof VaultError) throw error;
17543
- throw new VaultError(`No se pudo mudar la copia: ${messageOf3(error)}`);
17808
+ throw new VaultError(serverText("vaultMoveFailed", { detail: messageOf3(error) }));
17544
17809
  } finally {
17545
17810
  this.scheduleStatus();
17546
17811
  }
@@ -17565,7 +17830,7 @@ var VaultService = class {
17565
17830
  (info) => info.isDirectory(),
17566
17831
  () => false
17567
17832
  );
17568
- if (!isFolder) throw new VaultError("Todav\xEDa no hay copia.");
17833
+ if (!isFolder) throw new VaultError(serverText("vaultNoCopyYet"));
17569
17834
  this.openWithSystem(dir);
17570
17835
  }
17571
17836
  /**
@@ -17576,7 +17841,7 @@ var VaultService = class {
17576
17841
  this.refuseWhileMoving();
17577
17842
  const { catalog } = this.options;
17578
17843
  const header = catalog.header(agent, sessionId);
17579
- if (header === null) throw new VaultError("Esa sesi\xF3n no est\xE1 en la copia.");
17844
+ if (header === null) throw new VaultError(serverText("vaultSessionMissing"));
17580
17845
  const dir = this.activeDir();
17581
17846
  const lines = await collect(catalog.readBody(agent, sessionId));
17582
17847
  const text = renderSessionMarkdown(header, lines, {
@@ -17585,7 +17850,7 @@ var VaultService = class {
17585
17850
  fromCopy: true
17586
17851
  });
17587
17852
  const file = sessionExportFile(dir, agent, sessionId);
17588
- await mkdir15(path44.dirname(file), { recursive: true });
17853
+ await mkdir15(path45.dirname(file), { recursive: true });
17589
17854
  await writeFileAtomic(file, text, this.writeDeps);
17590
17855
  this.openWithSystem(file);
17591
17856
  return file;
@@ -17670,7 +17935,7 @@ var VaultService = class {
17670
17935
  async exportProject(projectKey) {
17671
17936
  this.refuseWhileMoving();
17672
17937
  const project = this.options.index.getProjects().find((candidate) => candidate.key === projectKey);
17673
- if (project === void 0) throw new VaultError("Ese proyecto ya no est\xE1 en la barra.");
17938
+ if (project === void 0) throw new VaultError(serverText("vaultProjectGone"));
17674
17939
  const folder = projectExportDirFor(this.activeDir(), project, this.options.platform);
17675
17940
  await mkdir15(folder, { recursive: true });
17676
17941
  const entries = [];
@@ -17681,7 +17946,7 @@ var VaultService = class {
17681
17946
  try {
17682
17947
  text = await this.renderForExport(summary);
17683
17948
  } catch (error) {
17684
- this.log.warn(`[copia] no se pudo exportar ${summary.agent}/${summary.sessionId}: ${messageOf3(error)}`);
17949
+ this.log.warn(`[vault] couldn't export ${summary.agent}/${summary.sessionId}: ${messageOf3(error)}`);
17685
17950
  text = null;
17686
17951
  }
17687
17952
  if (text === null) {
@@ -17689,7 +17954,7 @@ var VaultService = class {
17689
17954
  continue;
17690
17955
  }
17691
17956
  const fileName = this.exportFileName(summary, names);
17692
- await writeFileAtomic(path44.join(folder, fileName), text, this.writeDeps);
17957
+ await writeFileAtomic(path45.join(folder, fileName), text, this.writeDeps);
17693
17958
  entries.push({
17694
17959
  updatedAt: summary.updatedAt,
17695
17960
  agentLabel: this.labelOf(summary.agent),
@@ -17700,16 +17965,16 @@ var VaultService = class {
17700
17965
  });
17701
17966
  await nextTurn3();
17702
17967
  }
17703
- const name = project.cwd.length > 0 ? path44.basename(project.cwd) || project.cwd : project.fallbackName;
17968
+ const name = project.cwd.length > 0 ? path45.basename(project.cwd) || project.cwd : project.fallbackName;
17704
17969
  const index = renderProjectIndex({ name, cwd: project.cwd, exportedAt: this.now(), entries, skipped });
17705
- await writeFileAtomic(path44.join(folder, "index.md"), index, this.writeDeps);
17970
+ await writeFileAtomic(path45.join(folder, "index.md"), index, this.writeDeps);
17706
17971
  this.openWithSystem(folder);
17707
17972
  return { sessions: entries.length, skipped, folder };
17708
17973
  }
17709
17974
  // ---- Internos ---------------------------------------------------------------
17710
17975
  refuseWhileMoving() {
17711
17976
  if (this.writer.snapshot().activity === "moving") {
17712
- throw new VaultError("La copia se est\xE1 mudando de carpeta. Prob\xE1 de nuevo cuando termine.");
17977
+ throw new VaultError(serverText("vaultMoving"));
17713
17978
  }
17714
17979
  }
17715
17980
  labelOf(agent) {
@@ -17831,7 +18096,7 @@ var VaultService = class {
17831
18096
  try {
17832
18097
  listener(status);
17833
18098
  } catch (error) {
17834
- this.log.warn("[copia] un oyente del estado lanzo:", error);
18099
+ this.log.warn("[vault] a status listener threw:", error);
17835
18100
  }
17836
18101
  }
17837
18102
  }
@@ -17884,8 +18149,8 @@ function attachTerminalSocket(options) {
17884
18149
  if (socket !== except && socket.readyState === socket.OPEN) socket.send(payload);
17885
18150
  }
17886
18151
  };
17887
- const sendError = (socket, code, message, detail, requestId) => {
17888
- const payload = { type: "error", code, message };
18152
+ const sendError = (socket, code, text, detail, requestId) => {
18153
+ const payload = { type: "error", code, text };
17889
18154
  if (detail !== void 0) payload.detail = detail;
17890
18155
  if (requestId !== void 0) payload.requestId = requestId;
17891
18156
  send(socket, payload);
@@ -17899,13 +18164,13 @@ function attachTerminalSocket(options) {
17899
18164
  if (descriptor === null || descriptor.kind !== "agent" || descriptor.agent === null) return null;
17900
18165
  return agents.get(descriptor.agent)?.adapter ?? null;
17901
18166
  };
17902
- const imageStyleFor = (socket, terminalId, unsupportedMessage) => {
18167
+ const imageStyleFor = (socket, terminalId, unsupported) => {
17903
18168
  if (registry.get(terminalId) === null) {
17904
- sendError(socket, "unknown-terminal", "La terminal ya no existe.");
18169
+ sendError(socket, "unknown-terminal", serverText("terminalGone"));
17905
18170
  return null;
17906
18171
  }
17907
18172
  const style = adapterOf(terminalId)?.input.imageReference ?? null;
17908
- if (style === null) sendError(socket, "agent-unsupported", unsupportedMessage);
18173
+ if (style === null) sendError(socket, "agent-unsupported", unsupported);
17909
18174
  return style;
17910
18175
  };
17911
18176
  const inputOf = (terminalId) => adapterOf(terminalId)?.input ?? PLAIN_INPUT;
@@ -17922,13 +18187,9 @@ function attachTerminalSocket(options) {
17922
18187
  const writeToTerminal = (socket, terminalId, data) => {
17923
18188
  if (registry.write(terminalId, data)) return true;
17924
18189
  if (registry.get(terminalId) === null) {
17925
- sendError(socket, "unknown-terminal", "La terminal ya no existe.");
18190
+ sendError(socket, "unknown-terminal", serverText("terminalGone"));
17926
18191
  } else {
17927
- sendError(
17928
- socket,
17929
- "terminal-asleep",
17930
- "Esta pesta\xF1a no tiene la CLI abierta. Abrila para escribirle al agente."
17931
- );
18192
+ sendError(socket, "terminal-asleep", serverText("terminalAsleep"));
17932
18193
  }
17933
18194
  return false;
17934
18195
  };
@@ -18034,12 +18295,12 @@ function attachTerminalSocket(options) {
18034
18295
  const stopVaultStatus = vault.onStatus((status) => broadcast({ type: "vault.status", status }));
18035
18296
  const sendVaultError = (socket, error) => {
18036
18297
  if (error instanceof VaultError) {
18037
- sendError(socket, "vault-failed", error.message);
18298
+ sendError(socket, "vault-failed", error.text);
18038
18299
  return;
18039
18300
  }
18040
18301
  const detail = error instanceof Error ? error.message : String(error);
18041
- console.warn("[copia] un pedido fallo:", detail);
18042
- sendError(socket, "vault-failed", "No se pudo completar la operaci\xF3n de la copia propia.", detail);
18302
+ console.warn("[vault] a request failed:", detail);
18303
+ sendError(socket, "vault-failed", serverText("vaultFailed"), detail);
18043
18304
  };
18044
18305
  const onUpgrade = (request, socket, head) => {
18045
18306
  let pathname;
@@ -18053,7 +18314,7 @@ function attachTerminalSocket(options) {
18053
18314
  if (rejection !== null) {
18054
18315
  socket.write("HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n");
18055
18316
  socket.destroy();
18056
- console.warn(`[seguridad] upgrade rechazado en ${WS_PATH}: ${rejection}`);
18317
+ console.warn(`[security] upgrade rejected on ${WS_PATH}: ${rejection}`);
18057
18318
  return;
18058
18319
  }
18059
18320
  wss.handleUpgrade(request, socket, head, (client) => {
@@ -18078,7 +18339,7 @@ function attachTerminalSocket(options) {
18078
18339
  };
18079
18340
  const sendPathError = (error, fallback) => {
18080
18341
  if (error instanceof InvalidPathError) {
18081
- sendError(socket, "invalid-path", error.message);
18342
+ sendError(socket, "invalid-path", error.text);
18082
18343
  return;
18083
18344
  }
18084
18345
  sendError(
@@ -18090,30 +18351,30 @@ function attachTerminalSocket(options) {
18090
18351
  };
18091
18352
  const sendNotesError = (error) => {
18092
18353
  if (error instanceof NotesError) {
18093
- sendError(socket, "notes-failed", error.message);
18354
+ sendError(socket, "notes-failed", error.text);
18094
18355
  return;
18095
18356
  }
18096
18357
  sendError(
18097
18358
  socket,
18098
18359
  "internal",
18099
- "No se pudo guardar la nota.",
18360
+ serverText("noteSaveFailed"),
18100
18361
  error instanceof Error ? error.message : String(error)
18101
18362
  );
18102
18363
  };
18103
18364
  const sendMemoryError = (error, requestId) => {
18104
18365
  if (error instanceof UnknownTerminalError) {
18105
- sendError(socket, "unknown-terminal", "La terminal ya no existe.", void 0, requestId);
18366
+ sendError(socket, "unknown-terminal", serverText("terminalGone"), void 0, requestId);
18106
18367
  return;
18107
18368
  }
18108
18369
  if (error instanceof InvalidPathError) {
18109
- sendError(socket, "invalid-path", error.message, void 0, requestId);
18370
+ sendError(socket, "invalid-path", error.text, void 0, requestId);
18110
18371
  return;
18111
18372
  }
18112
18373
  const detail = error instanceof Error ? error.message : String(error);
18113
18374
  sendError(
18114
18375
  socket,
18115
18376
  "memory-failed",
18116
- error instanceof MemoryBridgeError ? detail : `No se pudo completar la operaci\xF3n: ${detail}`,
18377
+ error instanceof MemoryBridgeError ? error.text : serverText("operationFailed", { detail }),
18117
18378
  detail,
18118
18379
  requestId
18119
18380
  );
@@ -18139,11 +18400,11 @@ function attachTerminalSocket(options) {
18139
18400
  const text = Array.isArray(raw) ? Buffer.concat(raw).toString("utf8") : Buffer.from(raw).toString("utf8");
18140
18401
  const message = parseClientMessage(text);
18141
18402
  if (message === null) {
18142
- debugLog("socket", `mensaje rechazado: ${text.slice(0, 160)}`);
18143
- sendError(socket, "bad-message", "Mensaje no reconocido por el protocolo.");
18403
+ debugLog("socket", `message rejected: ${text.slice(0, 160)}`);
18404
+ sendError(socket, "bad-message", serverText("badMessage"));
18144
18405
  return;
18145
18406
  }
18146
- if (message.type !== "input") debugLog("socket", `recibido ${message.type}`);
18407
+ if (message.type !== "input") debugLog("socket", `received ${message.type}`);
18147
18408
  switch (message.type) {
18148
18409
  case "input":
18149
18410
  writeToTerminal(socket, message.terminalId, message.data);
@@ -18162,15 +18423,24 @@ function attachTerminalSocket(options) {
18162
18423
  */
18163
18424
  case "agent.submit": {
18164
18425
  const { terminalId, text: text2, images } = message;
18426
+ const files = message.files ?? [];
18427
+ if (files.length > MAX_FILES_PER_SUBMIT) {
18428
+ sendError(
18429
+ socket,
18430
+ "submit-failed",
18431
+ serverText("filesPerMessage", { max: MAX_FILES_PER_SUBMIT })
18432
+ );
18433
+ break;
18434
+ }
18165
18435
  if (images.length > MAX_IMAGES_PER_SUBMIT) {
18166
18436
  sendError(
18167
18437
  socket,
18168
18438
  "submit-failed",
18169
- `Se pueden adjuntar hasta ${MAX_IMAGES_PER_SUBMIT} imagenes por mensaje.`
18439
+ serverText("imagesPerMessage", { max: MAX_IMAGES_PER_SUBMIT })
18170
18440
  );
18171
18441
  break;
18172
18442
  }
18173
- if (images.length > 0 && imageStyleFor(socket, terminalId, "Esta CLI no recibe imagenes desde el cuadro de escritura.") === null) {
18443
+ if (images.length > 0 && imageStyleFor(socket, terminalId, serverText("imagesUnsupported")) === null) {
18174
18444
  break;
18175
18445
  }
18176
18446
  const submitting = adapterOf(terminalId);
@@ -18202,7 +18472,7 @@ function attachTerminalSocket(options) {
18202
18472
  return !guardsPending || await conversations.checkWaitingFor(terminalId) === null;
18203
18473
  } : void 0;
18204
18474
  const input = inputOf(terminalId);
18205
- const interrupted = () => sendError(socket, "submit-failed", "El mensaje no se termino de mandar: lo corto la interrupcion.");
18475
+ const interrupted = () => sendError(socket, "submit-failed", serverText("submitInterrupted"));
18206
18476
  void writeQueue.enqueue(
18207
18477
  terminalId,
18208
18478
  async (lane) => {
@@ -18217,21 +18487,46 @@ function attachTerminalSocket(options) {
18217
18487
  sendError(
18218
18488
  socket,
18219
18489
  "submit-failed",
18220
- error instanceof PasteImageError ? detail : "No se pudo guardar la imagen pegada.",
18490
+ error instanceof PasteImageError ? error.text : serverText("pasteImageFailed"),
18221
18491
  detail
18222
18492
  );
18223
18493
  return;
18224
18494
  }
18225
- const pieces = buildSubmissionWrites(text2, imagePaths, input, {
18495
+ const attachmentLines = [];
18496
+ try {
18497
+ for (const file of files) {
18498
+ const stored = await pasteStore.saveAttachment(terminalId, file.name, file.data);
18499
+ attachmentLines.push(
18500
+ attachmentLine(file.name, stored.bytes, attachmentReference(stored.path, input.transcriptReference))
18501
+ );
18502
+ }
18503
+ } catch (error) {
18504
+ const detail = error instanceof Error ? error.message : String(error);
18505
+ sendError(
18506
+ socket,
18507
+ "submit-failed",
18508
+ error instanceof PasteFileError ? error.text : serverText("attachmentSaveFailed"),
18509
+ detail
18510
+ );
18511
+ return;
18512
+ }
18513
+ const fullText = textWithAttachments(text2, attachmentLines);
18514
+ const pieces = buildSubmissionWrites(fullText, imagePaths, input, {
18226
18515
  send: message.send !== false
18227
18516
  });
18228
18517
  if (pieces === null) return;
18229
- registry.noteSubmitted(terminalId, text2);
18518
+ registry.noteSubmitted(terminalId, fullText);
18519
+ const startAfterMs = submitStartDelayMs(
18520
+ registry.launchedAtOf(terminalId),
18521
+ Date.now(),
18522
+ input.readyAfterLaunchMs ?? 0
18523
+ );
18230
18524
  const outcome = await lane.writePieces(
18231
18525
  pieces,
18232
18526
  input.pieceGapMs,
18233
18527
  pieceWriter(socket, terminalId),
18234
- approvalGuard
18528
+ approvalGuard,
18529
+ { startAfterMs }
18235
18530
  );
18236
18531
  if (outcome === "interrupted") interrupted();
18237
18532
  if (outcome === "blocked") {
@@ -18239,7 +18534,7 @@ function attachTerminalSocket(options) {
18239
18534
  sendError(
18240
18535
  socket,
18241
18536
  "submit-failed",
18242
- waitingNow ? `El mensaje no se termino de mandar: ${submitting?.label ?? "la CLI"} empezo a esperar una respuesta mientras se escribia. Contestala antes de mandar otro mensaje.` : `El mensaje no se termino de mandar: ${submitting?.label ?? "la CLI"} abrio una herramienta mientras se escribia y puede estar pidiendo una aprobacion. Revisa la solapa CLI.`
18537
+ waitingNow ? submitting === null ? serverText("submitStoppedWaitingNoLabel") : serverText("submitStoppedWaiting", { label: submitting.label }) : submitting === null ? serverText("submitStoppedToolNoLabel") : serverText("submitStoppedTool", { label: submitting.label })
18243
18538
  );
18244
18539
  }
18245
18540
  },
@@ -18263,7 +18558,7 @@ function attachTerminalSocket(options) {
18263
18558
  sendError(
18264
18559
  socket,
18265
18560
  "agent-unsupported",
18266
- "Esta CLI no recibe respuestas desde la conversacion. Contestala en la solapa CLI."
18561
+ serverText("answersUnsupported")
18267
18562
  );
18268
18563
  break;
18269
18564
  }
@@ -18271,36 +18566,36 @@ function attachTerminalSocket(options) {
18271
18566
  if (channel !== void 0) {
18272
18567
  const descriptor = registry.get(message.terminalId);
18273
18568
  if (descriptor === null) {
18274
- sendError(socket, "unknown-terminal", "La terminal ya no existe.");
18569
+ sendError(socket, "unknown-terminal", serverText("terminalGone"));
18275
18570
  break;
18276
18571
  }
18277
18572
  const { terminalId, toolUseId, selections } = message;
18278
18573
  void (async () => {
18279
18574
  try {
18280
- const failure = answerFailureMessage(
18575
+ const failure = answerFailureText(
18281
18576
  await channel.answer({ cwd: descriptor.cwd, sessionId: descriptor.sessionId }, toolUseId, selections)
18282
18577
  );
18283
18578
  if (failure !== null) sendError(socket, "answer-failed", failure);
18284
18579
  } catch (error) {
18285
18580
  const detail = error instanceof Error ? error.message : String(error);
18286
- debugLog("socket", `respuesta por API en ${terminalId.slice(0, 8)}: ${error instanceof Error ? error.name : "error"}`);
18581
+ debugLog("socket", `API answer in ${terminalId.slice(0, 8)}: ${error instanceof Error ? error.name : "error"}`);
18287
18582
  sendError(
18288
18583
  socket,
18289
18584
  "answer-failed",
18290
- "No se pudo mandar la respuesta: contestala en la solapa CLI.",
18585
+ serverText("answerSendFailed"),
18291
18586
  detail
18292
18587
  );
18293
18588
  }
18294
18589
  })();
18295
18590
  break;
18296
18591
  }
18297
- const interruptedAnswer = () => sendError(socket, "answer-failed", "La respuesta no se termino de mandar: la corto la interrupcion.");
18592
+ const interruptedAnswer = () => sendError(socket, "answer-failed", serverText("answerInterrupted"));
18298
18593
  void writeQueue.enqueue(
18299
18594
  message.terminalId,
18300
18595
  async (lane) => {
18301
18596
  const pending = conversations.getPendingQuestion(message.terminalId);
18302
18597
  if (pending === null || pending.toolUseId !== message.toolUseId) {
18303
- sendError(socket, "answer-failed", ANSWER_NOT_PENDING_MESSAGE);
18598
+ sendError(socket, "answer-failed", ANSWER_NOT_PENDING_TEXT);
18304
18599
  return;
18305
18600
  }
18306
18601
  const keys = buildAnswerKeys(
@@ -18311,7 +18606,7 @@ function attachTerminalSocket(options) {
18311
18606
  message.selections
18312
18607
  );
18313
18608
  if (keys === null) {
18314
- sendError(socket, "answer-failed", ANSWER_INVALID_MESSAGE);
18609
+ sendError(socket, "answer-failed", ANSWER_INVALID_TEXT);
18315
18610
  return;
18316
18611
  }
18317
18612
  const outcome = await lane.writePieces(
@@ -18340,12 +18635,12 @@ function attachTerminalSocket(options) {
18340
18635
  */
18341
18636
  case "agent.mode": {
18342
18637
  if (registry.get(message.terminalId) === null) {
18343
- sendError(socket, "unknown-terminal", "La terminal ya no existe.");
18638
+ sendError(socket, "unknown-terminal", serverText("terminalGone"));
18344
18639
  break;
18345
18640
  }
18346
18641
  const cycle = adapterOf(message.terminalId)?.capabilities.permissionCycle ?? null;
18347
18642
  if (cycle === null) {
18348
- sendError(socket, "agent-unsupported", "Esta CLI no tiene modos de permiso que cambiar desde aca.");
18643
+ sendError(socket, "agent-unsupported", serverText("modesUnsupported"));
18349
18644
  break;
18350
18645
  }
18351
18646
  const current = conversations.getPermissionMode(message.terminalId) ?? cycle.launchMode;
@@ -18357,7 +18652,7 @@ function attachTerminalSocket(options) {
18357
18652
  });
18358
18653
  if (plan.kind === "none") break;
18359
18654
  if (plan.kind === "refused") {
18360
- sendError(socket, "mode-failed", plan.message);
18655
+ sendError(socket, "mode-failed", plan.text);
18361
18656
  break;
18362
18657
  }
18363
18658
  const { keys } = plan;
@@ -18391,7 +18686,7 @@ function attachTerminalSocket(options) {
18391
18686
  sendError(
18392
18687
  socket,
18393
18688
  "archive-failed",
18394
- "Las sesiones con una pesta\xF1a abierta no se archivan. Cerrala primero."
18689
+ serverText("archiveOpenTab")
18395
18690
  );
18396
18691
  }
18397
18692
  break;
@@ -18426,7 +18721,7 @@ function attachTerminalSocket(options) {
18426
18721
  sendError(
18427
18722
  socket,
18428
18723
  "agent-unsupported",
18429
- "Este servidor no sabe lanzar esa CLI.",
18724
+ serverText("agentUnknown"),
18430
18725
  message.unsupportedAgent,
18431
18726
  message.requestId
18432
18727
  );
@@ -18448,12 +18743,12 @@ function attachTerminalSocket(options) {
18448
18743
  });
18449
18744
  } catch (error) {
18450
18745
  if (error instanceof TerminalOpenError) {
18451
- sendError(socket, error.code, error.message, error.detail, message.requestId);
18746
+ sendError(socket, error.code, error.text, error.detail, message.requestId);
18452
18747
  } else {
18453
18748
  sendError(
18454
18749
  socket,
18455
18750
  "internal",
18456
- "No se pudo abrir la pestana.",
18751
+ serverText("tabOpenFailed"),
18457
18752
  error instanceof Error ? error.message : String(error),
18458
18753
  message.requestId
18459
18754
  );
@@ -18471,16 +18766,16 @@ function attachTerminalSocket(options) {
18471
18766
  try {
18472
18767
  const descriptor = await registry.wake(message.terminalId);
18473
18768
  if (descriptor === null && !existed) {
18474
- sendError(socket, "unknown-terminal", "La terminal ya no existe.");
18769
+ sendError(socket, "unknown-terminal", serverText("terminalGone"));
18475
18770
  }
18476
18771
  } catch (error) {
18477
18772
  if (error instanceof TerminalOpenError) {
18478
- sendError(socket, error.code, error.message, error.detail);
18773
+ sendError(socket, error.code, error.text, error.detail);
18479
18774
  } else {
18480
18775
  sendError(
18481
18776
  socket,
18482
18777
  "internal",
18483
- "No se pudo abrir la CLI de la pesta\xF1a.",
18778
+ serverText("cliOpenFailed"),
18484
18779
  error instanceof Error ? error.message : String(error)
18485
18780
  );
18486
18781
  }
@@ -18490,7 +18785,7 @@ function attachTerminalSocket(options) {
18490
18785
  }
18491
18786
  case "terminal.close":
18492
18787
  if (!dropTab(message.terminalId)) {
18493
- sendError(socket, "unknown-terminal", "La terminal ya no existe.");
18788
+ sendError(socket, "unknown-terminal", serverText("terminalGone"));
18494
18789
  } else {
18495
18790
  subscriptions.delete(message.terminalId);
18496
18791
  gitSubscriptions.delete(message.terminalId);
@@ -18500,7 +18795,7 @@ function attachTerminalSocket(options) {
18500
18795
  case "terminal.attach": {
18501
18796
  const attached = registry.attach(message.terminalId, listener);
18502
18797
  if (attached === null) {
18503
- sendError(socket, "unknown-terminal", "La terminal ya no existe.");
18798
+ sendError(socket, "unknown-terminal", serverText("terminalGone"));
18504
18799
  break;
18505
18800
  }
18506
18801
  send(socket, {
@@ -18517,7 +18812,7 @@ function attachTerminalSocket(options) {
18517
18812
  break;
18518
18813
  case "terminal.rename":
18519
18814
  if (!registry.rename(message.terminalId, message.label)) {
18520
- sendError(socket, "unknown-terminal", "La terminal ya no existe.");
18815
+ sendError(socket, "unknown-terminal", serverText("terminalGone"));
18521
18816
  }
18522
18817
  break;
18523
18818
  case "tabs.reorder":
@@ -18547,7 +18842,7 @@ function attachTerminalSocket(options) {
18547
18842
  }
18548
18843
  const snapshot = await conversations.subscribe(message.terminalId);
18549
18844
  if (snapshot === null) {
18550
- sendError(socket, "unknown-terminal", "La terminal ya no existe.");
18845
+ sendError(socket, "unknown-terminal", serverText("terminalGone"));
18551
18846
  return;
18552
18847
  }
18553
18848
  subscriptions.add(message.terminalId);
@@ -18581,7 +18876,7 @@ function attachTerminalSocket(options) {
18581
18876
  void (async () => {
18582
18877
  const plan = await conversations.readPlan(message.terminalId, message.fileName).catch(() => null);
18583
18878
  if (plan === null) {
18584
- sendError(socket, "read-failed", "No se pudo leer el plan.");
18879
+ sendError(socket, "read-failed", serverText("planReadFailed"));
18585
18880
  return;
18586
18881
  }
18587
18882
  send(socket, { type: "plans.content", terminalId: message.terminalId, plan });
@@ -18618,7 +18913,7 @@ function attachTerminalSocket(options) {
18618
18913
  message.limit
18619
18914
  );
18620
18915
  if (page === null) {
18621
- sendError(socket, "unknown-terminal", "Esa conversacion ya no se esta siguiendo.");
18916
+ sendError(socket, "unknown-terminal", serverText("conversationNotFollowed"));
18622
18917
  break;
18623
18918
  }
18624
18919
  send(socket, {
@@ -18637,7 +18932,7 @@ function attachTerminalSocket(options) {
18637
18932
  }
18638
18933
  const status = await repos.subscribe(message.terminalId);
18639
18934
  if (status === null) {
18640
- sendError(socket, "unknown-terminal", "La terminal ya no existe.");
18935
+ sendError(socket, "unknown-terminal", serverText("terminalGone"));
18641
18936
  return;
18642
18937
  }
18643
18938
  gitSubscriptions.add(message.terminalId);
@@ -18733,7 +19028,7 @@ function attachTerminalSocket(options) {
18733
19028
  case "memory.read":
18734
19029
  void memory.read(message.terminalId, message.name).then((note) => {
18735
19030
  if (note === null) {
18736
- sendError(socket, "memory-failed", "Esa nota ya no existe.", void 0, message.requestId);
19031
+ sendError(socket, "memory-failed", serverText("memoryNoteGone"), void 0, message.requestId);
18737
19032
  return;
18738
19033
  }
18739
19034
  send(
@@ -18749,7 +19044,7 @@ function attachTerminalSocket(options) {
18749
19044
  void (async () => {
18750
19045
  const status = repos.getStatus(message.terminalId);
18751
19046
  if (status === null || status.state !== "ready") {
18752
- sendError(socket, "read-failed", "Todavia no se conoce el estado del repositorio.");
19047
+ sendError(socket, "read-failed", serverText("gitStatusUnknown"));
18753
19048
  return;
18754
19049
  }
18755
19050
  try {
@@ -18763,7 +19058,7 @@ function attachTerminalSocket(options) {
18763
19058
  });
18764
19059
  send(socket, { type: "git.diff", terminalId: message.terminalId, diff });
18765
19060
  } catch (error) {
18766
- sendPathError(error, "No se pudo leer el diff.");
19061
+ sendPathError(error, serverText("diffReadFailed"));
18767
19062
  }
18768
19063
  })();
18769
19064
  break;
@@ -18771,7 +19066,7 @@ function attachTerminalSocket(options) {
18771
19066
  void (async () => {
18772
19067
  const cwd = cwdOf(message.terminalId);
18773
19068
  if (cwd === null) {
18774
- sendError(socket, "unknown-terminal", "La terminal ya no existe.");
19069
+ sendError(socket, "unknown-terminal", serverText("terminalGone"));
18775
19070
  return;
18776
19071
  }
18777
19072
  try {
@@ -18780,7 +19075,7 @@ function attachTerminalSocket(options) {
18780
19075
  });
18781
19076
  send(socket, { type: "files.listing", terminalId: message.terminalId, listing });
18782
19077
  } catch (error) {
18783
- sendPathError(error, "No se pudo leer el directorio.");
19078
+ sendPathError(error, serverText("dirReadFailed"));
18784
19079
  }
18785
19080
  })();
18786
19081
  break;
@@ -18794,7 +19089,7 @@ function attachTerminalSocket(options) {
18794
19089
  void (async () => {
18795
19090
  const cwd = cwdOf(message.terminalId);
18796
19091
  if (cwd === null) {
18797
- sendError(socket, "unknown-terminal", "La terminal ya no existe.");
19092
+ sendError(socket, "unknown-terminal", serverText("terminalGone"));
18798
19093
  return;
18799
19094
  }
18800
19095
  try {
@@ -18803,7 +19098,7 @@ function attachTerminalSocket(options) {
18803
19098
  });
18804
19099
  send(socket, { type: "files.results", terminalId: message.terminalId, result });
18805
19100
  } catch (error) {
18806
- sendPathError(error, "No se pudo buscar en el directorio.");
19101
+ sendPathError(error, serverText("dirSearchFailed"));
18807
19102
  }
18808
19103
  })();
18809
19104
  break;
@@ -18811,14 +19106,14 @@ function attachTerminalSocket(options) {
18811
19106
  void (async () => {
18812
19107
  const cwd = cwdOf(message.terminalId);
18813
19108
  if (cwd === null) {
18814
- sendError(socket, "unknown-terminal", "La terminal ya no existe.");
19109
+ sendError(socket, "unknown-terminal", serverText("terminalGone"));
18815
19110
  return;
18816
19111
  }
18817
19112
  try {
18818
19113
  const preview = await readPreview(cwd, message.path);
18819
19114
  send(socket, { type: "files.preview", terminalId: message.terminalId, preview });
18820
19115
  } catch (error) {
18821
- sendPathError(error, "No se pudo leer el archivo.");
19116
+ sendPathError(error, serverText("fileReadFailed"));
18822
19117
  }
18823
19118
  })();
18824
19119
  break;
@@ -18826,13 +19121,13 @@ function attachTerminalSocket(options) {
18826
19121
  void (async () => {
18827
19122
  const cwd = cwdOf(message.terminalId);
18828
19123
  if (cwd === null) {
18829
- sendError(socket, "unknown-terminal", "La terminal ya no existe.");
19124
+ sendError(socket, "unknown-terminal", serverText("terminalGone"));
18830
19125
  return;
18831
19126
  }
18832
19127
  try {
18833
19128
  revealPath(await resolveInside(cwd, message.path, { mustExist: true }));
18834
19129
  } catch (error) {
18835
- sendPathError(error, "No se pudo abrir la ruta.");
19130
+ sendPathError(error, serverText("pathOpenFailed"));
18836
19131
  }
18837
19132
  })();
18838
19133
  break;
@@ -18918,7 +19213,7 @@ function attachTerminalSocket(options) {
18918
19213
  sendError(
18919
19214
  socket,
18920
19215
  "picker-failed",
18921
- error instanceof DirectoryPickerError ? error.message : "No se pudo listar esa carpeta.",
19216
+ error instanceof DirectoryPickerError ? error.text : serverText("pickerListFailed"),
18922
19217
  error instanceof Error ? error.message : String(error)
18923
19218
  );
18924
19219
  }
@@ -18958,20 +19253,20 @@ function attachTerminalSocket(options) {
18958
19253
  void (async () => {
18959
19254
  const descriptor = registry.get(message.terminalId);
18960
19255
  if (descriptor === null) {
18961
- sendError(socket, "unknown-terminal", "La terminal ya no existe.");
19256
+ sendError(socket, "unknown-terminal", serverText("terminalGone"));
18962
19257
  return;
18963
19258
  }
18964
19259
  if (descriptor.kind !== "agent" || descriptor.sessionId.length === 0) {
18965
- sendError(socket, "submit-failed", "Esa pestana no tiene un agente al que mandarle la nota.");
19260
+ sendError(socket, "submit-failed", serverText("noteNoAgent"));
18966
19261
  return;
18967
19262
  }
18968
19263
  const note = await notes.readForSubmit(message.noteId);
18969
19264
  if (note === null) {
18970
- sendError(socket, "submit-failed", "La nota ya no existe.");
19265
+ sendError(socket, "submit-failed", serverText("noteGone"));
18971
19266
  return;
18972
19267
  }
18973
19268
  if (note.text.trim().length === 0 && note.images.length === 0) {
18974
- sendError(socket, "submit-failed", "La nota esta vacia.");
19269
+ sendError(socket, "submit-failed", serverText("noteEmpty"));
18975
19270
  return;
18976
19271
  }
18977
19272
  const outcome = await deliverWhenReady(deliverDeps, {
@@ -18988,26 +19283,26 @@ function attachTerminalSocket(options) {
18988
19283
  case "refused":
18989
19284
  return;
18990
19285
  case "gone":
18991
- sendError(socket, "unknown-terminal", "La terminal ya no existe.");
19286
+ sendError(socket, "unknown-terminal", serverText("terminalGone"));
18992
19287
  return;
18993
19288
  case "no-agent":
18994
19289
  case "no-session":
18995
- sendError(socket, "submit-failed", "Esa pestana no tiene un agente al que mandarle la nota.");
19290
+ sendError(socket, "submit-failed", serverText("noteNoAgent"));
18996
19291
  return;
18997
19292
  case "no-ready-signal":
18998
- sendError(socket, "agent-unsupported", "Esta CLI no avisa cuando esta lista; la nota no se mando.");
19293
+ sendError(socket, "agent-unsupported", serverText("noteNoReadySignal"));
18999
19294
  return;
19000
19295
  case "no-images":
19001
- sendError(socket, "agent-unsupported", "Esta CLI no recibe imagenes; la nota no se mando.");
19296
+ sendError(socket, "agent-unsupported", serverText("noteImagesUnsupported"));
19002
19297
  return;
19003
19298
  case "not-ready":
19004
- sendError(socket, "submit-failed", "La CLI de esa pestana no llego a arrancar; la nota no se mando.");
19299
+ sendError(socket, "submit-failed", serverText("noteCliNotStarted"));
19005
19300
  return;
19006
19301
  case "image-failed":
19007
- sendError(socket, "submit-failed", "No se pudo adjuntar una imagen de la nota.", outcome.detail);
19302
+ sendError(socket, "submit-failed", serverText("noteImageFailed"), outcome.detail);
19008
19303
  return;
19009
19304
  case "interrupted":
19010
- sendError(socket, "submit-failed", "La nota no se termino de mandar: la corto la interrupcion.");
19305
+ sendError(socket, "submit-failed", serverText("noteInterrupted"));
19011
19306
  return;
19012
19307
  }
19013
19308
  })();
@@ -19031,7 +19326,7 @@ function attachTerminalSocket(options) {
19031
19326
  sendError(
19032
19327
  socket,
19033
19328
  "agent-unsupported",
19034
- "Este servidor no sabe lanzar esa CLI.",
19329
+ serverText("agentUnknown"),
19035
19330
  message.unsupportedAgent,
19036
19331
  message.requestId
19037
19332
  );
@@ -19041,19 +19336,24 @@ function attachTerminalSocket(options) {
19041
19336
  void (async () => {
19042
19337
  let outcome;
19043
19338
  try {
19044
- outcome = await continueSession(continueDeps, { agent: message.agent, sessionId: message.sessionId, target });
19339
+ outcome = await continueSession(continueDeps, {
19340
+ agent: message.agent,
19341
+ sessionId: message.sessionId,
19342
+ target,
19343
+ ...message.label !== void 0 ? { label: message.label } : {}
19344
+ });
19045
19345
  } catch (error) {
19046
19346
  sendError(
19047
19347
  socket,
19048
19348
  "continue-failed",
19049
- "No se pudo armar la continuaci\xF3n.",
19349
+ serverText("continueFailed"),
19050
19350
  error instanceof Error ? error.message : String(error),
19051
19351
  requestId
19052
19352
  );
19053
19353
  return;
19054
19354
  }
19055
19355
  if (!outcome.ok) {
19056
- sendError(socket, outcome.code, outcome.message, outcome.detail, requestId);
19356
+ sendError(socket, outcome.code, outcome.text, outcome.detail, requestId);
19057
19357
  return;
19058
19358
  }
19059
19359
  const { terminalId } = outcome.descriptor;
@@ -19073,7 +19373,7 @@ function attachTerminalSocket(options) {
19073
19373
  });
19074
19374
  const reason2 = prefillReasonFor(delivered);
19075
19375
  if (reason2 !== null) {
19076
- debugLog("continuar", `no se mando sola a ${terminalId.slice(0, 8)}: ${delivered.kind}`);
19376
+ debugLog("continue", `not sent on its own to ${terminalId.slice(0, 8)}: ${delivered.kind}`);
19077
19377
  send(socket, { type: "composer.prefill", terminalId, text: outcome.message, reason: reason2 });
19078
19378
  }
19079
19379
  })();
@@ -19104,7 +19404,7 @@ function attachTerminalSocket(options) {
19104
19404
  sendError(
19105
19405
  socket,
19106
19406
  "search-failed",
19107
- error instanceof VaultError ? error.message : "No se pudo buscar en la copia propia.",
19407
+ error instanceof VaultError ? error.text : serverText("searchFailed"),
19108
19408
  detail,
19109
19409
  searchId
19110
19410
  );
@@ -19159,7 +19459,7 @@ function attachTerminalSocket(options) {
19159
19459
 
19160
19460
  // packages/server/src/vault/catalog.ts
19161
19461
  import { open as open7, readdir as readdir16, stat as stat27 } from "node:fs/promises";
19162
- import path45 from "node:path";
19462
+ import path46 from "node:path";
19163
19463
  import { createInterface as createInterface2 } from "node:readline";
19164
19464
  var HEADER_MAX_BYTES = 64 * 1024;
19165
19465
  var LOAD_CONCURRENCY = 16;
@@ -19179,7 +19479,7 @@ async function assetBytesOf(dir, agent, sessionId) {
19179
19479
  for (const name of names) {
19180
19480
  if (!VAULT_ASSET_NAME_PATTERN.test(name)) continue;
19181
19481
  try {
19182
- const info = await stat27(path45.join(folder, name));
19482
+ const info = await stat27(path46.join(folder, name));
19183
19483
  if (info.isFile()) total += info.size;
19184
19484
  } catch {
19185
19485
  }
@@ -19392,34 +19692,37 @@ var VaultCatalog = class {
19392
19692
  try {
19393
19693
  listener();
19394
19694
  } catch (error) {
19395
- console.warn("[copia] un oyente del catalogo lanzo:", error);
19695
+ console.warn("[vault] a catalog listener threw:", error);
19396
19696
  }
19397
19697
  }
19398
19698
  }
19399
19699
  };
19400
19700
 
19401
19701
  // packages/server/src/index.ts
19402
- var serverDir = path46.dirname(fileURLToPath2(import.meta.url));
19702
+ var serverDir = path47.dirname(fileURLToPath2(import.meta.url));
19403
19703
  var isPackaged = true;
19404
- var webRoot = isPackaged ? serverDir : path46.resolve(serverDir, "../../web");
19405
- var webDist = isPackaged ? path46.join(serverDir, "web") : path46.join(webRoot, "dist");
19406
- var repoRoot = path46.resolve(serverDir, "../../..");
19704
+ var webRoot = isPackaged ? serverDir : path47.resolve(serverDir, "../../web");
19705
+ var webDist = isPackaged ? path47.join(serverDir, "web") : path47.join(webRoot, "dist");
19706
+ var repoRoot = path47.resolve(serverDir, "../../..");
19407
19707
  var isProduction = isPackaged || process.argv.includes("--prod");
19408
19708
  function resolveDefaultCwd() {
19409
19709
  const override = process.env["AGENT_WORKBENCH_CWD"];
19410
- if (override !== void 0 && override.length > 0) return path46.resolve(override);
19710
+ if (override !== void 0 && override.length > 0) return path47.resolve(override);
19411
19711
  if (isPackaged) return process.cwd();
19412
19712
  return repoRoot;
19413
19713
  }
19714
+ var ORIGIN_REFUSED_TEXT = "Origin not allowed.\nOrigen no permitido.";
19715
+ var TOKEN_MISSING_TEXT = "The session token is missing. Open the URL the server printed at startup.\nFalta el token de sesi\xF3n. Abre la URL que imprimi\xF3 el servidor al arrancar.";
19716
+ var NOT_FOUND_TEXT = "Not found.\nNo encontrado.";
19414
19717
  function createAuthMiddleware(port, token) {
19415
19718
  return (request, response, next) => {
19416
19719
  if (!hasValidHost(request, port) || !hasValidOrigin(request, port)) {
19417
- response.status(403).type("text/plain").send("Origen no permitido.");
19720
+ response.status(403).type("text/plain").send(ORIGIN_REFUSED_TEXT);
19418
19721
  return;
19419
19722
  }
19420
19723
  const match = matchToken(request, token);
19421
19724
  if (match === null) {
19422
- response.status(401).type("text/plain").send("Falta el token de sesion. Abri la URL que imprimio el servidor al arrancar.");
19725
+ response.status(401).type("text/plain").send(TOKEN_MISSING_TEXT);
19423
19726
  return;
19424
19727
  }
19425
19728
  if (match.source === "query") {
@@ -19440,17 +19743,17 @@ async function mountDevUi(app, httpServer) {
19440
19743
  }
19441
19744
  var LOOKS_LIKE_FILE = /\.[a-zA-Z0-9]{1,8}$/;
19442
19745
  function mountBuiltUi(app) {
19443
- const indexHtml = path46.join(webDist, "index.html");
19746
+ const indexHtml = path47.join(webDist, "index.html");
19444
19747
  if (!existsSync4(indexHtml)) {
19445
19748
  throw new Error(
19446
- `No hay interfaz compilada en ${webDist}.
19447
- Corre "pnpm build" antes de "pnpm start", o usa "pnpm dev".`
19749
+ `There's no compiled interface in ${webDist}.
19750
+ Run "pnpm build" before "pnpm start", or use "pnpm dev".`
19448
19751
  );
19449
19752
  }
19450
19753
  app.use(express.static(webDist, { index: false, maxAge: 0 }));
19451
19754
  app.get(/.*/, (request, response) => {
19452
19755
  if (LOOKS_LIKE_FILE.test(request.path)) {
19453
- response.status(404).type("text/plain").send("No encontrado.");
19756
+ response.status(404).type("text/plain").send(NOT_FOUND_TEXT);
19454
19757
  return;
19455
19758
  }
19456
19759
  response.sendFile(indexHtml);
@@ -19465,8 +19768,8 @@ ${line}`);
19465
19768
  console.log(" Agent Workbench");
19466
19769
  console.log(line);
19467
19770
  console.log(` URL ${url}`);
19468
- console.log(` Modo ${isProduction ? "produccion (interfaz compilada)" : "desarrollo"}`);
19469
- console.log(` Directorio ${cwd}`);
19771
+ console.log(` Mode ${isProduction ? "production (compiled interface)" : "development"}`);
19772
+ console.log(` Directory ${cwd}`);
19470
19773
  const startupAgents = agentList.map((info) => ({
19471
19774
  id: info.id,
19472
19775
  label: info.label,
@@ -19478,12 +19781,12 @@ ${line}`);
19478
19781
  statusLine: info.statusLine?.state ?? null
19479
19782
  }));
19480
19783
  for (const agentLine of startupAgentLines(startupAgents)) console.log(agentLine);
19481
- console.log(` Consola ${shell === null ? "no encontrada" : shell.file}`);
19784
+ console.log(` Console ${shell === null ? "not found" : shell.file}`);
19482
19785
  if (agentList.some((info) => info.environmentNotice === "child-session-marker")) {
19483
19786
  console.log("");
19484
- console.log(" Nota: este proceso heredo CLAUDE_CODE_CHILD_SESSION, que apaga el");
19485
- console.log(" guardado del historial. Se quita del entorno de las pestanas para");
19486
- console.log(" que el historial y la vista de conversacion funcionen igual.");
19787
+ console.log(" Note: this process inherited CLAUDE_CODE_CHILD_SESSION, which turns off");
19788
+ console.log(" saving the history. It is removed from the tabs' environment so the");
19789
+ console.log(" history and the conversation view work the same.");
19487
19790
  }
19488
19791
  console.log(`${line}
19489
19792
  `);
@@ -19494,8 +19797,9 @@ async function main() {
19494
19797
  const agents = createAgentRegistry();
19495
19798
  const [, shell] = await Promise.all([agents.locateAll(), locateShell()]);
19496
19799
  const movedFrom = await migrateLegacyConfigDir();
19497
- if (movedFrom !== null) console.log(`Configuracion movida de ${movedFrom} a ${appConfigDir()}`);
19800
+ if (movedFrom !== null) console.log(`Configuration moved from ${movedFrom} to ${appConfigDir()}`);
19498
19801
  await agents.prepareAll();
19802
+ ensurePtyHelperExecutable();
19499
19803
  const store = new WorkspaceStore();
19500
19804
  const archived = new ArchivedSessions();
19501
19805
  await archived.load();
@@ -19527,7 +19831,7 @@ async function main() {
19527
19831
  });
19528
19832
  const address = httpServer.address();
19529
19833
  if (address === null || typeof address === "string") {
19530
- throw new Error("No se pudo determinar el puerto asignado.");
19834
+ throw new Error("Couldn't determine the assigned port.");
19531
19835
  }
19532
19836
  const { port } = address;
19533
19837
  app.use(createAuthMiddleware(port, token));
@@ -19556,7 +19860,7 @@ async function main() {
19556
19860
  void store.load().then((state) => {
19557
19861
  if (state.tabs.length === 0 && state.foreignTabs.length === 0) return;
19558
19862
  if (agents.anyAvailable()) {
19559
- console.log(`Restaurando ${state.tabs.length} pestana(s) del arranque anterior...`);
19863
+ console.log(`Restoring ${state.tabs.length} tab(s) from the previous run...`);
19560
19864
  }
19561
19865
  return registry.restore(state);
19562
19866
  });
@@ -19568,7 +19872,7 @@ async function main() {
19568
19872
  const shutdown = () => {
19569
19873
  if (shuttingDown) return;
19570
19874
  shuttingDown = true;
19571
- console.log("\nCerrando Agent Workbench...");
19875
+ console.log("\nClosing Agent Workbench...");
19572
19876
  stopWatching();
19573
19877
  detachSocket();
19574
19878
  vault.dispose();
@@ -19587,7 +19891,7 @@ async function main() {
19587
19891
  }
19588
19892
  main().catch((error) => {
19589
19893
  const detail = error instanceof Error ? error.stack ?? error.message : typeof error === "object" && error !== null ? JSON.stringify(error, Object.getOwnPropertyNames(error)) : String(error);
19590
- console.error(`Agent Workbench no pudo arrancar:
19894
+ console.error(`Agent Workbench couldn't start:
19591
19895
  ${detail}`);
19592
19896
  process.exit(1);
19593
19897
  });