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/LICENSE +21 -21
- package/README.md +426 -413
- package/dist/server.js +927 -623
- package/dist/web/assets/de-PtmfVEFc.js +1 -0
- package/dist/web/assets/es-BWOH-zRt.js +1 -0
- package/dist/web/assets/fr-tY7LZ7vh.js +1 -0
- package/dist/web/assets/{highlight-D9nIZ0qi.js → highlight-BdawNAzW.js} +1 -1
- package/dist/web/assets/index-CmLNpznn.css +32 -0
- package/dist/web/assets/index-Crbjtl9a.js +135 -0
- package/dist/web/assets/ja-CebmU1Hk.js +1 -0
- package/dist/web/assets/ko-BuKbKnfu.js +1 -0
- package/dist/web/assets/pt-BR-CxtiCd-u.js +1 -0
- package/dist/web/assets/ru-C3EXpruH.js +1 -0
- package/dist/web/assets/zh-CN-DugN9tNb.js +1 -0
- package/dist/web/favicon.svg +32 -32
- package/dist/web/index.html +19 -19
- package/package.json +2 -2
- package/dist/web/assets/index-Bgx4KtjD.css +0 -32
- package/dist/web/assets/index-DIDbH8tn.js +0 -130
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
|
|
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
|
-
"##
|
|
371
|
+
"## Project memory",
|
|
358
372
|
"",
|
|
359
|
-
"
|
|
360
|
-
"
|
|
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
|
-
"-
|
|
363
|
-
"
|
|
364
|
-
"
|
|
365
|
-
"
|
|
366
|
-
"
|
|
367
|
-
"
|
|
368
|
-
"
|
|
369
|
-
"
|
|
370
|
-
"
|
|
371
|
-
"-
|
|
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
|
-
"#
|
|
395
|
+
"# Project memory",
|
|
381
396
|
"",
|
|
382
|
-
"
|
|
383
|
-
"
|
|
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 =
|
|
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
|
|
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("[
|
|
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
|
|
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 `
|
|
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}]
|
|
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]
|
|
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, `
|
|
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, `
|
|
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]
|
|
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]
|
|
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]
|
|
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(`
|
|
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:(
|
|
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]
|
|
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("
|
|
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]
|
|
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]
|
|
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
|
|
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("
|
|
4431
|
+
throw new InvalidPathError(serverText("pathNullByte"));
|
|
4385
4432
|
}
|
|
4386
4433
|
if (path14.isAbsolute(relativePath) || /^[a-zA-Z]:/.test(relativePath)) {
|
|
4387
|
-
throw new InvalidPathError("
|
|
4434
|
+
throw new InvalidPathError(serverText("pathNotRelative"));
|
|
4388
4435
|
}
|
|
4389
4436
|
if (relativePath.startsWith("/") || relativePath.startsWith("\\")) {
|
|
4390
|
-
throw new InvalidPathError("
|
|
4437
|
+
throw new InvalidPathError(serverText("pathNotRelative"));
|
|
4391
4438
|
}
|
|
4392
4439
|
const segments = relativePath.split(/[\\/]/);
|
|
4393
4440
|
if (segments.includes("..")) {
|
|
4394
|
-
throw new InvalidPathError("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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
|
-
|
|
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
|
-
`[
|
|
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
|
|
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 ? "
|
|
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 = "
|
|
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 = "
|
|
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 = "
|
|
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]
|
|
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]
|
|
6433
|
+
console.warn(`[codex] session ${id8} was assigned to tab ${tab8} without confirmation: ${picked.reason ?? ""}`);
|
|
6382
6434
|
}
|
|
6383
|
-
debugLog("
|
|
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("
|
|
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]
|
|
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:(
|
|
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
|
-
|
|
7047
|
-
|
|
7048
|
-
|
|
7049
|
-
|
|
7050
|
-
|
|
7051
|
-
|
|
7052
|
-
|
|
7053
|
-
|
|
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("
|
|
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]
|
|
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 : "(
|
|
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("
|
|
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(`
|
|
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(`
|
|
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]
|
|
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]
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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("
|
|
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", `
|
|
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("
|
|
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(`
|
|
8717
|
+
throw new ServeRouteError(`This OpenCode version doesn't have the route ${route}.`);
|
|
8660
8718
|
}
|
|
8661
8719
|
if (!response.ok) {
|
|
8662
|
-
throw new ServeRequestError(`
|
|
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(`
|
|
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("
|
|
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(`
|
|
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(`
|
|
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("
|
|
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("
|
|
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", "
|
|
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(`
|
|
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", `
|
|
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(`
|
|
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 ?? `
|
|
9025
|
+
const how = signal ?? `code ${code ?? "?"}`;
|
|
8968
9026
|
if (!settled) {
|
|
8969
9027
|
const tail = running.tail.trim();
|
|
8970
|
-
fail(`
|
|
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", `
|
|
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", "
|
|
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
|
|
9119
|
-
function
|
|
9120
|
-
return waitingFor === "permission prompt" ?
|
|
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
|
|
9123
|
-
var
|
|
9124
|
-
var
|
|
9125
|
-
function
|
|
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
|
|
9189
|
+
return ANSWER_NOT_PENDING_TEXT;
|
|
9131
9190
|
case "invalid":
|
|
9132
|
-
return
|
|
9191
|
+
return ANSWER_INVALID_TEXT;
|
|
9133
9192
|
case "no-free-text":
|
|
9134
|
-
return
|
|
9193
|
+
return ANSWER_NO_FREE_TEXT_TEXT;
|
|
9135
9194
|
}
|
|
9136
9195
|
}
|
|
9137
|
-
function
|
|
9138
|
-
return
|
|
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
|
|
9201
|
+
return pendingSubmitText(input.label, input.waitingFor);
|
|
9143
9202
|
}
|
|
9144
9203
|
if (input.waitingBlocksSubmit === true && input.activity === "waiting") {
|
|
9145
|
-
return
|
|
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",
|
|
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),
|
|
9247
|
+
return send ? [pasted(payload, shape.pasteMarkers), submit] : [pasted(payload, shape.pasteMarkers)];
|
|
9194
9248
|
}
|
|
9195
|
-
return [`${pasted(payload, shape.pasteMarkers)}${send ?
|
|
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(
|
|
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", `
|
|
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 "
|
|
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 ? `
|
|
9776
|
-
return present ? `${paths.dbFile} (
|
|
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(`
|
|
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(`[
|
|
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
|
|
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(`[
|
|
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
|
-
"
|
|
10057
|
-
`
|
|
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
|
-
"
|
|
10203
|
-
`${terminalId.slice(0, 8)}
|
|
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("
|
|
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(`[
|
|
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
|
|
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("
|
|
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:
|
|
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: "
|
|
10831
|
+
message: serverText("gitRunFailed")
|
|
10723
10832
|
};
|
|
10724
10833
|
}
|
|
10725
10834
|
return {
|
|
10726
10835
|
state: "error",
|
|
10727
10836
|
root: "",
|
|
10728
|
-
message: stderr.slice(0, 400)
|
|
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 ?
|
|
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:
|
|
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: "
|
|
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: "
|
|
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)
|
|
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: "
|
|
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: "
|
|
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 ? "
|
|
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(
|
|
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)
|
|
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 ? "
|
|
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", `
|
|
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]
|
|
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
|
|
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
|
|
11271
|
-
var MemoryBridgeError = class extends
|
|
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 =
|
|
11397
|
+
const folder = path31.join(cwd, MEMORY_DIR);
|
|
11293
11398
|
const folderExists = await isDirectory2(folder);
|
|
11294
|
-
const indexExists = folderExists && await isFile(
|
|
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(
|
|
11327
|
-
throw new MemoryBridgeError("
|
|
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("
|
|
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 (
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(`
|
|
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("
|
|
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",
|
|
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",
|
|
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(
|
|
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",
|
|
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(
|
|
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
|
|
11785
|
-
if (file.problem === "encoding") return
|
|
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 ? "
|
|
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 ?? "
|
|
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: "
|
|
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: "
|
|
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 ?? "
|
|
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(
|
|
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(
|
|
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 =
|
|
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) =>
|
|
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 =
|
|
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 =
|
|
11907
|
-
const codexTarget =
|
|
11908
|
-
const geminiTarget =
|
|
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:
|
|
12012
|
+
target: path31.join(home, ".claude", "CLAUDE.md"),
|
|
11915
12013
|
kind: "line",
|
|
11916
12014
|
text: `@~/${GLOBAL_MEMORY_RELATIVE}`,
|
|
11917
|
-
note: "
|
|
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:
|
|
12036
|
+
target: path31.join(home, ".config", "opencode", "opencode.json"),
|
|
11939
12037
|
kind: "json",
|
|
11940
12038
|
text: `"instructions": ["${homeSlashes}/${GLOBAL_MEMORY_RELATIVE}"]`,
|
|
11941
|
-
note: "
|
|
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
|
-
|
|
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.
|
|
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(
|
|
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("
|
|
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
|
|
11984
|
-
const absoluteGitDir =
|
|
11985
|
-
const absoluteCommonDir =
|
|
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 (
|
|
11990
|
-
const mainRoot =
|
|
11991
|
-
const inner =
|
|
11992
|
-
const sameFolder = inner.length === 0 || inner.startsWith("..") ||
|
|
11993
|
-
const mainPath = sameFolder ? mainRoot :
|
|
11994
|
-
const memoryRel = sameFolder ? MEMORY_DIR : [...inner.split(
|
|
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.
|
|
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
|
-
|
|
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
|
|
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 &&
|
|
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(
|
|
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 =
|
|
12125
|
-
|
|
12126
|
-
`.${
|
|
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("
|
|
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", `
|
|
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(`[
|
|
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(`[
|
|
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 =
|
|
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 =
|
|
12465
|
+
const relative = path32.relative(cwd, candidate);
|
|
12369
12466
|
if (relative.length === 0) return true;
|
|
12370
|
-
if (relative.startsWith("..") ||
|
|
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
|
|
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
|
|
12595
|
-
var
|
|
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 (
|
|
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("
|
|
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 = "
|
|
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 : "
|
|
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) : "
|
|
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 ? `-
|
|
12678
|
-
if (result === void 0) return `${title}:
|
|
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 ? "
|
|
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 `##
|
|
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 = `##
|
|
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(`-
|
|
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)} (
|
|
12844
|
+
pushLine(event.role === "user" ? `- ${imagesText(1)} (from the user)` : `- ${imagesText(1)}`);
|
|
12738
12845
|
return;
|
|
12739
12846
|
case "notice":
|
|
12740
|
-
pushLine(`-
|
|
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([`##
|
|
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 ["##
|
|
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 = `
|
|
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 ? "
|
|
12874
|
+
what = total === 1 ? "Includes the only turn." : `Includes all ${total} turns.`;
|
|
12767
12875
|
} else {
|
|
12768
|
-
what = `
|
|
12876
|
+
what = `Includes ${last} of ${total} turns.`;
|
|
12769
12877
|
}
|
|
12770
|
-
return `${what}
|
|
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
|
-
`-
|
|
12780
|
-
`-
|
|
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(`-
|
|
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("-
|
|
12785
|
-
return `#
|
|
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}
|
|
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
|
-
`
|
|
12884
|
-
input.includedTurns === 1 ? `
|
|
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("
|
|
12996
|
+
lines.push("Read all of it and continue from the last request, which was:", "", quote(input.lastRequest));
|
|
12888
12997
|
} else {
|
|
12889
|
-
lines.push("
|
|
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(
|
|
12894
|
-
return style === "at-quoted" ? fileReference(
|
|
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
|
-
|
|
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
|
|
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 ??
|
|
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(
|
|
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("
|
|
13192
|
+
if (bytes.length === 0) throw new PasteImageError(serverText("imageEmpty"));
|
|
12984
13193
|
if (bytes.length > MAX_IMAGE_BYTES3) {
|
|
12985
13194
|
throw new PasteImageError(
|
|
12986
|
-
|
|
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("
|
|
13203
|
+
throw new PasteImageError(serverText("imageUnknownFormat"));
|
|
12992
13204
|
}
|
|
12993
|
-
const directory =
|
|
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 =
|
|
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(`
|
|
13225
|
+
throw new PasteTextError(`The text is ${bytes.length} bytes; the maximum is ${MAX_TEXT_BYTES}.`);
|
|
13014
13226
|
}
|
|
13015
|
-
const directory =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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
|
|
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:
|
|
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("[
|
|
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(`[
|
|
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
|
|
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
|
|
13911
|
+
import path35 from "node:path";
|
|
13666
13912
|
function defaultVaultDir() {
|
|
13667
|
-
return
|
|
13913
|
+
return path35.join(appConfigDir(), "vault");
|
|
13668
13914
|
}
|
|
13669
13915
|
function vaultMarkerFile(dir) {
|
|
13670
|
-
return
|
|
13916
|
+
return path35.join(dir, "vault.json");
|
|
13671
13917
|
}
|
|
13672
13918
|
function sessionsRoot2(dir) {
|
|
13673
|
-
return
|
|
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(`
|
|
13683
|
-
if (!isSafeSessionId(sessionId)) throw new Error(`
|
|
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(`
|
|
13687
|
-
return
|
|
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
|
|
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
|
|
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(`
|
|
13699
|
-
return
|
|
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" ?
|
|
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
|
|
13954
|
+
return path35.join(dir, "memory", projectFolderName(cwd, platform));
|
|
13709
13955
|
}
|
|
13710
13956
|
function projectExportDir(dir, cwd, platform) {
|
|
13711
|
-
return
|
|
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
|
|
13963
|
+
return path35.join(dir, "export", `${name}-${hash}`);
|
|
13718
13964
|
}
|
|
13719
13965
|
function sessionExportFile(dir, agent, sessionId) {
|
|
13720
13966
|
assertSessionKey(agent, sessionId);
|
|
13721
|
-
return
|
|
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
|
|
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(`[
|
|
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("[
|
|
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("[
|
|
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(`
|
|
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(
|
|
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 "
|
|
14173
|
+
return "configured";
|
|
13928
14174
|
case "missing":
|
|
13929
|
-
return "
|
|
14175
|
+
return "not configured (no status or meter; set it up from the meter)";
|
|
13930
14176
|
case "other-command":
|
|
13931
|
-
return "
|
|
14177
|
+
return "another one is configured (no status or meter)";
|
|
13932
14178
|
case "disabled":
|
|
13933
|
-
return "
|
|
14179
|
+
return "disabled with enabled: false";
|
|
13934
14180
|
case "unreadable":
|
|
13935
|
-
return "
|
|
14181
|
+
return "couldn't read its settings.json";
|
|
13936
14182
|
}
|
|
13937
14183
|
}
|
|
13938
|
-
var AVAILABLE_AGENTS_LABEL = "CLIs
|
|
13939
|
-
var HISTORY_LABEL = "
|
|
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 ? "
|
|
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
|
|
13948
|
-
lines.push(`
|
|
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}
|
|
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
|
|
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(`
|
|
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
|
|
14226
|
+
import path37 from "node:path";
|
|
13972
14227
|
var STATE_VERSION2 = 1;
|
|
13973
14228
|
var WRITE_DEBOUNCE_MS2 = 500;
|
|
13974
|
-
var NotesError = class extends
|
|
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("
|
|
14035
|
-
if (this.get(noteId) !== null) throw new NotesError("
|
|
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(
|
|
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("
|
|
14302
|
+
if (note === null) throw new NotesError(serverText("noteGone"));
|
|
14048
14303
|
if (text.length > MAX_NOTE_TEXT_CHARS) {
|
|
14049
|
-
throw new NotesError(
|
|
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("
|
|
14331
|
+
if (note === null) throw new NotesError(serverText("noteGone"));
|
|
14077
14332
|
if (note.images.length >= MAX_NOTE_IMAGES) {
|
|
14078
|
-
throw new NotesError(
|
|
14333
|
+
throw new NotesError(serverText("noteImagesMax", { max: MAX_NOTE_IMAGES }));
|
|
14079
14334
|
}
|
|
14080
14335
|
if (!IMAGE_MEDIA_TYPES.has(declaredType)) {
|
|
14081
|
-
throw new NotesError(
|
|
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("
|
|
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
|
-
|
|
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("
|
|
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
|
|
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(
|
|
14191
|
-
const temporary =
|
|
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("[
|
|
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
|
|
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
|
|
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:
|
|
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]
|
|
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]
|
|
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]
|
|
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
|
|
14599
|
-
constructor(code,
|
|
14600
|
-
super(
|
|
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
|
-
"
|
|
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
|
|
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]
|
|
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 =
|
|
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]
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
|
|
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", "
|
|
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", "
|
|
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("
|
|
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",
|
|
15267
|
+
throw new TerminalOpenError("invalid-cwd", serverText("cwdMissing", { cwd }));
|
|
15011
15268
|
}
|
|
15012
15269
|
if (!info.isDirectory()) {
|
|
15013
|
-
throw new TerminalOpenError("invalid-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
|
-
`
|
|
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
|
-
"
|
|
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("
|
|
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]
|
|
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]
|
|
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
|
-
"
|
|
15351
|
-
`attach ${terminalId.slice(0, 8)}: replay
|
|
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
|
|
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
|
|
15698
|
+
var DirectoryPickerError = class extends ServerTextError {
|
|
15442
15699
|
};
|
|
15443
15700
|
function isInsideProtected(target, dirs) {
|
|
15444
|
-
const resolved =
|
|
15701
|
+
const resolved = path40.resolve(target);
|
|
15445
15702
|
return dirs.some((dir) => {
|
|
15446
|
-
const protectedDir =
|
|
15447
|
-
return resolved === protectedDir || resolved.startsWith(protectedDir +
|
|
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("
|
|
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 =
|
|
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("
|
|
15773
|
+
throw new DirectoryPickerError(serverText("pickerNotListed"));
|
|
15517
15774
|
}
|
|
15518
|
-
const target =
|
|
15775
|
+
const target = path40.join(picker.current, name);
|
|
15519
15776
|
if (isInsideProtected(target, this.protectedDirs)) {
|
|
15520
|
-
throw new DirectoryPickerError("
|
|
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("
|
|
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("
|
|
15802
|
+
throw new DirectoryPickerError(serverText("pickerNameInvalid"));
|
|
15546
15803
|
}
|
|
15547
15804
|
if (clean === "." || clean === "..") {
|
|
15548
|
-
throw new DirectoryPickerError("
|
|
15805
|
+
throw new DirectoryPickerError(serverText("pickerNameInvalid"));
|
|
15549
15806
|
}
|
|
15550
15807
|
if (/[\\/:*?"<>|]/.test(clean)) {
|
|
15551
|
-
throw new DirectoryPickerError("
|
|
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("
|
|
15812
|
+
throw new DirectoryPickerError(serverText("pickerNameChars"));
|
|
15556
15813
|
}
|
|
15557
15814
|
}
|
|
15558
|
-
const target =
|
|
15815
|
+
const target = path40.join(picker.current, clean);
|
|
15559
15816
|
if (isInsideProtected(target, this.protectedDirs)) {
|
|
15560
|
-
throw new DirectoryPickerError("
|
|
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" ? "
|
|
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("
|
|
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(
|
|
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:
|
|
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
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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("
|
|
16125
|
+
throw new Error("It's a directory, not a file.");
|
|
15869
16126
|
}
|
|
15870
16127
|
const content = await readFile15(absolute);
|
|
15871
|
-
const fileName =
|
|
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("
|
|
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}:
|
|
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}:
|
|
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 = (
|
|
16517
|
+
const failed = (text, detail) => ({
|
|
16261
16518
|
ok: false,
|
|
16262
16519
|
code: "continue-failed",
|
|
16263
|
-
|
|
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",
|
|
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",
|
|
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("
|
|
16531
|
+
return failed(serverText("continueSameCli"));
|
|
16275
16532
|
}
|
|
16276
16533
|
const summary = deps.index.find(request.agent, request.sessionId);
|
|
16277
|
-
if (summary === null) return failed("
|
|
16278
|
-
if (summary.cwd.length === 0) return failed("
|
|
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("
|
|
16540
|
+
return failed(serverText("continueReadFailed"), messageOf(error));
|
|
16284
16541
|
}
|
|
16285
|
-
if (source === null) return failed("
|
|
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.
|
|
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,
|
|
16569
|
+
return { ok: false, code: error.code, text: error.text, ...error.detail !== void 0 ? { detail: error.detail } : {} };
|
|
16313
16570
|
}
|
|
16314
|
-
return failed("
|
|
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("
|
|
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("[
|
|
16632
|
+
console.warn("[files] couldn't open the path:", error.message);
|
|
16376
16633
|
});
|
|
16377
16634
|
child.unref();
|
|
16378
16635
|
} catch (error) {
|
|
16379
|
-
console.warn("[
|
|
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
|
-
`[
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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",
|
|
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",
|
|
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",
|
|
16783
|
+
return { kind: "refused", text: serverText("vaultTargetInsideCurrent") };
|
|
16525
16784
|
}
|
|
16526
16785
|
if (isSameOrInside(current, target, options.platform)) {
|
|
16527
|
-
return { kind: "refused",
|
|
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(
|
|
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(`
|
|
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(`
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
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 = "
|
|
16740
|
-
var FOREIGN_FORMAT = "
|
|
16741
|
-
var UNSAFE_SESSION_ID = "
|
|
16742
|
-
var
|
|
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(`
|
|
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
|
-
|
|
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 :
|
|
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 = "
|
|
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("[
|
|
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 =
|
|
17054
|
-
this.log.warn(`[
|
|
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 =
|
|
17060
|
-
this.log.warn(`[
|
|
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 =
|
|
17075
|
-
this.log.warn(`[
|
|
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(`[
|
|
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(`[
|
|
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) =>
|
|
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(
|
|
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(
|
|
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 =
|
|
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
|
|
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
|
|
17366
|
-
|
|
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("
|
|
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(
|
|
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("
|
|
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(),
|
|
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.
|
|
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("
|
|
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(
|
|
17806
|
+
if (error instanceof VaultBusyError) throw new VaultError(busyText(error));
|
|
17542
17807
|
if (error instanceof VaultError) throw error;
|
|
17543
|
-
throw new VaultError(
|
|
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("
|
|
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("
|
|
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(
|
|
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("
|
|
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(`[
|
|
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(
|
|
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 ?
|
|
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(
|
|
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("
|
|
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("[
|
|
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,
|
|
17888
|
-
const payload = { type: "error", code,
|
|
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,
|
|
18167
|
+
const imageStyleFor = (socket, terminalId, unsupported) => {
|
|
17903
18168
|
if (registry.get(terminalId) === null) {
|
|
17904
|
-
sendError(socket, "unknown-terminal", "
|
|
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",
|
|
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", "
|
|
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.
|
|
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("[
|
|
18042
|
-
sendError(socket, "vault-failed", "
|
|
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(`[
|
|
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.
|
|
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.
|
|
18354
|
+
sendError(socket, "notes-failed", error.text);
|
|
18094
18355
|
return;
|
|
18095
18356
|
}
|
|
18096
18357
|
sendError(
|
|
18097
18358
|
socket,
|
|
18098
18359
|
"internal",
|
|
18099
|
-
"
|
|
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", "
|
|
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.
|
|
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 ?
|
|
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", `
|
|
18143
|
-
sendError(socket, "bad-message", "
|
|
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", `
|
|
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
|
-
|
|
18439
|
+
serverText("imagesPerMessage", { max: MAX_IMAGES_PER_SUBMIT })
|
|
18170
18440
|
);
|
|
18171
18441
|
break;
|
|
18172
18442
|
}
|
|
18173
|
-
if (images.length > 0 && imageStyleFor(socket, terminalId, "
|
|
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", "
|
|
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 ?
|
|
18490
|
+
error instanceof PasteImageError ? error.text : serverText("pasteImageFailed"),
|
|
18221
18491
|
detail
|
|
18222
18492
|
);
|
|
18223
18493
|
return;
|
|
18224
18494
|
}
|
|
18225
|
-
const
|
|
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,
|
|
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 ?
|
|
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
|
-
"
|
|
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", "
|
|
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 =
|
|
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", `
|
|
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
|
-
"
|
|
18585
|
+
serverText("answerSendFailed"),
|
|
18291
18586
|
detail
|
|
18292
18587
|
);
|
|
18293
18588
|
}
|
|
18294
18589
|
})();
|
|
18295
18590
|
break;
|
|
18296
18591
|
}
|
|
18297
|
-
const interruptedAnswer = () => sendError(socket, "answer-failed", "
|
|
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",
|
|
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",
|
|
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", "
|
|
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", "
|
|
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.
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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.
|
|
18746
|
+
sendError(socket, error.code, error.text, error.detail, message.requestId);
|
|
18452
18747
|
} else {
|
|
18453
18748
|
sendError(
|
|
18454
18749
|
socket,
|
|
18455
18750
|
"internal",
|
|
18456
|
-
"
|
|
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", "
|
|
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.
|
|
18773
|
+
sendError(socket, error.code, error.text, error.detail);
|
|
18479
18774
|
} else {
|
|
18480
18775
|
sendError(
|
|
18481
18776
|
socket,
|
|
18482
18777
|
"internal",
|
|
18483
|
-
"
|
|
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", "
|
|
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", "
|
|
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", "
|
|
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", "
|
|
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", "
|
|
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", "
|
|
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", "
|
|
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", "
|
|
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", "
|
|
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, "
|
|
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", "
|
|
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, "
|
|
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", "
|
|
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, "
|
|
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", "
|
|
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, "
|
|
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", "
|
|
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, "
|
|
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.
|
|
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", "
|
|
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", "
|
|
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", "
|
|
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", "
|
|
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", "
|
|
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", "
|
|
19290
|
+
sendError(socket, "submit-failed", serverText("noteNoAgent"));
|
|
18996
19291
|
return;
|
|
18997
19292
|
case "no-ready-signal":
|
|
18998
|
-
sendError(socket, "agent-unsupported", "
|
|
19293
|
+
sendError(socket, "agent-unsupported", serverText("noteNoReadySignal"));
|
|
18999
19294
|
return;
|
|
19000
19295
|
case "no-images":
|
|
19001
|
-
sendError(socket, "agent-unsupported", "
|
|
19296
|
+
sendError(socket, "agent-unsupported", serverText("noteImagesUnsupported"));
|
|
19002
19297
|
return;
|
|
19003
19298
|
case "not-ready":
|
|
19004
|
-
sendError(socket, "submit-failed", "
|
|
19299
|
+
sendError(socket, "submit-failed", serverText("noteCliNotStarted"));
|
|
19005
19300
|
return;
|
|
19006
19301
|
case "image-failed":
|
|
19007
|
-
sendError(socket, "submit-failed", "
|
|
19302
|
+
sendError(socket, "submit-failed", serverText("noteImageFailed"), outcome.detail);
|
|
19008
19303
|
return;
|
|
19009
19304
|
case "interrupted":
|
|
19010
|
-
sendError(socket, "submit-failed", "
|
|
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
|
-
"
|
|
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, {
|
|
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
|
-
"
|
|
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.
|
|
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("
|
|
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.
|
|
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
|
|
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(
|
|
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("[
|
|
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 =
|
|
19702
|
+
var serverDir = path47.dirname(fileURLToPath2(import.meta.url));
|
|
19403
19703
|
var isPackaged = true;
|
|
19404
|
-
var webRoot = isPackaged ? serverDir :
|
|
19405
|
-
var webDist = isPackaged ?
|
|
19406
|
-
var repoRoot =
|
|
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
|
|
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(
|
|
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(
|
|
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 =
|
|
19746
|
+
const indexHtml = path47.join(webDist, "index.html");
|
|
19444
19747
|
if (!existsSync4(indexHtml)) {
|
|
19445
19748
|
throw new Error(
|
|
19446
|
-
`
|
|
19447
|
-
|
|
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(
|
|
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(`
|
|
19469
|
-
console.log(`
|
|
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(`
|
|
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("
|
|
19485
|
-
console.log("
|
|
19486
|
-
console.log("
|
|
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(`
|
|
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("
|
|
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(`
|
|
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("\
|
|
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
|
|
19894
|
+
console.error(`Agent Workbench couldn't start:
|
|
19591
19895
|
${detail}`);
|
|
19592
19896
|
process.exit(1);
|
|
19593
19897
|
});
|