agent-workbench 0.8.1 → 0.9.0
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/README.md +17 -6
- package/dist/server.js +1852 -376
- package/dist/web/assets/de-5zSaN2t-.js +5 -0
- package/dist/web/assets/es-CFiCJcc8.js +5 -0
- package/dist/web/assets/fr-C7R68L38.js +5 -0
- package/dist/web/assets/{highlight-BUWAzUc6.js → highlight-BsYn5BK8.js} +1 -1
- package/dist/web/assets/index-fLdlIaV_.js +163 -0
- package/dist/web/assets/index-vwOssvpV.css +32 -0
- package/dist/web/assets/ja-CsWmKRl-.js +5 -0
- package/dist/web/assets/ko-DakWHUDB.js +5 -0
- package/dist/web/assets/pt-BR-CULiM_-O.js +5 -0
- package/dist/web/assets/ru-D8gI2TvJ.js +5 -0
- package/dist/web/assets/zh-CN-BYr9GmHu.js +5 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/de-CDXh9n3d.js +0 -1
- package/dist/web/assets/es-CLg7TmrV.js +0 -1
- package/dist/web/assets/fr-Pnm-P-lG.js +0 -1
- package/dist/web/assets/index-B0Pd_BkE.css +0 -32
- package/dist/web/assets/index-Hua8TqlR.js +0 -156
- package/dist/web/assets/ja-COKYooK_.js +0 -1
- package/dist/web/assets/ko-CGAnMMHl.js +0 -1
- package/dist/web/assets/pt-BR-DPgHVjMQ.js +0 -1
- package/dist/web/assets/ru-BfcWzg67.js +0 -1
- package/dist/web/assets/zh-CN-Xcl7z1Nt.js +0 -1
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 path77 from "node:path";
|
|
7
7
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
8
8
|
import express from "express";
|
|
9
9
|
|
|
@@ -83,6 +83,27 @@ function serverText(key, ...params) {
|
|
|
83
83
|
const [values] = params;
|
|
84
84
|
return values === void 0 ? { key } : { key, params: values };
|
|
85
85
|
}
|
|
86
|
+
function parseServerText(value, depth = 0) {
|
|
87
|
+
const record = asRecord(value);
|
|
88
|
+
if (record === null) return null;
|
|
89
|
+
const key = asString(record["key"]);
|
|
90
|
+
if (key === null || key.length === 0) return null;
|
|
91
|
+
const rawParams = record["params"];
|
|
92
|
+
if (rawParams === void 0) return { key };
|
|
93
|
+
const paramsRecord = asRecord(rawParams);
|
|
94
|
+
if (paramsRecord === null) return null;
|
|
95
|
+
const params = {};
|
|
96
|
+
for (const [name, param] of Object.entries(paramsRecord)) {
|
|
97
|
+
if (typeof param === "string" || typeof param === "number" && Number.isFinite(param)) {
|
|
98
|
+
params[name] = param;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const nested = depth === 0 ? parseServerText(param, depth + 1) : null;
|
|
102
|
+
if (nested === null) return null;
|
|
103
|
+
params[name] = nested;
|
|
104
|
+
}
|
|
105
|
+
return { key, params };
|
|
106
|
+
}
|
|
86
107
|
var ServerTextError = class extends Error {
|
|
87
108
|
text;
|
|
88
109
|
constructor(text, options) {
|
|
@@ -241,6 +262,9 @@ function sameQuestionItem(a, b) {
|
|
|
241
262
|
return left.length > 0 && right.length > 0 && (left.startsWith(right) || right.startsWith(left));
|
|
242
263
|
});
|
|
243
264
|
}
|
|
265
|
+
function askedBeforeLaunch(askedAt, launchedAt) {
|
|
266
|
+
return askedAt > 0 && launchedAt !== null && askedAt < launchedAt;
|
|
267
|
+
}
|
|
244
268
|
var CONVERSATION_IMAGE_SOURCES = [
|
|
245
269
|
"content",
|
|
246
270
|
"attachment",
|
|
@@ -498,6 +522,17 @@ function parseComposerDraft(value) {
|
|
|
498
522
|
return { text, pasted: pasted2 };
|
|
499
523
|
}
|
|
500
524
|
|
|
525
|
+
// packages/shared/src/scheduled-messages.ts
|
|
526
|
+
var MAX_SCHEDULED_MESSAGES = 50;
|
|
527
|
+
var MAX_SCHEDULED_TEXT_CHARS = 2e5;
|
|
528
|
+
var MAX_SCHEDULE_AHEAD_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
529
|
+
var SCHEDULE_LATE_LIMIT_MS = 60 * 60 * 1e3;
|
|
530
|
+
function scheduleTimeProblem(sendAt, now) {
|
|
531
|
+
if (!Number.isFinite(sendAt) || sendAt <= now) return "past";
|
|
532
|
+
if (sendAt - now > MAX_SCHEDULE_AHEAD_MS) return "too-far";
|
|
533
|
+
return null;
|
|
534
|
+
}
|
|
535
|
+
|
|
501
536
|
// packages/shared/src/memory.ts
|
|
502
537
|
var MEMORY_DIR = ".agents/memory";
|
|
503
538
|
var MEMORY_INDEX_FILE = "MEMORY.md";
|
|
@@ -857,13 +892,16 @@ function asAgentIdList(value) {
|
|
|
857
892
|
function parseAppSettingsChange(record) {
|
|
858
893
|
const rawShell = record["consoleShell"];
|
|
859
894
|
const rawAgents = record["disabledAgents"];
|
|
895
|
+
const rawScheduled = record["scheduledMessages"];
|
|
860
896
|
const consoleShell = rawShell === void 0 ? void 0 : asConsoleShellId(rawShell);
|
|
861
897
|
const disabledAgents = rawAgents === void 0 ? void 0 : asAgentIdList(rawAgents);
|
|
862
|
-
|
|
863
|
-
if (consoleShell ===
|
|
898
|
+
const scheduledMessages = rawScheduled === void 0 ? void 0 : typeof rawScheduled === "boolean" ? rawScheduled : null;
|
|
899
|
+
if (consoleShell === null || disabledAgents === null || scheduledMessages === null) return null;
|
|
900
|
+
if (consoleShell === void 0 && disabledAgents === void 0 && scheduledMessages === void 0) return null;
|
|
864
901
|
return {
|
|
865
902
|
...consoleShell !== void 0 ? { consoleShell } : {},
|
|
866
|
-
...disabledAgents !== void 0 ? { disabledAgents } : {}
|
|
903
|
+
...disabledAgents !== void 0 ? { disabledAgents } : {},
|
|
904
|
+
...scheduledMessages !== void 0 ? { scheduledMessages } : {}
|
|
867
905
|
};
|
|
868
906
|
}
|
|
869
907
|
|
|
@@ -1300,6 +1338,21 @@ function parseClientMessage(raw) {
|
|
|
1300
1338
|
const draft = parseComposerDraft(record["draft"]);
|
|
1301
1339
|
return terminalId === null || draft === null ? null : { type: "composer.draft", terminalId, draft };
|
|
1302
1340
|
}
|
|
1341
|
+
case "schedule.create": {
|
|
1342
|
+
const requestId = asNonEmptyString(record["requestId"]);
|
|
1343
|
+
const terminalId = asNonEmptyString(record["terminalId"]);
|
|
1344
|
+
const text = asString(record["text"]);
|
|
1345
|
+
const sendAt = asFiniteNumber(record["sendAt"]);
|
|
1346
|
+
return requestId === null || terminalId === null || text === null || sendAt === null ? null : { type: "schedule.create", requestId, terminalId, text, sendAt };
|
|
1347
|
+
}
|
|
1348
|
+
case "schedule.cancel": {
|
|
1349
|
+
const id = asNonEmptyString(record["id"]);
|
|
1350
|
+
return id === null ? null : { type: "schedule.cancel", id };
|
|
1351
|
+
}
|
|
1352
|
+
case "schedule.sendNow": {
|
|
1353
|
+
const id = asNonEmptyString(record["id"]);
|
|
1354
|
+
return id === null ? null : { type: "schedule.sendNow", id };
|
|
1355
|
+
}
|
|
1303
1356
|
case "settings.update": {
|
|
1304
1357
|
const change = parseAppSettingsChange(record);
|
|
1305
1358
|
return change === null ? null : { type: "settings.update", ...change };
|
|
@@ -1382,8 +1435,8 @@ function parseClientMessage(raw) {
|
|
|
1382
1435
|
case "worktree.changesDiff": {
|
|
1383
1436
|
const requestId = asNonEmptyString(record["requestId"]);
|
|
1384
1437
|
const worktreeId = asNonEmptyString(record["worktreeId"]);
|
|
1385
|
-
const
|
|
1386
|
-
return requestId === null || worktreeId === null ||
|
|
1438
|
+
const path78 = asNonEmptyString(record["path"]);
|
|
1439
|
+
return requestId === null || worktreeId === null || path78 === null ? null : { type: "worktree.changesDiff", requestId, worktreeId, path: path78 };
|
|
1387
1440
|
}
|
|
1388
1441
|
case "worktree.requestReview": {
|
|
1389
1442
|
const requestId = asNonEmptyString(record["requestId"]);
|
|
@@ -1521,6 +1574,15 @@ function notesImagesDir() {
|
|
|
1521
1574
|
function composerDraftsPath() {
|
|
1522
1575
|
return path.join(appConfigDir(), "composer-drafts.json");
|
|
1523
1576
|
}
|
|
1577
|
+
function scheduledMessagesPath() {
|
|
1578
|
+
return path.join(appConfigDir(), "scheduled-messages.json");
|
|
1579
|
+
}
|
|
1580
|
+
function scheduledClaimsDir() {
|
|
1581
|
+
return path.join(appConfigDir(), "scheduled-claims");
|
|
1582
|
+
}
|
|
1583
|
+
function runningInstancesDir() {
|
|
1584
|
+
return path.join(appConfigDir(), "instances");
|
|
1585
|
+
}
|
|
1524
1586
|
function catalogTempDir() {
|
|
1525
1587
|
return path.join(tmpdir(), "agent-workbench", "agy-catalog");
|
|
1526
1588
|
}
|
|
@@ -2970,9 +3032,9 @@ function toTitle(raw) {
|
|
|
2970
3032
|
return text;
|
|
2971
3033
|
}
|
|
2972
3034
|
function withoutPastedText(raw) {
|
|
2973
|
-
const
|
|
2974
|
-
const typed =
|
|
2975
|
-
return typed.trim().length > 0 ? typed :
|
|
3035
|
+
const normalized2 = raw.replace(/\r\n?/g, "\n");
|
|
3036
|
+
const typed = normalized2.replace(pastedBlockPattern(), "$1");
|
|
3037
|
+
return typed.trim().length > 0 ? typed : normalized2.replace(pastedBoundaryPattern(), " ");
|
|
2976
3038
|
}
|
|
2977
3039
|
|
|
2978
3040
|
// packages/server/src/agents/antigravity/session-follower.ts
|
|
@@ -3240,12 +3302,12 @@ function blockPattern(name) {
|
|
|
3240
3302
|
return new RegExp(`<${name}>([\\s\\S]*?)</${name}>`, "g");
|
|
3241
3303
|
}
|
|
3242
3304
|
function unwrapUserInput(content) {
|
|
3243
|
-
const
|
|
3244
|
-
const close =
|
|
3305
|
+
const open10 = content.indexOf(REQUEST_OPEN);
|
|
3306
|
+
const close = open10 === -1 ? -1 : content.indexOf(REQUEST_CLOSE, open10 + REQUEST_OPEN.length);
|
|
3245
3307
|
let text;
|
|
3246
3308
|
let rest;
|
|
3247
|
-
if (
|
|
3248
|
-
text = content.slice(
|
|
3309
|
+
if (open10 !== -1 && close !== -1) {
|
|
3310
|
+
text = content.slice(open10 + REQUEST_OPEN.length, close).trim();
|
|
3249
3311
|
rest = content.slice(close + REQUEST_CLOSE.length);
|
|
3250
3312
|
} else {
|
|
3251
3313
|
text = content.replace(blockPattern("ADDITIONAL_METADATA"), "").replace(blockPattern("USER_SETTINGS_CHANGE"), "").trim();
|
|
@@ -4068,6 +4130,15 @@ function createAntigravityQueueEdit() {
|
|
|
4068
4130
|
return { run: (io, context) => runAntigravityQueueEdit(io, context) };
|
|
4069
4131
|
}
|
|
4070
4132
|
|
|
4133
|
+
// packages/server/src/agents/antigravity/ready-screen.ts
|
|
4134
|
+
function antigravityReadyScreen(lines) {
|
|
4135
|
+
const screen = readAntigravityQueueScreen(lines);
|
|
4136
|
+
if (screen === null || screen.queued) return false;
|
|
4137
|
+
const tail = lines.filter((line) => line.trim() !== "").slice(-3);
|
|
4138
|
+
if (tail.some((line) => /esc to cancel|Generating/.test(line))) return false;
|
|
4139
|
+
return tail.some((line) => /^\s*\? for shortcuts/.test(line));
|
|
4140
|
+
}
|
|
4141
|
+
|
|
4071
4142
|
// packages/server/src/agents/antigravity/settings.ts
|
|
4072
4143
|
import { readFile as readFile4, stat as stat7 } from "node:fs/promises";
|
|
4073
4144
|
var SETTINGS_MAX_BYTES = 256 * 1024;
|
|
@@ -4652,6 +4723,8 @@ var ANTIGRAVITY_BASE_CAPABILITIES = {
|
|
|
4652
4723
|
forceSend: true,
|
|
4653
4724
|
// La flecha arriba saca la cola al editor, y Ctrl+U Ctrl+K lo vacian sin cortar el turno (1.4.4, §6.35.1).
|
|
4654
4725
|
editQueue: true,
|
|
4726
|
+
// Hito 52: sin status line no se sabe si trabaja, y no se le programa nada.
|
|
4727
|
+
scheduledSend: false,
|
|
4655
4728
|
/*
|
|
4656
4729
|
Hito 49: `--mode plan`. Medido con la 1.4.0 al lanzar: arranca en "Plan
|
|
4657
4730
|
mode: research & plan only" y el ciclo sigue desde ahi. Que hace con
|
|
@@ -4663,7 +4736,13 @@ var ANTIGRAVITY_STATUS_LINE_CAPABILITIES = {
|
|
|
4663
4736
|
...ANTIGRAVITY_BASE_CAPABILITIES,
|
|
4664
4737
|
statusSource: true,
|
|
4665
4738
|
readySignal: READY_SIGNAL_SAFE,
|
|
4666
|
-
contextWindowSource: "status-line"
|
|
4739
|
+
contextWindowSource: "status-line",
|
|
4740
|
+
/*
|
|
4741
|
+
Hito 52 (§12.17): con la status line si. Al retomar una conversacion, el
|
|
4742
|
+
primer "libre" escrito despues de lanzarla llego cuando su pantalla ya
|
|
4743
|
+
aceptaba un pegado (3 de 3), y la caja vacia lo confirma.
|
|
4744
|
+
*/
|
|
4745
|
+
scheduledSend: true
|
|
4667
4746
|
};
|
|
4668
4747
|
var ANTIGRAVITY_INPUT = {
|
|
4669
4748
|
imageReference: null,
|
|
@@ -4858,6 +4937,8 @@ function createAntigravityAdapter(options = {}) {
|
|
|
4858
4937
|
es lo que la app le mando con el agente trabajando. "Enviar ya" es Alt+M.
|
|
4859
4938
|
*/
|
|
4860
4939
|
promptQueue: { source: "app", forceSend: "alt-m" },
|
|
4940
|
+
// Hito 52 (§12.17): con la status line, que dice si trabaja; lo escrito se confirma cuando pasa a trabajando.
|
|
4941
|
+
scheduled: { kind: "screen", readyScreen: antigravityReadyScreen, confirmTimeoutMs: 6e4 },
|
|
4861
4942
|
// Sacar la cola para editarla en el cuadro (§6.35.1): vuelve lo que la app le mando.
|
|
4862
4943
|
queueEdit: createAntigravityQueueEdit(),
|
|
4863
4944
|
/**
|
|
@@ -5087,6 +5168,17 @@ async function readAgentDefaults(cwd, sessionModel = null) {
|
|
|
5087
5168
|
import { readdir as readdir7, readFile as readFile8 } from "node:fs/promises";
|
|
5088
5169
|
import path14 from "node:path";
|
|
5089
5170
|
|
|
5171
|
+
// packages/server/src/process-alive.ts
|
|
5172
|
+
function processAlive(pid) {
|
|
5173
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
5174
|
+
try {
|
|
5175
|
+
process.kill(pid, 0);
|
|
5176
|
+
return true;
|
|
5177
|
+
} catch (error) {
|
|
5178
|
+
return error.code === "EPERM";
|
|
5179
|
+
}
|
|
5180
|
+
}
|
|
5181
|
+
|
|
5090
5182
|
// packages/server/src/agents/claude-code/paths.ts
|
|
5091
5183
|
import { homedir as homedir4, tmpdir as tmpdir2 } from "node:os";
|
|
5092
5184
|
import path13 from "node:path";
|
|
@@ -5127,7 +5219,16 @@ function readStatus(value) {
|
|
|
5127
5219
|
statusUpdatedAt: typeof updatedAt === "number" && Number.isFinite(updatedAt) ? updatedAt : null
|
|
5128
5220
|
};
|
|
5129
5221
|
}
|
|
5222
|
+
function fileCounts(record, isAlive) {
|
|
5223
|
+
const pid = record["pid"];
|
|
5224
|
+
return typeof pid !== "number" || isAlive(pid);
|
|
5225
|
+
}
|
|
5130
5226
|
var CliStatusWatcher = class {
|
|
5227
|
+
/** Inyectable para el chequeo, que no puede inventar procesos vivos. */
|
|
5228
|
+
constructor(isAlive = processAlive) {
|
|
5229
|
+
this.isAlive = isAlive;
|
|
5230
|
+
}
|
|
5231
|
+
isAlive;
|
|
5131
5232
|
subscriptions = /* @__PURE__ */ new Set();
|
|
5132
5233
|
timer = null;
|
|
5133
5234
|
reading = false;
|
|
@@ -5176,6 +5277,34 @@ var CliStatusWatcher = class {
|
|
|
5176
5277
|
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
5177
5278
|
}
|
|
5178
5279
|
}
|
|
5280
|
+
/**
|
|
5281
|
+
* true si algun proceso vivo de la CLI tiene esa conversacion (hito 51,
|
|
5282
|
+
* §6.39). Cuenta tambien el archivo que todavia no escribio su estado: la CLI
|
|
5283
|
+
* lo crea antes de decir si esta libre (medido con la 2.1.310), y ese proceso
|
|
5284
|
+
* ya tiene la conversacion abierta.
|
|
5285
|
+
*/
|
|
5286
|
+
async isOpen(sessionId) {
|
|
5287
|
+
const root = cliSessionsStateRoot();
|
|
5288
|
+
let names;
|
|
5289
|
+
try {
|
|
5290
|
+
names = await readdir7(root);
|
|
5291
|
+
} catch {
|
|
5292
|
+
return false;
|
|
5293
|
+
}
|
|
5294
|
+
for (const name of names) {
|
|
5295
|
+
if (!name.endsWith(".json")) continue;
|
|
5296
|
+
let parsed;
|
|
5297
|
+
try {
|
|
5298
|
+
parsed = JSON.parse(await readFile8(path14.join(root, name), "utf8"));
|
|
5299
|
+
} catch {
|
|
5300
|
+
continue;
|
|
5301
|
+
}
|
|
5302
|
+
if (typeof parsed !== "object" || parsed === null) continue;
|
|
5303
|
+
const record = parsed;
|
|
5304
|
+
if (record["sessionId"] === sessionId && fileCounts(record, this.isAlive)) return true;
|
|
5305
|
+
}
|
|
5306
|
+
return false;
|
|
5307
|
+
}
|
|
5179
5308
|
dispose() {
|
|
5180
5309
|
this.subscriptions.clear();
|
|
5181
5310
|
this.stop();
|
|
@@ -5214,8 +5343,10 @@ var CliStatusWatcher = class {
|
|
|
5214
5343
|
continue;
|
|
5215
5344
|
}
|
|
5216
5345
|
if (typeof parsed !== "object" || parsed === null) continue;
|
|
5217
|
-
const
|
|
5346
|
+
const record = parsed;
|
|
5347
|
+
const sessionId = record["sessionId"];
|
|
5218
5348
|
if (typeof sessionId !== "string" || sessionId.length === 0) continue;
|
|
5349
|
+
if (!fileCounts(record, this.isAlive)) continue;
|
|
5219
5350
|
const status = readStatus(parsed);
|
|
5220
5351
|
if (status !== null) found.set(sessionId, status);
|
|
5221
5352
|
}
|
|
@@ -5896,11 +6027,11 @@ function relativeInside(root, absolute) {
|
|
|
5896
6027
|
}
|
|
5897
6028
|
function planFileNameOf(value) {
|
|
5898
6029
|
if (typeof value !== "string" || value.length === 0) return null;
|
|
5899
|
-
const
|
|
6030
|
+
const normalized2 = value.replace(/\\/g, "/");
|
|
5900
6031
|
const marker = "/.claude/plans/";
|
|
5901
|
-
const at =
|
|
6032
|
+
const at = normalized2.toLowerCase().lastIndexOf(marker);
|
|
5902
6033
|
if (at === -1) return null;
|
|
5903
|
-
const name =
|
|
6034
|
+
const name = normalized2.slice(at + marker.length);
|
|
5904
6035
|
if (name.length === 0 || name.includes("/")) return null;
|
|
5905
6036
|
if (name === "." || name === "..") return null;
|
|
5906
6037
|
return name;
|
|
@@ -5980,18 +6111,18 @@ var BranchTracker = class {
|
|
|
5980
6111
|
}
|
|
5981
6112
|
/** La linea y todas las de las que cuelga. */
|
|
5982
6113
|
pathFrom(leaf) {
|
|
5983
|
-
const
|
|
6114
|
+
const path78 = /* @__PURE__ */ new Set();
|
|
5984
6115
|
let current = leaf;
|
|
5985
|
-
while (current !== null && !
|
|
6116
|
+
while (current !== null && !path78.has(current)) {
|
|
5986
6117
|
const node = this.nodes.get(current);
|
|
5987
6118
|
if (node === void 0) break;
|
|
5988
|
-
|
|
6119
|
+
path78.add(current);
|
|
5989
6120
|
current = node.parent;
|
|
5990
6121
|
}
|
|
5991
|
-
return
|
|
6122
|
+
return path78;
|
|
5992
6123
|
}
|
|
5993
6124
|
recompute() {
|
|
5994
|
-
const
|
|
6125
|
+
const path78 = this.pathFrom(this.leaf());
|
|
5995
6126
|
const children = /* @__PURE__ */ new Map();
|
|
5996
6127
|
for (const [uuid, node] of this.nodes) {
|
|
5997
6128
|
const key = node.parent !== null && this.nodes.has(node.parent) ? node.parent : null;
|
|
@@ -6000,9 +6131,9 @@ var BranchTracker = class {
|
|
|
6000
6131
|
else list.push(uuid);
|
|
6001
6132
|
}
|
|
6002
6133
|
const roots = [];
|
|
6003
|
-
for (const uuid of children.get(null) ?? []) if (!
|
|
6004
|
-
for (const uuid of
|
|
6005
|
-
for (const child of children.get(uuid) ?? []) if (!
|
|
6134
|
+
for (const uuid of children.get(null) ?? []) if (!path78.has(uuid)) roots.push(uuid);
|
|
6135
|
+
for (const uuid of path78) {
|
|
6136
|
+
for (const child of children.get(uuid) ?? []) if (!path78.has(child)) roots.push(child);
|
|
6006
6137
|
}
|
|
6007
6138
|
const hidden = /* @__PURE__ */ new Set();
|
|
6008
6139
|
for (const root of roots) {
|
|
@@ -8019,6 +8150,12 @@ var CLAUDE_CODE_CAPABILITIES = {
|
|
|
8019
8150
|
forceSend: true,
|
|
8020
8151
|
// La flecha arriba saca la cola a su caja, y Ctrl+U Ctrl+K la vacian sin cortar el turno (2.1.309, §6.35.1).
|
|
8021
8152
|
editQueue: true,
|
|
8153
|
+
/*
|
|
8154
|
+
Hito 51: su archivo de `sessions/` dice si esta libre, si otro proceso tiene
|
|
8155
|
+
la conversacion (con el pid vivo: el de una CLI matada queda, medido con la
|
|
8156
|
+
2.1.310) y cuando quedo lista despues de abrirla (§6.39).
|
|
8157
|
+
*/
|
|
8158
|
+
scheduledSend: true,
|
|
8022
8159
|
/*
|
|
8023
8160
|
Hito 49: `--permission-mode plan`. Medido con la 2.1.304 y el pedido de
|
|
8024
8161
|
revision: corrio git log, diff y status sin pedir permiso, no ofrecio salir
|
|
@@ -8052,6 +8189,8 @@ function createStatusSource(watcher) {
|
|
|
8052
8189
|
)
|
|
8053
8190
|
),
|
|
8054
8191
|
waitUntilReady: (sessionId, timeoutMs) => watcher.waitUntilIdle(sessionId, timeoutMs),
|
|
8192
|
+
// Hito 51: antes de abrir una pestana dormida para un mensaje programado.
|
|
8193
|
+
isOpen: (sessionId) => watcher.isOpen(sessionId),
|
|
8055
8194
|
dispose: () => watcher.dispose()
|
|
8056
8195
|
};
|
|
8057
8196
|
}
|
|
@@ -8672,9 +8811,9 @@ var CodexSessionDiscovery = class {
|
|
|
8672
8811
|
onInput: () => void 0,
|
|
8673
8812
|
onSubmitted: (text) => {
|
|
8674
8813
|
if (released || this.pending.get(terminalId) !== tab) return;
|
|
8675
|
-
const
|
|
8676
|
-
if (
|
|
8677
|
-
tab.submitted.push(
|
|
8814
|
+
const normalized2 = normalizeMessageText(text);
|
|
8815
|
+
if (normalized2.length === 0) return;
|
|
8816
|
+
tab.submitted.push(normalized2);
|
|
8678
8817
|
if (tab.submitted.length > MAX_SUBMITTED) tab.submitted.splice(0, tab.submitted.length - MAX_SUBMITTED);
|
|
8679
8818
|
}
|
|
8680
8819
|
};
|
|
@@ -9089,6 +9228,13 @@ var CodexRolloutSink = class {
|
|
|
9089
9228
|
turns = [];
|
|
9090
9229
|
/** Llamadas del turno en curso sin resultado todavia: `call_id` -> epoch ms. */
|
|
9091
9230
|
openCalls = /* @__PURE__ */ new Map();
|
|
9231
|
+
/**
|
|
9232
|
+
* Desde cuando hay un turno abierto (epoch ms; 0 si su linea no trae hora), o
|
|
9233
|
+
* null. Hito 52: Codex no publica si trabaja, y el reloj de los mensajes
|
|
9234
|
+
* programados no le escribe con un turno abierto (§10.13: le cortaria la
|
|
9235
|
+
* respuesta).
|
|
9236
|
+
*/
|
|
9237
|
+
turnOpenSince = null;
|
|
9092
9238
|
warnedForeignMeta = false;
|
|
9093
9239
|
getUsage() {
|
|
9094
9240
|
return this.usage;
|
|
@@ -9114,7 +9260,13 @@ var CodexRolloutSink = class {
|
|
|
9114
9260
|
}
|
|
9115
9261
|
return false;
|
|
9116
9262
|
}
|
|
9263
|
+
/** Ver `SessionFollower.hasOpenTurn`. Un turno sin hora no se sabe de que proceso es: cuenta. */
|
|
9264
|
+
hasOpenTurn(launchedAt) {
|
|
9265
|
+
if (this.turnOpenSince === null) return false;
|
|
9266
|
+
return this.turnOpenSince === 0 || this.turnOpenSince >= launchedAt;
|
|
9267
|
+
}
|
|
9117
9268
|
reset() {
|
|
9269
|
+
this.turnOpenSince = null;
|
|
9118
9270
|
this.model = null;
|
|
9119
9271
|
this.effort = null;
|
|
9120
9272
|
this.window = null;
|
|
@@ -9196,6 +9348,7 @@ var CodexRolloutSink = class {
|
|
|
9196
9348
|
if (TURN_START_TYPES.includes(kind)) {
|
|
9197
9349
|
this.lastAssistantEventId = null;
|
|
9198
9350
|
this.openCalls.clear();
|
|
9351
|
+
this.turnOpenSince = at;
|
|
9199
9352
|
const window = readWindow(payload["model_context_window"]);
|
|
9200
9353
|
if (window !== null) {
|
|
9201
9354
|
this.window = window;
|
|
@@ -9217,6 +9370,7 @@ var CodexRolloutSink = class {
|
|
|
9217
9370
|
}
|
|
9218
9371
|
if (TURN_END_TYPES.includes(kind)) {
|
|
9219
9372
|
this.openCalls.clear();
|
|
9373
|
+
this.turnOpenSince = null;
|
|
9220
9374
|
const duration = payload["duration_ms"];
|
|
9221
9375
|
const eventId = this.lastAssistantEventId;
|
|
9222
9376
|
this.lastAssistantEventId = null;
|
|
@@ -9450,6 +9604,9 @@ var CodexSessionFollower = class {
|
|
|
9450
9604
|
hasOpenToolCall(launchedAt) {
|
|
9451
9605
|
return this.sink.hasOpenToolCall(launchedAt);
|
|
9452
9606
|
}
|
|
9607
|
+
hasOpenTurn(launchedAt) {
|
|
9608
|
+
return this.sink.hasOpenTurn(launchedAt);
|
|
9609
|
+
}
|
|
9453
9610
|
getState() {
|
|
9454
9611
|
return this.jsonl.getState();
|
|
9455
9612
|
}
|
|
@@ -9597,6 +9754,18 @@ function createCodexHistory(options = {}) {
|
|
|
9597
9754
|
};
|
|
9598
9755
|
}
|
|
9599
9756
|
|
|
9757
|
+
// packages/server/src/agents/codex/ready-screen.ts
|
|
9758
|
+
var BUSY = /esc to interrupt|Working \(|Waiting for startup|esc cancel/;
|
|
9759
|
+
var FOOTER = /\? for shortcuts/;
|
|
9760
|
+
var COMPOSER = /^\s*›\s/;
|
|
9761
|
+
function codexReadyScreen(lines) {
|
|
9762
|
+
const tail = lines.filter((line) => line.trim() !== "").slice(-8);
|
|
9763
|
+
if (tail.some((line) => BUSY.test(line))) return false;
|
|
9764
|
+
const footer = tail.findIndex((line) => FOOTER.test(line));
|
|
9765
|
+
if (footer <= 0) return false;
|
|
9766
|
+
return tail.slice(0, footer).some((line) => COMPOSER.test(line));
|
|
9767
|
+
}
|
|
9768
|
+
|
|
9600
9769
|
// packages/server/src/agents/codex/index.ts
|
|
9601
9770
|
var CODEX_COMMAND = "codex";
|
|
9602
9771
|
var CODEX_INSTALL_URL = "https://learn.chatgpt.com/docs/codex/cli";
|
|
@@ -9632,6 +9801,11 @@ var CODEX_CAPABILITIES = {
|
|
|
9632
9801
|
// No hace cola: un mensaje con el agente trabajando le llega al modelo en el acto (hito 45).
|
|
9633
9802
|
forceSend: false,
|
|
9634
9803
|
editQueue: false,
|
|
9804
|
+
/*
|
|
9805
|
+
Hito 52 (§10.18): por su pantalla, cuando muestra la caja vacia y el
|
|
9806
|
+
historial no tiene un turno abierto. Codex no publica si trabaja.
|
|
9807
|
+
*/
|
|
9808
|
+
scheduledSend: true,
|
|
9635
9809
|
/*
|
|
9636
9810
|
Hito 49: `--sandbox read-only`, al abrir y al reanudar. Medido con la 0.162.1
|
|
9637
9811
|
y un proveedor falso: con la sandbox de Windows del usuario, escribir fallo
|
|
@@ -9718,6 +9892,13 @@ function createCodexAdapter() {
|
|
|
9718
9892
|
},
|
|
9719
9893
|
history: createCodexHistory({ planLimits }),
|
|
9720
9894
|
status: null,
|
|
9895
|
+
/*
|
|
9896
|
+
Hito 52 (§10.18): un mensaje programado se escribe cuando la pantalla
|
|
9897
|
+
muestra la caja vacia. Con el daemon en frio lo escrito espera en la caja
|
|
9898
|
+
("Waiting for startup") y llego a los 40 s: la confirmacion espera dos
|
|
9899
|
+
minutos.
|
|
9900
|
+
*/
|
|
9901
|
+
scheduled: { kind: "screen", readyScreen: codexReadyScreen, confirmTimeoutMs: 12e4 },
|
|
9721
9902
|
planLimits,
|
|
9722
9903
|
defaults: () => Promise.resolve(null),
|
|
9723
9904
|
/**
|
|
@@ -10379,7 +10560,22 @@ FROM part WHERE id = $id AND json_extract(data, '$.type') = 'file'`,
|
|
|
10379
10560
|
* `question`; `$id` y `$s`.
|
|
10380
10561
|
*/
|
|
10381
10562
|
partCallById: `SELECT message_id, json_extract(data, '$.callID') AS call_id
|
|
10382
|
-
FROM part WHERE id = $id AND session_id = $s AND json_extract(data, '$.tool') = 'question'
|
|
10563
|
+
FROM part WHERE id = $id AND session_id = $s AND json_extract(data, '$.tool') = 'question'`,
|
|
10564
|
+
/**
|
|
10565
|
+
* Hito 52 (§11.16). El agente, el modelo y la variante del ultimo mensaje del
|
|
10566
|
+
* usuario de UNA sesion, para mandar un mensaje programado por `prompt_async`
|
|
10567
|
+
* con los de la conversacion: sin agente, OpenCode usa el predeterminado
|
|
10568
|
+
* (medido con la 1.19.4). Solo esos cuatro campos; `$s`. Los del ultimo
|
|
10569
|
+
* instante, en el orden de siempre (regla 3): el ultimo de la lista es el que
|
|
10570
|
+
* vale.
|
|
10571
|
+
*/
|
|
10572
|
+
lastUserPrompt: `SELECT json_extract(data, '$.agent') AS agent,
|
|
10573
|
+
json_extract(data, '$.model.providerID') AS provider_id,
|
|
10574
|
+
json_extract(data, '$.model.modelID') AS model_id,
|
|
10575
|
+
json_extract(data, '$.variant') AS variant
|
|
10576
|
+
FROM message WHERE session_id = $s AND json_extract(data, '$.role') = 'user'
|
|
10577
|
+
AND time_created = (SELECT max(time_created) FROM message WHERE session_id = $s AND json_extract(data, '$.role') = 'user')
|
|
10578
|
+
ORDER BY time_created, id`
|
|
10383
10579
|
});
|
|
10384
10580
|
var OPENCODE_REQUIRED_COLUMNS = Object.freeze([
|
|
10385
10581
|
{ table: "session", columns: ["id", "parent_id", "directory", "title", "time_created", "time_updated", "revert"] },
|
|
@@ -11246,6 +11442,29 @@ var OpenCodeServeClient = class {
|
|
|
11246
11442
|
if (!OPENCODE_SESSION_ID_PATTERN.test(sessionId)) throw new Error("Invalid OpenCode session id.");
|
|
11247
11443
|
await this.request("POST", `/session/${sessionId}/abort`, directory, {});
|
|
11248
11444
|
}
|
|
11445
|
+
/**
|
|
11446
|
+
* Hito 52 (§11.16). Manda un mensaje a la sesion sin esperar la respuesta:
|
|
11447
|
+
* `POST /session/{id}/prompt_async`, que contesta 204 y deja el mensaje en la
|
|
11448
|
+
* base en el acto; el TUI enganchado lo muestra como uno mas (medido con la
|
|
11449
|
+
* 1.19.4, a los ~2 s). Sin agente, OpenCode usa el predeterminado y no el de
|
|
11450
|
+
* la conversacion: por eso se le pasan el agente, el modelo y la variante del
|
|
11451
|
+
* ultimo mensaje del usuario, si se conocen. `not-sent` si la sesion no existe
|
|
11452
|
+
* (404): no se mando nada. Otro fallo lanza: no se sabe si lo tomo.
|
|
11453
|
+
*/
|
|
11454
|
+
async promptAsync(directory, sessionId, text, choice) {
|
|
11455
|
+
if (!OPENCODE_SESSION_ID_PATTERN.test(sessionId)) throw new Error("Invalid OpenCode session id.");
|
|
11456
|
+
const body = { parts: [{ type: "text", text }] };
|
|
11457
|
+
if (choice?.agent) body["agent"] = choice.agent;
|
|
11458
|
+
if (choice?.providerID && choice.modelID) body["model"] = { providerID: choice.providerID, modelID: choice.modelID };
|
|
11459
|
+
if (choice?.variant) body["variant"] = choice.variant;
|
|
11460
|
+
try {
|
|
11461
|
+
await this.request("POST", `/session/${sessionId}/prompt_async`, directory, body);
|
|
11462
|
+
return "sent";
|
|
11463
|
+
} catch (error) {
|
|
11464
|
+
if (error instanceof ServeRequestError && error.status === 404) return "not-sent";
|
|
11465
|
+
throw error;
|
|
11466
|
+
}
|
|
11467
|
+
}
|
|
11249
11468
|
/**
|
|
11250
11469
|
* El flujo de `/global/event`. Reconecta con las esperas de
|
|
11251
11470
|
* `SERVE_RECONNECT_DELAYS_MS` y llama `onReconnect` **cada vez que la conexion
|
|
@@ -12286,6 +12505,22 @@ var OpenCodeServeStatus = class {
|
|
|
12286
12505
|
}
|
|
12287
12506
|
};
|
|
12288
12507
|
|
|
12508
|
+
// packages/server/src/agents/opencode/scheduled.ts
|
|
12509
|
+
var OPENCODE_SCHEDULED_CONFIRM_MS = 3e4;
|
|
12510
|
+
function shortText2(value) {
|
|
12511
|
+
return typeof value === "string" && value.length > 0 && value.length <= 200 ? value : null;
|
|
12512
|
+
}
|
|
12513
|
+
function readLastUserPrompt(db, sessionId) {
|
|
12514
|
+
const row = db.all(OPENCODE_SQL.lastUserPrompt, { $s: sessionId }).at(-1);
|
|
12515
|
+
if (row === void 0) return null;
|
|
12516
|
+
return {
|
|
12517
|
+
agent: shortText2(row.agent),
|
|
12518
|
+
providerID: shortText2(row.provider_id),
|
|
12519
|
+
modelID: shortText2(row.model_id),
|
|
12520
|
+
variant: shortText2(row.variant)
|
|
12521
|
+
};
|
|
12522
|
+
}
|
|
12523
|
+
|
|
12289
12524
|
// packages/server/src/agents/opencode/index.ts
|
|
12290
12525
|
var OPENCODE_COMMAND = "opencode";
|
|
12291
12526
|
var OPENCODE_INSTALL_URL = "https://opencode.ai/docs/";
|
|
@@ -12336,6 +12571,11 @@ var OPENCODE_CAPABILITIES = {
|
|
|
12336
12571
|
forceSend: false,
|
|
12337
12572
|
// El mensaje en cola ya esta escrito en su base (§11.13): no hay de donde sacarlo.
|
|
12338
12573
|
editQueue: false,
|
|
12574
|
+
/*
|
|
12575
|
+
Hito 52 (§11.16): por la API de su `serve` (`prompt_async`), sin tocar la
|
|
12576
|
+
pantalla del TUI, con el agente y el modelo del ultimo mensaje.
|
|
12577
|
+
*/
|
|
12578
|
+
scheduledSend: true,
|
|
12339
12579
|
/*
|
|
12340
12580
|
Hito 49: ninguno. Su agente `plan` pide permiso antes de editar, pero la app
|
|
12341
12581
|
abre el TUI con `opencode attach`, que segun su documentacion no acepta
|
|
@@ -12461,6 +12701,27 @@ function createOpenCodeAdapter(options = {}) {
|
|
|
12461
12701
|
questions,
|
|
12462
12702
|
// La cola la dice la base: un mensaje sin respuesta detras de uno que se esta contestando (hito 45, §11.13).
|
|
12463
12703
|
promptQueue: { source: "session", forceSend: null },
|
|
12704
|
+
/*
|
|
12705
|
+
Hito 52 (§11.16): un mensaje programado va por `prompt_async` del `serve`
|
|
12706
|
+
que la pestana abierta ya levanto, con el agente, el modelo y la variante
|
|
12707
|
+
del ultimo mensaje del usuario. Sin `serve`, o sin poder leer la base, no
|
|
12708
|
+
se manda nada: sin el agente, OpenCode usaria el predeterminado.
|
|
12709
|
+
*/
|
|
12710
|
+
scheduled: {
|
|
12711
|
+
kind: "api",
|
|
12712
|
+
confirmTimeoutMs: OPENCODE_SCHEDULED_CONFIRM_MS,
|
|
12713
|
+
deliver: async (target, text) => {
|
|
12714
|
+
const client = current?.client ?? null;
|
|
12715
|
+
if (client === null) return "not-sent";
|
|
12716
|
+
let choice;
|
|
12717
|
+
try {
|
|
12718
|
+
choice = readLastUserPrompt(db, target.sessionId);
|
|
12719
|
+
} catch {
|
|
12720
|
+
return "not-sent";
|
|
12721
|
+
}
|
|
12722
|
+
return client.promptAsync(target.cwd, target.sessionId, text, choice);
|
|
12723
|
+
}
|
|
12724
|
+
},
|
|
12464
12725
|
/**
|
|
12465
12726
|
* La base que se lee, para el arranque. Solo si hay algo que decir, para que
|
|
12466
12727
|
* quien no usa OpenCode no vea una linea nueva:
|
|
@@ -13086,8 +13347,8 @@ function textOf2(message) {
|
|
|
13086
13347
|
}
|
|
13087
13348
|
function sameFile(a, b) {
|
|
13088
13349
|
const normalize3 = (value) => {
|
|
13089
|
-
const
|
|
13090
|
-
return process.platform === "win32" ?
|
|
13350
|
+
const normalized2 = path36.normalize(value);
|
|
13351
|
+
return process.platform === "win32" ? normalized2.toLowerCase() : normalized2;
|
|
13091
13352
|
};
|
|
13092
13353
|
return normalize3(a) === normalize3(b);
|
|
13093
13354
|
}
|
|
@@ -14086,6 +14347,23 @@ function createPiQueueEdit(timing = DEFAULT_TIMING3) {
|
|
|
14086
14347
|
return { run: (io, context) => runPiQueueEdit(io, context, piDequeueKeys(), timing) };
|
|
14087
14348
|
}
|
|
14088
14349
|
|
|
14350
|
+
// packages/server/src/agents/pi/ready-screen.ts
|
|
14351
|
+
function isBorder2(line) {
|
|
14352
|
+
return /^─{2}.*─{6,}$/.test(line.trim());
|
|
14353
|
+
}
|
|
14354
|
+
function piReadyScreen(lines) {
|
|
14355
|
+
const screen = readPiQueueScreen(lines);
|
|
14356
|
+
if (screen === null || screen.editor.length > 0 || screen.queued) return false;
|
|
14357
|
+
const borders = [];
|
|
14358
|
+
lines.forEach((line, index) => {
|
|
14359
|
+
if (isBorder2(line)) borders.push(index);
|
|
14360
|
+
});
|
|
14361
|
+
const top = borders.at(-2);
|
|
14362
|
+
if (top === void 0) return false;
|
|
14363
|
+
if (/[A-Za-z]/.test(lines[top] ?? "")) return false;
|
|
14364
|
+
return lines.slice(0, top).some((line) => line.trim() !== "" && !isBorder2(line));
|
|
14365
|
+
}
|
|
14366
|
+
|
|
14089
14367
|
// packages/server/src/agents/pi/tab-watch.ts
|
|
14090
14368
|
import { readdir as readdir11, stat as stat25 } from "node:fs/promises";
|
|
14091
14369
|
import path40 from "node:path";
|
|
@@ -14208,9 +14486,9 @@ var PiTabWatch = class {
|
|
|
14208
14486
|
checkTrust() {
|
|
14209
14487
|
if (this.closed || this.sessionId.length === 0) return;
|
|
14210
14488
|
const output = this.context.readOutput();
|
|
14211
|
-
const
|
|
14212
|
-
this.live.setBlocked(this.sessionId,
|
|
14213
|
-
if (
|
|
14489
|
+
const open10 = trustDialogOpen(output);
|
|
14490
|
+
this.live.setBlocked(this.sessionId, open10);
|
|
14491
|
+
if (open10) return;
|
|
14214
14492
|
if (interfaceStarted(output) || this.now() - this.context.launchedAt > STARTUP_DEADLINE_MS) {
|
|
14215
14493
|
if (this.startupTimer !== null) clearInterval(this.startupTimer);
|
|
14216
14494
|
this.startupTimer = null;
|
|
@@ -14380,6 +14658,11 @@ var PI_BASE_CAPABILITIES = {
|
|
|
14380
14658
|
(§6.35.1). Medido en Windows; fuera, la tecla es Alt+Up y no esta medida.
|
|
14381
14659
|
*/
|
|
14382
14660
|
editQueue: process.platform === "win32",
|
|
14661
|
+
/*
|
|
14662
|
+
Hito 52 (§16.21): por su pantalla, cuando muestra el editor vacio con la
|
|
14663
|
+
cabecera ya dibujada (un Enter antes, Pi no lo manda), y libre por su estado.
|
|
14664
|
+
*/
|
|
14665
|
+
scheduledSend: true,
|
|
14383
14666
|
// Hito 49: sin `edit` ni `write` (`PI_REVIEW_TOOLS`), pero su consola puede escribir: a medias (§16.19).
|
|
14384
14667
|
readOnlyLaunch: { mode: null, guard: "tools" }
|
|
14385
14668
|
};
|
|
@@ -14489,6 +14772,8 @@ function createPiAdapter(options = {}) {
|
|
|
14489
14772
|
a mandar lo que devolvio a su editor (hito 45, §16.18).
|
|
14490
14773
|
*/
|
|
14491
14774
|
promptQueue: { source: "app", forceSend: "interrupt-and-resend" },
|
|
14775
|
+
// Hito 52 (§16.21): el mensaje entra a la sesion unos 50 ms despues del Enter.
|
|
14776
|
+
scheduled: { kind: "screen", readyScreen: piReadyScreen, confirmTimeoutMs: 6e4 },
|
|
14492
14777
|
// Sacar la cola para editarla en el cuadro (§6.35.1): vuelve lo que la app le mando.
|
|
14493
14778
|
queueEdit: createPiQueueEdit(),
|
|
14494
14779
|
// `settings.json` no se abre: sin modelo por defecto que anunciar. Al reanudar,
|
|
@@ -15214,12 +15499,13 @@ var ConversationHub = class extends EventEmitter {
|
|
|
15214
15499
|
/**
|
|
15215
15500
|
* Avisa la pregunta leida de la pantalla si cambio. No se avisa la que el
|
|
15216
15501
|
* historial ya tiene abierta: esa tarjeta ya esta en el hilo, con el id de la
|
|
15217
|
-
* CLI, y verla dos veces seria peor que no verla.
|
|
15502
|
+
* CLI, y verla dos veces seria peor que no verla. Una igual que hizo un
|
|
15503
|
+
* proceso anterior no cuenta: esa ya no espera (§5.5.3).
|
|
15218
15504
|
*/
|
|
15219
15505
|
refreshLiveQuestion(terminalId, entry) {
|
|
15220
15506
|
if (this.entries.get(terminalId) !== entry) return;
|
|
15221
15507
|
const read2 = entry.live?.current()?.part ?? null;
|
|
15222
|
-
const pending = read2 === null ? null : this.pendingQuestionOf(entry);
|
|
15508
|
+
const pending = read2 === null ? null : this.pendingQuestionOf(terminalId, entry);
|
|
15223
15509
|
const item = read2?.questions[0];
|
|
15224
15510
|
const shown = read2 !== null && item !== void 0 && !(pending?.questions.some((each) => sameQuestionItem(each, item)) ?? false) ? read2 : null;
|
|
15225
15511
|
if (shown?.toolUseId === entry.liveShown?.toolUseId) return;
|
|
@@ -15308,10 +15594,10 @@ var ConversationHub = class extends EventEmitter {
|
|
|
15308
15594
|
*/
|
|
15309
15595
|
refreshOpenToolCall(terminalId, entry) {
|
|
15310
15596
|
const launchedAt = this.isBlind(entry) ? this.registry.launchedAtOf(terminalId) : null;
|
|
15311
|
-
const
|
|
15312
|
-
if (
|
|
15313
|
-
entry.openToolCall =
|
|
15314
|
-
this.emit("toolCall", terminalId,
|
|
15597
|
+
const open10 = launchedAt !== null && entry.follower.hasOpenToolCall(launchedAt);
|
|
15598
|
+
if (open10 === entry.openToolCall) return;
|
|
15599
|
+
entry.openToolCall = open10;
|
|
15600
|
+
this.emit("toolCall", terminalId, open10);
|
|
15315
15601
|
}
|
|
15316
15602
|
/**
|
|
15317
15603
|
* Recalcula los subagentes que siguen trabajando y avisa si cambiaron
|
|
@@ -15759,10 +16045,15 @@ var ConversationHub = class extends EventEmitter {
|
|
|
15759
16045
|
* Se recorre desde el final: la ultima pregunta sin su `tool_result` es la
|
|
15760
16046
|
* abierta. Si ya tiene resultado —porque se contesto desde la solapa CLI—
|
|
15761
16047
|
* devuelve null, y esa es la unica defensa contra responder dos veces.
|
|
16048
|
+
*
|
|
16049
|
+
* Tambien devuelve null si la hizo un proceso anterior de la CLI (24-10-2026,
|
|
16050
|
+
* §5.5.3): sin resultado en el archivo, pero la CLI de ahora no tiene ese
|
|
16051
|
+
* menu, y las teclas caerian en su prompt. Sin proceso la pregunta sigue
|
|
16052
|
+
* aca, y la escritura falla con el aviso de la pestana sin CLI.
|
|
15762
16053
|
*/
|
|
15763
16054
|
getPendingQuestion(terminalId) {
|
|
15764
16055
|
const entry = this.entries.get(terminalId);
|
|
15765
|
-
return entry === void 0 ? null : this.pendingQuestionOf(entry);
|
|
16056
|
+
return entry === void 0 ? null : this.pendingQuestionOf(terminalId, entry);
|
|
15766
16057
|
}
|
|
15767
16058
|
/**
|
|
15768
16059
|
* La pregunta leida de la pantalla que el hilo esta mostrando (§5.5.4), o
|
|
@@ -15792,7 +16083,7 @@ var ConversationHub = class extends EventEmitter {
|
|
|
15792
16083
|
if (entry === void 0 || entry.live === null || item === void 0) return "unknown";
|
|
15793
16084
|
return entry.live.check(item);
|
|
15794
16085
|
}
|
|
15795
|
-
pendingQuestionOf(entry) {
|
|
16086
|
+
pendingQuestionOf(terminalId, entry) {
|
|
15796
16087
|
const { events } = entry.follower.getTail(PENDING_QUESTION_LOOKBACK);
|
|
15797
16088
|
const answered = /* @__PURE__ */ new Set();
|
|
15798
16089
|
for (const event2 of events) {
|
|
@@ -15801,11 +16092,13 @@ var ConversationHub = class extends EventEmitter {
|
|
|
15801
16092
|
}
|
|
15802
16093
|
}
|
|
15803
16094
|
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
15804
|
-
const
|
|
16095
|
+
const event2 = events[index];
|
|
16096
|
+
const parts = event2?.parts ?? [];
|
|
15805
16097
|
for (let at = parts.length - 1; at >= 0; at -= 1) {
|
|
15806
16098
|
const part = parts[at];
|
|
15807
16099
|
if (part?.kind !== "question") continue;
|
|
15808
|
-
|
|
16100
|
+
if (answered.has(part.toolUseId)) return null;
|
|
16101
|
+
return askedBeforeLaunch(event2?.at ?? 0, this.registry.launchedAtOf(terminalId)) ? null : part;
|
|
15809
16102
|
}
|
|
15810
16103
|
}
|
|
15811
16104
|
return null;
|
|
@@ -18099,9 +18392,9 @@ function findMarkers(content, lines, ignoredFences) {
|
|
|
18099
18392
|
}
|
|
18100
18393
|
continue;
|
|
18101
18394
|
}
|
|
18102
|
-
const
|
|
18103
|
-
const opening =
|
|
18104
|
-
if (opening !== void 0 && !ignoredFences.has(index) && !(opening[0] === "`" && (
|
|
18395
|
+
const open10 = FENCE_OPEN.exec(text);
|
|
18396
|
+
const opening = open10?.[1];
|
|
18397
|
+
if (opening !== void 0 && !ignoredFences.has(index) && !(opening[0] === "`" && (open10?.[2] ?? "").includes("`"))) {
|
|
18105
18398
|
fence = { line: index, char: opening[0] ?? "`", length: opening.length };
|
|
18106
18399
|
continue;
|
|
18107
18400
|
}
|
|
@@ -19317,8 +19610,8 @@ function buildContinuationMessage(input) {
|
|
|
19317
19610
|
}
|
|
19318
19611
|
return sanitizeForPaste(lines.join("\n"));
|
|
19319
19612
|
}
|
|
19320
|
-
function transcriptReferenceFor(
|
|
19321
|
-
return style === "at-quoted" ? fileReference(
|
|
19613
|
+
function transcriptReferenceFor(path78, style) {
|
|
19614
|
+
return style === "at-quoted" ? fileReference(path78, "at-quoted") : `"${path78}"`;
|
|
19322
19615
|
}
|
|
19323
19616
|
function planTranscript(input) {
|
|
19324
19617
|
const turns = splitTurns(input.events);
|
|
@@ -20259,7 +20552,8 @@ function defaultAppSettings() {
|
|
|
20259
20552
|
vault: { enabled: false, dir: null, toolResultMaxChars: VAULT_TOOL_RESULT_DEFAULT_CHARS },
|
|
20260
20553
|
remote: { enabled: false, port: REMOTE_DEFAULT_PORT },
|
|
20261
20554
|
console: { shell: "auto" },
|
|
20262
|
-
agents: { disabled: [] }
|
|
20555
|
+
agents: { disabled: [] },
|
|
20556
|
+
scheduledMessages: { enabled: false }
|
|
20263
20557
|
};
|
|
20264
20558
|
}
|
|
20265
20559
|
function isAbsoluteDir(dir, platform) {
|
|
@@ -20281,6 +20575,8 @@ function parseAppSettings(value, platform, warn = () => void 0) {
|
|
|
20281
20575
|
const agentsRecord = asRecord(record["agents"]);
|
|
20282
20576
|
const disabled = agentsRecord === null ? null : asAgentIdList(agentsRecord["disabled"]);
|
|
20283
20577
|
if (disabled !== null) settings2.agents.disabled = disabled;
|
|
20578
|
+
const scheduledRecord = asRecord(record["scheduledMessages"]);
|
|
20579
|
+
settings2.scheduledMessages.enabled = (scheduledRecord === null ? null : asBoolean(scheduledRecord["enabled"])) ?? false;
|
|
20284
20580
|
const remote = asRecord(record["remote"]);
|
|
20285
20581
|
if (remote !== null) {
|
|
20286
20582
|
settings2.remote.enabled = asBoolean(remote["enabled"]) ?? false;
|
|
@@ -20304,7 +20600,8 @@ function frozen(settings2) {
|
|
|
20304
20600
|
vault: Object.freeze({ ...settings2.vault }),
|
|
20305
20601
|
remote: Object.freeze({ ...settings2.remote }),
|
|
20306
20602
|
console: Object.freeze({ ...settings2.console }),
|
|
20307
|
-
agents: Object.freeze({ disabled: Object.freeze([...settings2.agents.disabled]) })
|
|
20603
|
+
agents: Object.freeze({ disabled: Object.freeze([...settings2.agents.disabled]) }),
|
|
20604
|
+
scheduledMessages: Object.freeze({ ...settings2.scheduledMessages })
|
|
20308
20605
|
});
|
|
20309
20606
|
}
|
|
20310
20607
|
var SettingsStore = class {
|
|
@@ -20360,6 +20657,23 @@ var SettingsStore = class {
|
|
|
20360
20657
|
if (settings2 === null) return;
|
|
20361
20658
|
this.current = frozen({ ...this.current, remote: settings2.remote });
|
|
20362
20659
|
}
|
|
20660
|
+
/**
|
|
20661
|
+
* Relee **solo** "Mensajes programados" (hito 51), por lo mismo que el acceso
|
|
20662
|
+
* remoto: apagarlo desde la otra instancia tiene que valer en esta, que es la
|
|
20663
|
+
* que podria estar por mandar algo. true si cambio.
|
|
20664
|
+
*/
|
|
20665
|
+
async reloadScheduledMessages() {
|
|
20666
|
+
let parsed;
|
|
20667
|
+
try {
|
|
20668
|
+
parsed = JSON.parse(await readFile21(this.filePath, "utf8"));
|
|
20669
|
+
} catch {
|
|
20670
|
+
return false;
|
|
20671
|
+
}
|
|
20672
|
+
const settings2 = parseAppSettings(parsed, this.platform);
|
|
20673
|
+
if (settings2 === null || settings2.scheduledMessages.enabled === this.current.scheduledMessages.enabled) return false;
|
|
20674
|
+
this.current = frozen({ ...this.current, scheduledMessages: settings2.scheduledMessages });
|
|
20675
|
+
return true;
|
|
20676
|
+
}
|
|
20363
20677
|
/** Una marca que cambia cuando el archivo cambia (fecha y tamano), o '' si no esta. */
|
|
20364
20678
|
async stamp() {
|
|
20365
20679
|
try {
|
|
@@ -20381,12 +20695,14 @@ var SettingsStore = class {
|
|
|
20381
20695
|
update(patch) {
|
|
20382
20696
|
const run2 = async () => {
|
|
20383
20697
|
if (patch.remote === void 0) await this.reloadRemote();
|
|
20698
|
+
if (patch.scheduledMessages === void 0) await this.reloadScheduledMessages();
|
|
20384
20699
|
const next = {
|
|
20385
20700
|
version: SETTINGS_VERSION,
|
|
20386
20701
|
vault: { ...this.current.vault },
|
|
20387
20702
|
remote: { ...this.current.remote },
|
|
20388
20703
|
console: { ...this.current.console },
|
|
20389
|
-
agents: { disabled: [...this.current.agents.disabled] }
|
|
20704
|
+
agents: { disabled: [...this.current.agents.disabled] },
|
|
20705
|
+
scheduledMessages: { ...this.current.scheduledMessages }
|
|
20390
20706
|
};
|
|
20391
20707
|
const vault = patch.vault ?? {};
|
|
20392
20708
|
if (vault.enabled !== void 0) next.vault.enabled = vault.enabled;
|
|
@@ -20401,6 +20717,7 @@ var SettingsStore = class {
|
|
|
20401
20717
|
}
|
|
20402
20718
|
if (patch.console?.shell !== void 0) next.console.shell = patch.console.shell;
|
|
20403
20719
|
if (patch.agents?.disabled !== void 0) next.agents.disabled = [...new Set(patch.agents.disabled)];
|
|
20720
|
+
if (patch.scheduledMessages?.enabled !== void 0) next.scheduledMessages.enabled = patch.scheduledMessages.enabled;
|
|
20404
20721
|
const remote = patch.remote ?? {};
|
|
20405
20722
|
if (remote.enabled !== void 0) next.remote.enabled = remote.enabled;
|
|
20406
20723
|
if (remote.port !== void 0) {
|
|
@@ -20960,10 +21277,353 @@ var DraftStore = class {
|
|
|
20960
21277
|
}
|
|
20961
21278
|
};
|
|
20962
21279
|
|
|
21280
|
+
// packages/server/src/scheduled-messages/store.ts
|
|
21281
|
+
import { mkdir as mkdir14, open as open8, readFile as readFile24, readdir as readdir14, rm as rm5, stat as stat34 } from "node:fs/promises";
|
|
21282
|
+
import path56 from "node:path";
|
|
21283
|
+
var STATE_VERSION4 = 1;
|
|
21284
|
+
var ID_PATTERN2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
21285
|
+
function isScheduledMessageId(value) {
|
|
21286
|
+
return ID_PATTERN2.test(value);
|
|
21287
|
+
}
|
|
21288
|
+
function sameOwner(a, b) {
|
|
21289
|
+
return a.agent === b.agent && a.sessionId === b.sessionId;
|
|
21290
|
+
}
|
|
21291
|
+
function parseEntry2(value) {
|
|
21292
|
+
const record = asRecord(value);
|
|
21293
|
+
if (record === null) return null;
|
|
21294
|
+
const id = asNonEmptyString(record["id"]);
|
|
21295
|
+
const agent = asLiteral(record["agent"], AGENT_IDS);
|
|
21296
|
+
const sessionId = asNonEmptyString(record["sessionId"]);
|
|
21297
|
+
const text = asString(record["text"]);
|
|
21298
|
+
const sendAt = asFiniteNumber(record["sendAt"]);
|
|
21299
|
+
const createdAt = asFiniteNumber(record["createdAt"]);
|
|
21300
|
+
if (id === null || !isScheduledMessageId(id) || agent === null || sessionId === null) return null;
|
|
21301
|
+
if (text === null || text.length === 0 || text.length > MAX_SCHEDULED_TEXT_CHARS) return null;
|
|
21302
|
+
if (sendAt === null || createdAt === null) return null;
|
|
21303
|
+
const message = { id, agent, sessionId, text, sendAt, createdAt };
|
|
21304
|
+
if (record["failure"] !== void 0) {
|
|
21305
|
+
const failure = parseServerText(record["failure"]);
|
|
21306
|
+
message.failure = failure ?? { key: "scheduledUncertain" };
|
|
21307
|
+
}
|
|
21308
|
+
return message;
|
|
21309
|
+
}
|
|
21310
|
+
function parseScheduledState(raw) {
|
|
21311
|
+
let parsed;
|
|
21312
|
+
try {
|
|
21313
|
+
parsed = JSON.parse(raw);
|
|
21314
|
+
} catch {
|
|
21315
|
+
return [];
|
|
21316
|
+
}
|
|
21317
|
+
const record = asRecord(parsed);
|
|
21318
|
+
if (record === null || record["version"] !== STATE_VERSION4) return [];
|
|
21319
|
+
const seen = /* @__PURE__ */ new Set();
|
|
21320
|
+
const messages = [];
|
|
21321
|
+
for (const message of asArrayFiltered(record["messages"], parseEntry2) ?? []) {
|
|
21322
|
+
if (seen.has(message.id)) continue;
|
|
21323
|
+
seen.add(message.id);
|
|
21324
|
+
messages.push(message);
|
|
21325
|
+
}
|
|
21326
|
+
return messages;
|
|
21327
|
+
}
|
|
21328
|
+
function copyOf2(message) {
|
|
21329
|
+
return message.failure === void 0 ? { ...message } : { ...message, failure: { ...message.failure } };
|
|
21330
|
+
}
|
|
21331
|
+
var ScheduledMessageStore = class {
|
|
21332
|
+
messages = [];
|
|
21333
|
+
/** Fecha y tamano del archivo la ultima vez que se leyo: si cambian, lo cambio otra instancia. */
|
|
21334
|
+
stamp = "";
|
|
21335
|
+
/** Los cambios, en fila: cada uno relee lo que dejo el anterior. */
|
|
21336
|
+
queue = Promise.resolve();
|
|
21337
|
+
statePath;
|
|
21338
|
+
claimsDir;
|
|
21339
|
+
pid;
|
|
21340
|
+
isAlive;
|
|
21341
|
+
constructor(options = {}) {
|
|
21342
|
+
this.statePath = options.statePath ?? scheduledMessagesPath();
|
|
21343
|
+
this.claimsDir = options.claimsDir ?? scheduledClaimsDir();
|
|
21344
|
+
this.pid = options.pid ?? process.pid;
|
|
21345
|
+
this.isAlive = options.isAlive ?? processAlive;
|
|
21346
|
+
}
|
|
21347
|
+
async load() {
|
|
21348
|
+
await this.readDisk();
|
|
21349
|
+
}
|
|
21350
|
+
/** Una copia de cada uno. */
|
|
21351
|
+
list() {
|
|
21352
|
+
return this.messages.map(copyOf2);
|
|
21353
|
+
}
|
|
21354
|
+
get(id) {
|
|
21355
|
+
const found = this.messages.find((message) => message.id === id);
|
|
21356
|
+
return found === void 0 ? null : copyOf2(found);
|
|
21357
|
+
}
|
|
21358
|
+
get size() {
|
|
21359
|
+
return this.messages.length;
|
|
21360
|
+
}
|
|
21361
|
+
/** Relee el archivo si cambio desde la ultima vez (lo cambio otra instancia). true si cambio. */
|
|
21362
|
+
async refresh() {
|
|
21363
|
+
if (await this.currentStamp() === this.stamp) return false;
|
|
21364
|
+
const before = JSON.stringify(this.messages);
|
|
21365
|
+
await this.readDisk();
|
|
21366
|
+
return JSON.stringify(this.messages) !== before;
|
|
21367
|
+
}
|
|
21368
|
+
add(message) {
|
|
21369
|
+
return this.mutate((messages) => {
|
|
21370
|
+
messages.push(copyOf2(message));
|
|
21371
|
+
return void 0;
|
|
21372
|
+
});
|
|
21373
|
+
}
|
|
21374
|
+
/** Saca uno. Devuelve el que estaba, o null. */
|
|
21375
|
+
remove(id) {
|
|
21376
|
+
return this.mutate((messages) => {
|
|
21377
|
+
const index = messages.findIndex((message) => message.id === id);
|
|
21378
|
+
if (index === -1) return null;
|
|
21379
|
+
const [removed] = messages.splice(index, 1);
|
|
21380
|
+
return removed === void 0 ? null : copyOf2(removed);
|
|
21381
|
+
});
|
|
21382
|
+
}
|
|
21383
|
+
/** Cambia la hora o la falla de uno. null si ya no esta. */
|
|
21384
|
+
update(id, change) {
|
|
21385
|
+
return this.mutate((messages) => {
|
|
21386
|
+
const message = messages.find((entry) => entry.id === id);
|
|
21387
|
+
if (message === void 0) return null;
|
|
21388
|
+
if (change.sendAt !== void 0) message.sendAt = change.sendAt;
|
|
21389
|
+
if (change.failure === null) delete message.failure;
|
|
21390
|
+
else if (change.failure !== void 0) message.failure = { ...change.failure };
|
|
21391
|
+
return copyOf2(message);
|
|
21392
|
+
});
|
|
21393
|
+
}
|
|
21394
|
+
/** Saca los de una conversacion: se cerro su pestana. Devuelve los que saco. */
|
|
21395
|
+
removeOwner(owner) {
|
|
21396
|
+
return this.mutate((messages) => {
|
|
21397
|
+
const removed = messages.filter((message) => sameOwner(message, owner));
|
|
21398
|
+
if (removed.length === 0) return removed;
|
|
21399
|
+
const kept = messages.filter((message) => !sameOwner(message, owner));
|
|
21400
|
+
messages.splice(0, messages.length, ...kept);
|
|
21401
|
+
return removed.map(copyOf2);
|
|
21402
|
+
});
|
|
21403
|
+
}
|
|
21404
|
+
/**
|
|
21405
|
+
* La pestana cambio de conversacion (`/clear`): sus mensajes van con ella,
|
|
21406
|
+
* como el borrador (§6.29). Solo pasa entre CLIs iguales: el id es de una.
|
|
21407
|
+
*/
|
|
21408
|
+
move(from, to) {
|
|
21409
|
+
if (sameOwner(from, to) || from.agent !== to.agent) return Promise.resolve(0);
|
|
21410
|
+
return this.mutate((messages) => {
|
|
21411
|
+
let moved = 0;
|
|
21412
|
+
for (const message of messages) {
|
|
21413
|
+
if (!sameOwner(message, from)) continue;
|
|
21414
|
+
message.sessionId = to.sessionId;
|
|
21415
|
+
moved += 1;
|
|
21416
|
+
}
|
|
21417
|
+
return moved;
|
|
21418
|
+
});
|
|
21419
|
+
}
|
|
21420
|
+
/**
|
|
21421
|
+
* Deja solo los de estas conversaciones: las pestanas del archivo del
|
|
21422
|
+
* arranque. Lo demas es de una pestana que se cerro con una build anterior,
|
|
21423
|
+
* que no los borra, y no lo va a mostrar nadie.
|
|
21424
|
+
*/
|
|
21425
|
+
retainOnly(owners) {
|
|
21426
|
+
const keep = [...owners];
|
|
21427
|
+
return this.mutate((messages) => {
|
|
21428
|
+
const kept = messages.filter((message) => keep.some((owner) => sameOwner(message, owner)));
|
|
21429
|
+
const dropped = messages.length - kept.length;
|
|
21430
|
+
if (dropped > 0) messages.splice(0, messages.length, ...kept);
|
|
21431
|
+
return dropped;
|
|
21432
|
+
});
|
|
21433
|
+
}
|
|
21434
|
+
/** Espera a que se escriba lo pendiente, para el apagado. */
|
|
21435
|
+
async flush() {
|
|
21436
|
+
await this.queue.catch(() => void 0);
|
|
21437
|
+
}
|
|
21438
|
+
// ---- quien lo esta mandando ----
|
|
21439
|
+
/**
|
|
21440
|
+
* Reclama un mensaje para este proceso, en `reserved`. Un reclamo de un
|
|
21441
|
+
* proceso muerto en `reserved` se borra y se vuelve a intentar: ese proceso no
|
|
21442
|
+
* escribio nada.
|
|
21443
|
+
*/
|
|
21444
|
+
async claim(id) {
|
|
21445
|
+
if (!isScheduledMessageId(id)) return "other";
|
|
21446
|
+
await mkdir14(this.claimsDir, { recursive: true });
|
|
21447
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
21448
|
+
try {
|
|
21449
|
+
const handle = await open8(this.claimPath(id), "wx");
|
|
21450
|
+
try {
|
|
21451
|
+
await handle.writeFile(this.claimBody("reserved"));
|
|
21452
|
+
} finally {
|
|
21453
|
+
await handle.close();
|
|
21454
|
+
}
|
|
21455
|
+
return "mine";
|
|
21456
|
+
} catch (error) {
|
|
21457
|
+
if (error.code !== "EEXIST") throw error;
|
|
21458
|
+
}
|
|
21459
|
+
const existing = await this.claimOf(id);
|
|
21460
|
+
if (existing === null) continue;
|
|
21461
|
+
if (existing.mine) return "mine";
|
|
21462
|
+
if (existing.alive) return "other";
|
|
21463
|
+
if (existing.phase === "writing") return "orphan-writing";
|
|
21464
|
+
await rm5(this.claimPath(id), { force: true });
|
|
21465
|
+
}
|
|
21466
|
+
return "other";
|
|
21467
|
+
}
|
|
21468
|
+
/** Pasa un reclamo propio a otro paso. */
|
|
21469
|
+
async setPhase(id, phase) {
|
|
21470
|
+
const existing = await this.claimOf(id);
|
|
21471
|
+
if (existing === null || !existing.mine) return;
|
|
21472
|
+
await writeFileAtomic(this.claimPath(id), this.claimBody(phase));
|
|
21473
|
+
}
|
|
21474
|
+
/** Suelta un reclamo propio. El de otra instancia no se toca. */
|
|
21475
|
+
async release(id) {
|
|
21476
|
+
const existing = await this.claimOf(id);
|
|
21477
|
+
if (existing === null || !existing.mine) return;
|
|
21478
|
+
await rm5(this.claimPath(id), { force: true });
|
|
21479
|
+
}
|
|
21480
|
+
/** Borra el reclamo de un proceso muerto, ya declarado. El de uno vivo no se toca. */
|
|
21481
|
+
async discardOrphan(id) {
|
|
21482
|
+
const existing = await this.claimOf(id);
|
|
21483
|
+
if (existing === null || existing.mine || existing.alive) return;
|
|
21484
|
+
await rm5(this.claimPath(id), { force: true });
|
|
21485
|
+
}
|
|
21486
|
+
/** El reclamo de un mensaje, o null si no hay (o no se entiende: se trata como ausente). */
|
|
21487
|
+
async claimOf(id) {
|
|
21488
|
+
if (!isScheduledMessageId(id)) return null;
|
|
21489
|
+
let raw;
|
|
21490
|
+
try {
|
|
21491
|
+
raw = await readFile24(this.claimPath(id), "utf8");
|
|
21492
|
+
} catch {
|
|
21493
|
+
return null;
|
|
21494
|
+
}
|
|
21495
|
+
let parsed;
|
|
21496
|
+
try {
|
|
21497
|
+
parsed = JSON.parse(raw);
|
|
21498
|
+
} catch {
|
|
21499
|
+
return { pid: -1, phase: "reserved", mine: false, alive: true };
|
|
21500
|
+
}
|
|
21501
|
+
const record = asRecord(parsed);
|
|
21502
|
+
const pid = record === null ? null : asFiniteNumber(record["pid"]);
|
|
21503
|
+
const phase = record === null ? null : asLiteral(record["phase"], ["reserved", "writing"]);
|
|
21504
|
+
if (pid === null || phase === null) return { pid: -1, phase: "reserved", mine: false, alive: true };
|
|
21505
|
+
const mine = pid === this.pid;
|
|
21506
|
+
return { pid, phase, mine, alive: mine || this.isAlive(pid) };
|
|
21507
|
+
}
|
|
21508
|
+
/** Los ids que tienen reclamo en disco. Para limpiar los de mensajes que ya no estan. */
|
|
21509
|
+
async claimedIds() {
|
|
21510
|
+
let names;
|
|
21511
|
+
try {
|
|
21512
|
+
names = await readdir14(this.claimsDir);
|
|
21513
|
+
} catch {
|
|
21514
|
+
return [];
|
|
21515
|
+
}
|
|
21516
|
+
return names.filter((name) => name.endsWith(".json")).map((name) => name.slice(0, -".json".length)).filter(isScheduledMessageId);
|
|
21517
|
+
}
|
|
21518
|
+
/** Suelta los reclamos propios en `reserved`: la instancia se apaga sin haber escrito nada. */
|
|
21519
|
+
async releaseReserved() {
|
|
21520
|
+
for (const id of await this.claimedIds()) {
|
|
21521
|
+
const existing = await this.claimOf(id);
|
|
21522
|
+
if (existing !== null && existing.mine && existing.phase === "reserved") await rm5(this.claimPath(id), { force: true });
|
|
21523
|
+
}
|
|
21524
|
+
}
|
|
21525
|
+
// ---- disco ----
|
|
21526
|
+
claimPath(id) {
|
|
21527
|
+
return path56.join(this.claimsDir, `${id}.json`);
|
|
21528
|
+
}
|
|
21529
|
+
claimBody(phase) {
|
|
21530
|
+
return `${JSON.stringify({ pid: this.pid, phase, at: Date.now() })}
|
|
21531
|
+
`;
|
|
21532
|
+
}
|
|
21533
|
+
async currentStamp() {
|
|
21534
|
+
try {
|
|
21535
|
+
const info = await stat34(this.statePath);
|
|
21536
|
+
return `${info.mtimeMs}:${info.size}`;
|
|
21537
|
+
} catch {
|
|
21538
|
+
return "";
|
|
21539
|
+
}
|
|
21540
|
+
}
|
|
21541
|
+
async readDisk() {
|
|
21542
|
+
const stamp2 = await this.currentStamp();
|
|
21543
|
+
let messages = [];
|
|
21544
|
+
try {
|
|
21545
|
+
messages = parseScheduledState(await readFile24(this.statePath, "utf8"));
|
|
21546
|
+
} catch {
|
|
21547
|
+
}
|
|
21548
|
+
this.messages = messages;
|
|
21549
|
+
this.stamp = stamp2;
|
|
21550
|
+
return messages;
|
|
21551
|
+
}
|
|
21552
|
+
/** Relee, aplica el cambio y escribe. Lo que devuelve `change` es lo que devuelve el cambio. */
|
|
21553
|
+
mutate(change) {
|
|
21554
|
+
const run2 = async () => {
|
|
21555
|
+
const messages = await this.readDisk();
|
|
21556
|
+
const before = JSON.stringify(messages);
|
|
21557
|
+
const result2 = change(messages);
|
|
21558
|
+
if (JSON.stringify(messages) === before) return result2;
|
|
21559
|
+
await mkdir14(path56.dirname(this.statePath), { recursive: true });
|
|
21560
|
+
await writeFileAtomic(this.statePath, `${JSON.stringify({ version: STATE_VERSION4, messages }, null, 2)}
|
|
21561
|
+
`);
|
|
21562
|
+
this.messages = messages;
|
|
21563
|
+
this.stamp = await this.currentStamp();
|
|
21564
|
+
return result2;
|
|
21565
|
+
};
|
|
21566
|
+
const result = this.queue.then(run2, run2);
|
|
21567
|
+
this.queue = result.catch(() => void 0);
|
|
21568
|
+
return result;
|
|
21569
|
+
}
|
|
21570
|
+
};
|
|
21571
|
+
|
|
21572
|
+
// packages/server/src/scheduled-messages/instances.ts
|
|
21573
|
+
import { mkdir as mkdir15, readdir as readdir15, rm as rm6, writeFile as writeFile9 } from "node:fs/promises";
|
|
21574
|
+
import path57 from "node:path";
|
|
21575
|
+
var FILE_PATTERN = /^(\d+)\.json$/;
|
|
21576
|
+
var RunningInstances = class {
|
|
21577
|
+
constructor(dir = runningInstancesDir(), pid = process.pid, isAlive = processAlive) {
|
|
21578
|
+
this.dir = dir;
|
|
21579
|
+
this.pid = pid;
|
|
21580
|
+
this.isAlive = isAlive;
|
|
21581
|
+
}
|
|
21582
|
+
dir;
|
|
21583
|
+
pid;
|
|
21584
|
+
isAlive;
|
|
21585
|
+
fileOf(pid) {
|
|
21586
|
+
return path57.join(this.dir, `${pid}.json`);
|
|
21587
|
+
}
|
|
21588
|
+
/** Anota esta instancia. Nunca lanza: sin el archivo, la otra no espera, como antes del hito. */
|
|
21589
|
+
async register() {
|
|
21590
|
+
try {
|
|
21591
|
+
await mkdir15(this.dir, { recursive: true });
|
|
21592
|
+
await writeFile9(this.fileOf(this.pid), `${JSON.stringify({ pid: this.pid, startedAt: Date.now() })}
|
|
21593
|
+
`);
|
|
21594
|
+
} catch (error) {
|
|
21595
|
+
console.warn("[scheduled] couldn't note this instance:", error);
|
|
21596
|
+
}
|
|
21597
|
+
}
|
|
21598
|
+
/** La saca, al apagarse. Nunca lanza. */
|
|
21599
|
+
async unregister() {
|
|
21600
|
+
await rm6(this.fileOf(this.pid), { force: true }).catch(() => void 0);
|
|
21601
|
+
}
|
|
21602
|
+
/** true si otra instancia esta corriendo. Borra los archivos de las que ya no estan. */
|
|
21603
|
+
async othersRunning() {
|
|
21604
|
+
let names;
|
|
21605
|
+
try {
|
|
21606
|
+
names = await readdir15(this.dir);
|
|
21607
|
+
} catch {
|
|
21608
|
+
return false;
|
|
21609
|
+
}
|
|
21610
|
+
let found = false;
|
|
21611
|
+
for (const name of names) {
|
|
21612
|
+
const match = FILE_PATTERN.exec(name);
|
|
21613
|
+
if (match === null) continue;
|
|
21614
|
+
const pid = Number(match[1]);
|
|
21615
|
+
if (pid === this.pid) continue;
|
|
21616
|
+
if (this.isAlive(pid)) found = true;
|
|
21617
|
+
else await rm6(this.fileOf(pid), { force: true }).catch(() => void 0);
|
|
21618
|
+
}
|
|
21619
|
+
return found;
|
|
21620
|
+
}
|
|
21621
|
+
};
|
|
21622
|
+
|
|
20963
21623
|
// packages/server/src/shell-locator.ts
|
|
20964
21624
|
import { access as access2 } from "node:fs/promises";
|
|
20965
21625
|
import { constants as fsConstants2 } from "node:fs";
|
|
20966
|
-
import
|
|
21626
|
+
import path58 from "node:path";
|
|
20967
21627
|
async function isExecutable(candidate) {
|
|
20968
21628
|
try {
|
|
20969
21629
|
await access2(candidate, process.platform === "win32" ? fsConstants2.R_OK : fsConstants2.X_OK);
|
|
@@ -20981,21 +21641,21 @@ var SYSTEM_PROBE = {
|
|
|
20981
21641
|
function windowsPowerShellPath(env) {
|
|
20982
21642
|
const systemRoot = env["SystemRoot"] ?? env["windir"];
|
|
20983
21643
|
if (systemRoot === void 0 || systemRoot.length === 0) return null;
|
|
20984
|
-
return
|
|
21644
|
+
return path58.win32.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
20985
21645
|
}
|
|
20986
21646
|
function gitBashCandidates(gitPath, env) {
|
|
20987
21647
|
const candidates = [];
|
|
20988
21648
|
if (gitPath !== null) {
|
|
20989
|
-
let dir =
|
|
21649
|
+
let dir = path58.win32.dirname(gitPath);
|
|
20990
21650
|
for (let level = 0; level < 3; level++) {
|
|
20991
|
-
candidates.push(
|
|
20992
|
-
const parent =
|
|
21651
|
+
candidates.push(path58.win32.join(dir, "bin", "bash.exe"));
|
|
21652
|
+
const parent = path58.win32.dirname(dir);
|
|
20993
21653
|
if (parent === dir) break;
|
|
20994
21654
|
dir = parent;
|
|
20995
21655
|
}
|
|
20996
21656
|
}
|
|
20997
|
-
for (const base of [env["ProgramFiles"], env["LOCALAPPDATA"] === void 0 ? void 0 :
|
|
20998
|
-
if (base !== void 0 && base.length > 0) candidates.push(
|
|
21657
|
+
for (const base of [env["ProgramFiles"], env["LOCALAPPDATA"] === void 0 ? void 0 : path58.win32.join(env["LOCALAPPDATA"], "Programs")]) {
|
|
21658
|
+
if (base !== void 0 && base.length > 0) candidates.push(path58.win32.join(base, "Git", "bin", "bash.exe"));
|
|
20999
21659
|
}
|
|
21000
21660
|
const systemRoot = (env["SystemRoot"] ?? env["windir"] ?? "").toLowerCase();
|
|
21001
21661
|
return [...new Set(candidates)].filter((candidate) => systemRoot.length === 0 || !candidate.toLowerCase().startsWith(systemRoot));
|
|
@@ -21026,7 +21686,7 @@ async function locatePosixShells(probe) {
|
|
|
21026
21686
|
const found = [];
|
|
21027
21687
|
const preferred = probe.env["SHELL"];
|
|
21028
21688
|
if (preferred !== void 0 && preferred.length > 0 && await probe.isExecutable(preferred)) {
|
|
21029
|
-
const name =
|
|
21689
|
+
const name = path58.posix.basename(preferred);
|
|
21030
21690
|
found.push({ id: "login", label: name, location: { file: preferred, args: [], name } });
|
|
21031
21691
|
}
|
|
21032
21692
|
for (const [id, file] of [["bash", "/bin/bash"], ["sh", "/bin/sh"], ["zsh", "/bin/zsh"]]) {
|
|
@@ -21075,9 +21735,18 @@ var AppSettingsService = class extends EventEmitter5 {
|
|
|
21075
21735
|
consoleShells: this.options.shells.map(({ id, label }) => ({ id, label })),
|
|
21076
21736
|
shellName: this.currentShell()?.name ?? null,
|
|
21077
21737
|
disabledAgents: [...settings2.agents.disabled],
|
|
21078
|
-
disabledAtStart: [...this.disabledAtStart]
|
|
21738
|
+
disabledAtStart: [...this.disabledAtStart],
|
|
21739
|
+
scheduledMessages: settings2.scheduledMessages.enabled
|
|
21079
21740
|
};
|
|
21080
21741
|
}
|
|
21742
|
+
/** "Mensajes programados" encendido, segun lo ultimo leido (hito 51). */
|
|
21743
|
+
scheduledMessagesEnabled() {
|
|
21744
|
+
return this.options.settings.get().scheduledMessages.enabled;
|
|
21745
|
+
}
|
|
21746
|
+
/** Lo relee del disco; si la otra instancia lo cambio, avisa como cualquier cambio. */
|
|
21747
|
+
async refreshScheduledMessages() {
|
|
21748
|
+
if (await this.options.settings.reloadScheduledMessages()) this.emit("status", this.status());
|
|
21749
|
+
}
|
|
21081
21750
|
/** Guarda el cambio, avisa al registro si cambio la consola, y emite `status`. */
|
|
21082
21751
|
async update(change) {
|
|
21083
21752
|
if (change.consoleShell !== void 0 && !this.offered(change.consoleShell)) {
|
|
@@ -21086,7 +21755,8 @@ var AppSettingsService = class extends EventEmitter5 {
|
|
|
21086
21755
|
const before = this.currentShell();
|
|
21087
21756
|
await this.options.settings.update({
|
|
21088
21757
|
...change.consoleShell !== void 0 ? { console: { shell: change.consoleShell } } : {},
|
|
21089
|
-
...change.disabledAgents !== void 0 ? { agents: { disabled: change.disabledAgents } } : {}
|
|
21758
|
+
...change.disabledAgents !== void 0 ? { agents: { disabled: change.disabledAgents } } : {},
|
|
21759
|
+
...change.scheduledMessages !== void 0 ? { scheduledMessages: { enabled: change.scheduledMessages } } : {}
|
|
21090
21760
|
});
|
|
21091
21761
|
const after = this.currentShell();
|
|
21092
21762
|
if (after !== before) this.options.onShellChange(after);
|
|
@@ -21098,13 +21768,13 @@ var AppSettingsService = class extends EventEmitter5 {
|
|
|
21098
21768
|
};
|
|
21099
21769
|
|
|
21100
21770
|
// packages/server/src/session-watcher.ts
|
|
21101
|
-
import { stat as
|
|
21771
|
+
import { stat as stat35 } from "node:fs/promises";
|
|
21102
21772
|
import chokidar3 from "chokidar";
|
|
21103
21773
|
var INDEX_DEBOUNCE_MS = 750;
|
|
21104
21774
|
var ROOT_PROBE_MS = 5e3;
|
|
21105
21775
|
async function isDirectory3(target) {
|
|
21106
21776
|
try {
|
|
21107
|
-
return (await
|
|
21777
|
+
return (await stat35(target)).isDirectory();
|
|
21108
21778
|
} catch {
|
|
21109
21779
|
return false;
|
|
21110
21780
|
}
|
|
@@ -21207,7 +21877,7 @@ function watchSessions(index, hub, agents, options = {}) {
|
|
|
21207
21877
|
// packages/server/src/terminal-registry.ts
|
|
21208
21878
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
21209
21879
|
import { EventEmitter as EventEmitter6 } from "node:events";
|
|
21210
|
-
import { stat as
|
|
21880
|
+
import { stat as stat36 } from "node:fs/promises";
|
|
21211
21881
|
|
|
21212
21882
|
// packages/server/src/launch-hook-slot.ts
|
|
21213
21883
|
var LaunchHookSlot = class {
|
|
@@ -21508,9 +22178,9 @@ async function resolveLaunchPlan(launch) {
|
|
|
21508
22178
|
}
|
|
21509
22179
|
|
|
21510
22180
|
// packages/server/src/workspace-store.ts
|
|
21511
|
-
import { mkdir as
|
|
21512
|
-
import
|
|
21513
|
-
var
|
|
22181
|
+
import { mkdir as mkdir16, readFile as readFile25, writeFile as writeFile10 } from "node:fs/promises";
|
|
22182
|
+
import path59 from "node:path";
|
|
22183
|
+
var STATE_VERSION5 = 1;
|
|
21514
22184
|
var WRITE_DEBOUNCE_MS4 = 400;
|
|
21515
22185
|
var LEGACY_TAB_AGENT = "claude-code";
|
|
21516
22186
|
function placeByPosition(base, placed) {
|
|
@@ -21574,7 +22244,7 @@ function parseState4(raw) {
|
|
|
21574
22244
|
try {
|
|
21575
22245
|
const parsed = JSON.parse(raw);
|
|
21576
22246
|
if (!isRecord(parsed)) return null;
|
|
21577
|
-
if (parsed["version"] !==
|
|
22247
|
+
if (parsed["version"] !== STATE_VERSION5) return null;
|
|
21578
22248
|
const rawTabs = parsed["tabs"];
|
|
21579
22249
|
if (!Array.isArray(rawTabs)) return null;
|
|
21580
22250
|
const base = [];
|
|
@@ -21612,7 +22282,7 @@ function serializeState(state) {
|
|
|
21612
22282
|
otherTabs.push({ ...entry.tab, position });
|
|
21613
22283
|
}
|
|
21614
22284
|
}
|
|
21615
|
-
return otherTabs.length === 0 ? { version:
|
|
22285
|
+
return otherTabs.length === 0 ? { version: STATE_VERSION5, tabs } : { version: STATE_VERSION5, tabs, otherTabs };
|
|
21616
22286
|
}
|
|
21617
22287
|
var EMPTY_STATE = () => ({ tabs: [], foreignTabs: [] });
|
|
21618
22288
|
function persistableTabs(descriptors) {
|
|
@@ -21636,7 +22306,7 @@ var WorkspaceStore = class {
|
|
|
21636
22306
|
pending = null;
|
|
21637
22307
|
async load() {
|
|
21638
22308
|
try {
|
|
21639
|
-
const raw = await
|
|
22309
|
+
const raw = await readFile25(workspaceStatePath(), "utf8");
|
|
21640
22310
|
return parseState4(raw) ?? EMPTY_STATE();
|
|
21641
22311
|
} catch {
|
|
21642
22312
|
return EMPTY_STATE();
|
|
@@ -21666,10 +22336,10 @@ var WorkspaceStore = class {
|
|
|
21666
22336
|
}
|
|
21667
22337
|
async writeNow(state) {
|
|
21668
22338
|
try {
|
|
21669
|
-
await
|
|
22339
|
+
await mkdir16(appConfigDir(), { recursive: true });
|
|
21670
22340
|
const target = workspaceStatePath();
|
|
21671
|
-
const temporary =
|
|
21672
|
-
await
|
|
22341
|
+
const temporary = path59.join(path59.dirname(target), `workspace.${process.pid}.tmp`);
|
|
22342
|
+
await writeFile10(temporary, JSON.stringify(serializeState(state), null, 2), "utf8");
|
|
21673
22343
|
const { rename: rename8 } = await import("node:fs/promises");
|
|
21674
22344
|
await rename8(temporary, target);
|
|
21675
22345
|
} catch (error) {
|
|
@@ -21937,7 +22607,7 @@ var TerminalRegistry = class extends EventEmitter6 {
|
|
|
21937
22607
|
async assertDirectory(cwd) {
|
|
21938
22608
|
let info;
|
|
21939
22609
|
try {
|
|
21940
|
-
info = await
|
|
22610
|
+
info = await stat36(cwd);
|
|
21941
22611
|
} catch {
|
|
21942
22612
|
throw new TerminalOpenError("invalid-cwd", serverText("cwdMissing", { cwd }));
|
|
21943
22613
|
}
|
|
@@ -22395,26 +23065,26 @@ import { WebSocketServer } from "ws";
|
|
|
22395
23065
|
|
|
22396
23066
|
// packages/server/src/directory-picker.ts
|
|
22397
23067
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
22398
|
-
import { lstat, mkdir as
|
|
23068
|
+
import { lstat, mkdir as mkdir17, readdir as readdir16, stat as stat37 } from "node:fs/promises";
|
|
22399
23069
|
import { homedir as homedir14 } from "node:os";
|
|
22400
|
-
import
|
|
23070
|
+
import path60 from "node:path";
|
|
22401
23071
|
var MAX_ENTRIES = 500;
|
|
22402
23072
|
var MAX_PICKERS = 8;
|
|
22403
23073
|
var SKIPPED = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "bin", "obj", "$RECYCLE.BIN"]);
|
|
22404
23074
|
var DirectoryPickerError = class extends ServerTextError {
|
|
22405
23075
|
};
|
|
22406
23076
|
function isInsideProtected(target, dirs) {
|
|
22407
|
-
const resolved =
|
|
23077
|
+
const resolved = path60.resolve(target);
|
|
22408
23078
|
return dirs.some((dir) => {
|
|
22409
|
-
const protectedDir =
|
|
22410
|
-
return resolved === protectedDir || resolved.startsWith(protectedDir +
|
|
23079
|
+
const protectedDir = path60.resolve(dir);
|
|
23080
|
+
return resolved === protectedDir || resolved.startsWith(protectedDir + path60.sep);
|
|
22411
23081
|
});
|
|
22412
23082
|
}
|
|
22413
23083
|
async function isListedDirectory(parent, entry, readLink = lstat) {
|
|
22414
23084
|
if (entry.isDirectory()) return true;
|
|
22415
23085
|
if (!entry.isSymbolicLink()) return false;
|
|
22416
23086
|
try {
|
|
22417
|
-
const info = await readLink(
|
|
23087
|
+
const info = await readLink(path60.join(parent, entry.name));
|
|
22418
23088
|
return info.isDirectory() && !info.isSymbolicLink();
|
|
22419
23089
|
} catch {
|
|
22420
23090
|
return false;
|
|
@@ -22429,7 +23099,7 @@ async function listRoots() {
|
|
|
22429
23099
|
letters.map(async (letter) => {
|
|
22430
23100
|
const drive = `${letter}:\\`;
|
|
22431
23101
|
try {
|
|
22432
|
-
await
|
|
23102
|
+
await stat37(drive);
|
|
22433
23103
|
return drive;
|
|
22434
23104
|
} catch {
|
|
22435
23105
|
return null;
|
|
@@ -22481,14 +23151,14 @@ var DirectoryPickers = class {
|
|
|
22481
23151
|
async enter(pickerId, name) {
|
|
22482
23152
|
const picker = this.require(pickerId);
|
|
22483
23153
|
if (name === "..") {
|
|
22484
|
-
const parent =
|
|
23154
|
+
const parent = path60.dirname(picker.current);
|
|
22485
23155
|
if (parent !== picker.current) picker.current = parent;
|
|
22486
23156
|
return this.listing(picker);
|
|
22487
23157
|
}
|
|
22488
23158
|
if (!picker.entries.has(name)) {
|
|
22489
23159
|
throw new DirectoryPickerError(serverText("pickerNotListed"));
|
|
22490
23160
|
}
|
|
22491
|
-
const target =
|
|
23161
|
+
const target = path60.join(picker.current, name);
|
|
22492
23162
|
if (isInsideProtected(target, this.protectedDirs)) {
|
|
22493
23163
|
throw new DirectoryPickerError(serverText("pickerCliFolder"));
|
|
22494
23164
|
}
|
|
@@ -22528,12 +23198,12 @@ var DirectoryPickers = class {
|
|
|
22528
23198
|
throw new DirectoryPickerError(serverText("pickerNameChars"));
|
|
22529
23199
|
}
|
|
22530
23200
|
}
|
|
22531
|
-
const target =
|
|
23201
|
+
const target = path60.join(picker.current, clean);
|
|
22532
23202
|
if (isInsideProtected(target, this.protectedDirs)) {
|
|
22533
23203
|
throw new DirectoryPickerError(serverText("pickerNoProjectsHere"));
|
|
22534
23204
|
}
|
|
22535
23205
|
try {
|
|
22536
|
-
await
|
|
23206
|
+
await mkdir17(target);
|
|
22537
23207
|
} catch (error) {
|
|
22538
23208
|
const code = error.code;
|
|
22539
23209
|
throw new DirectoryPickerError(
|
|
@@ -22564,9 +23234,9 @@ var DirectoryPickers = class {
|
|
|
22564
23234
|
let names = [];
|
|
22565
23235
|
let truncated = false;
|
|
22566
23236
|
try {
|
|
22567
|
-
const found = await
|
|
23237
|
+
const found = await readdir16(picker.current, { withFileTypes: true });
|
|
22568
23238
|
const listed = await Promise.all(found.map((entry) => isListedDirectory(picker.current, entry)));
|
|
22569
|
-
const dirs = found.filter((_, index) => listed[index] === true).map((entry) => entry.name).filter((name) => !SKIPPED.has(name)).filter((name) => !isInsideProtected(
|
|
23239
|
+
const dirs = found.filter((_, index) => listed[index] === true).map((entry) => entry.name).filter((name) => !SKIPPED.has(name)).filter((name) => !isInsideProtected(path60.join(picker.current, name), this.protectedDirs)).sort((a, b) => a.localeCompare(b));
|
|
22570
23240
|
truncated = dirs.length > MAX_ENTRIES;
|
|
22571
23241
|
names = dirs.slice(0, MAX_ENTRIES);
|
|
22572
23242
|
} catch {
|
|
@@ -22577,7 +23247,7 @@ var DirectoryPickers = class {
|
|
|
22577
23247
|
path: picker.current,
|
|
22578
23248
|
entries: names,
|
|
22579
23249
|
roots,
|
|
22580
|
-
canGoUp:
|
|
23250
|
+
canGoUp: path60.dirname(picker.current) !== picker.current,
|
|
22581
23251
|
truncated
|
|
22582
23252
|
};
|
|
22583
23253
|
}
|
|
@@ -22637,7 +23307,7 @@ var PlanLimitsBoard = class {
|
|
|
22637
23307
|
|
|
22638
23308
|
// packages/server/src/cli-folders.ts
|
|
22639
23309
|
import { realpath as realpath4 } from "node:fs/promises";
|
|
22640
|
-
import
|
|
23310
|
+
import path61 from "node:path";
|
|
22641
23311
|
function isSameOrInside(child, parent, platform) {
|
|
22642
23312
|
const childKey = normalizeCwdKey(child, platform);
|
|
22643
23313
|
const parentKey = normalizeCwdKey(parent, platform);
|
|
@@ -22649,7 +23319,7 @@ function isSameOrInside(child, parent, platform) {
|
|
|
22649
23319
|
async function cliFolders(dirs, platform = process.platform) {
|
|
22650
23320
|
const known = [];
|
|
22651
23321
|
for (const dir of dirs) {
|
|
22652
|
-
known.push(
|
|
23322
|
+
known.push(path61.resolve(dir));
|
|
22653
23323
|
try {
|
|
22654
23324
|
known.push(await realpath4(dir));
|
|
22655
23325
|
} catch {
|
|
@@ -22672,8 +23342,8 @@ async function cliFolders(dirs, platform = process.platform) {
|
|
|
22672
23342
|
|
|
22673
23343
|
// packages/server/src/file-browser.ts
|
|
22674
23344
|
import { createHash as createHash4 } from "node:crypto";
|
|
22675
|
-
import { readFile as
|
|
22676
|
-
import
|
|
23345
|
+
import { readFile as readFile26, readdir as readdir17, realpath as realpath5, stat as stat38 } from "node:fs/promises";
|
|
23346
|
+
import path62 from "node:path";
|
|
22677
23347
|
var MAX_ENTRIES2 = 1e3;
|
|
22678
23348
|
var SEARCH_MAX_RESULTS = 200;
|
|
22679
23349
|
var SEARCH_MAX_DIRECTORIES = 4e3;
|
|
@@ -22736,7 +23406,7 @@ function languageFor(fileName) {
|
|
|
22736
23406
|
const lower = fileName.toLowerCase();
|
|
22737
23407
|
const byName = LANGUAGE_BY_NAME[lower];
|
|
22738
23408
|
if (byName !== void 0) return byName;
|
|
22739
|
-
const extension =
|
|
23409
|
+
const extension = path62.extname(lower);
|
|
22740
23410
|
return LANGUAGE_BY_EXTENSION[extension] ?? null;
|
|
22741
23411
|
}
|
|
22742
23412
|
function looksBinary2(buffer) {
|
|
@@ -22773,7 +23443,7 @@ async function listDirectory(cwd, relativePath, folders, options = {}) {
|
|
|
22773
23443
|
let sizeBytes = 0;
|
|
22774
23444
|
let modifiedAt = 0;
|
|
22775
23445
|
try {
|
|
22776
|
-
const info = await
|
|
23446
|
+
const info = await stat38(path62.join(absolute, candidate.name));
|
|
22777
23447
|
sizeBytes = candidate.isDirectory ? 0 : info.size;
|
|
22778
23448
|
modifiedAt = info.mtimeMs;
|
|
22779
23449
|
} catch {
|
|
@@ -22799,7 +23469,7 @@ async function scanLevel(absolute, repoRoot2, folders) {
|
|
|
22799
23469
|
return level;
|
|
22800
23470
|
}
|
|
22801
23471
|
async function readLevel(absolute, folders) {
|
|
22802
|
-
const dirents = await
|
|
23472
|
+
const dirents = await readdir17(absolute, { withFileTypes: true });
|
|
22803
23473
|
const realLevel = await realpath5(absolute);
|
|
22804
23474
|
let skipped = 0;
|
|
22805
23475
|
const candidates = [];
|
|
@@ -22807,8 +23477,8 @@ async function readLevel(absolute, folders) {
|
|
|
22807
23477
|
const always = ALWAYS_SKIP.has(dirent.name.toLowerCase());
|
|
22808
23478
|
if (dirent.isSymbolicLink()) {
|
|
22809
23479
|
try {
|
|
22810
|
-
const linked =
|
|
22811
|
-
const target = await
|
|
23480
|
+
const linked = path62.join(absolute, dirent.name);
|
|
23481
|
+
const target = await stat38(linked);
|
|
22812
23482
|
if (folders.holds(await realpath5(linked))) continue;
|
|
22813
23483
|
candidates.push({
|
|
22814
23484
|
name: dirent.name,
|
|
@@ -22824,7 +23494,7 @@ async function readLevel(absolute, folders) {
|
|
|
22824
23494
|
skipped += 1;
|
|
22825
23495
|
continue;
|
|
22826
23496
|
}
|
|
22827
|
-
if (folders.holds(
|
|
23497
|
+
if (folders.holds(path62.join(realLevel, dirent.name))) continue;
|
|
22828
23498
|
candidates.push({
|
|
22829
23499
|
name: dirent.name,
|
|
22830
23500
|
isDirectory: dirent.isDirectory(),
|
|
@@ -22839,7 +23509,7 @@ async function markIgnored(repoRoot2, levels) {
|
|
|
22839
23509
|
for (const level of levels) {
|
|
22840
23510
|
for (const entry of level.entries) {
|
|
22841
23511
|
if (entry.hidden !== null) continue;
|
|
22842
|
-
keys.set(entry, toPosixPath(
|
|
23512
|
+
keys.set(entry, toPosixPath(path62.relative(repoRoot2, path62.join(level.absolute, entry.name))));
|
|
22843
23513
|
}
|
|
22844
23514
|
}
|
|
22845
23515
|
if (keys.size === 0) return;
|
|
@@ -22901,7 +23571,7 @@ async function searchFiles(cwd, query, folders, options = {}) {
|
|
|
22901
23571
|
let sizeBytes = 0;
|
|
22902
23572
|
let modifiedAt = 0;
|
|
22903
23573
|
try {
|
|
22904
|
-
const info = await
|
|
23574
|
+
const info = await stat38(path62.join(level.absolute, candidate.name));
|
|
22905
23575
|
sizeBytes = candidate.isDirectory ? 0 : info.size;
|
|
22906
23576
|
modifiedAt = info.mtimeMs;
|
|
22907
23577
|
} catch {
|
|
@@ -22939,7 +23609,7 @@ async function existingPaths(cwd, relativePaths, folders) {
|
|
|
22939
23609
|
for (const relativePath of relativePaths) {
|
|
22940
23610
|
try {
|
|
22941
23611
|
const absolute = await resolveForFiles(cwd, relativePath, folders);
|
|
22942
|
-
const info = await
|
|
23612
|
+
const info = await stat38(absolute);
|
|
22943
23613
|
if (info.isFile()) files.push(relativePath);
|
|
22944
23614
|
else if (info.isDirectory()) directories.push(relativePath);
|
|
22945
23615
|
} catch {
|
|
@@ -22955,12 +23625,12 @@ function isEditableText(content, text) {
|
|
|
22955
23625
|
}
|
|
22956
23626
|
async function readPreview(cwd, relativePath, folders) {
|
|
22957
23627
|
const absolute = await resolveForFiles(cwd, relativePath, folders);
|
|
22958
|
-
const info = await
|
|
23628
|
+
const info = await stat38(absolute);
|
|
22959
23629
|
if (info.isDirectory()) {
|
|
22960
23630
|
throw new Error("It's a directory, not a file.");
|
|
22961
23631
|
}
|
|
22962
|
-
const content = await
|
|
22963
|
-
const fileName =
|
|
23632
|
+
const content = await readFile26(absolute);
|
|
23633
|
+
const fileName = path62.basename(absolute);
|
|
22964
23634
|
if (looksBinary2(content)) {
|
|
22965
23635
|
return {
|
|
22966
23636
|
path: relativePath,
|
|
@@ -22986,7 +23656,7 @@ async function readPreview(cwd, relativePath, folders) {
|
|
|
22986
23656
|
|
|
22987
23657
|
// packages/server/src/file-write.ts
|
|
22988
23658
|
import { constants } from "node:fs";
|
|
22989
|
-
import { access as access3, readFile as
|
|
23659
|
+
import { access as access3, readFile as readFile27, realpath as realpath6, stat as stat39 } from "node:fs/promises";
|
|
22990
23660
|
var FileWriteError = class extends ServerTextError {
|
|
22991
23661
|
};
|
|
22992
23662
|
function withLineEndings(text, original) {
|
|
@@ -23011,9 +23681,9 @@ async function saveProjectFile(cwd, relativePath, text, baseHash, folders) {
|
|
|
23011
23681
|
}
|
|
23012
23682
|
if (await folders.contains(absolute)) throw new InvalidPathError(serverText("cliFolderClosed"));
|
|
23013
23683
|
const target = await realpath6(absolute);
|
|
23014
|
-
const info = await
|
|
23684
|
+
const info = await stat39(target);
|
|
23015
23685
|
if (!info.isFile()) throw new FileWriteError(serverText("fileSaveFailed"));
|
|
23016
|
-
const current = await
|
|
23686
|
+
const current = await readFile27(target);
|
|
23017
23687
|
if (contentHash(current) !== baseHash) {
|
|
23018
23688
|
return { outcome: "changed", preview: await readPreview(cwd, relativePath, folders).catch(() => null) };
|
|
23019
23689
|
}
|
|
@@ -23323,12 +23993,115 @@ function prefillReasonFor(outcome) {
|
|
|
23323
23993
|
}
|
|
23324
23994
|
}
|
|
23325
23995
|
|
|
23326
|
-
// packages/server/src/
|
|
23327
|
-
|
|
23328
|
-
|
|
23329
|
-
|
|
23330
|
-
|
|
23331
|
-
|
|
23996
|
+
// packages/server/src/submit-message.ts
|
|
23997
|
+
function prepareSubmit(deps, terminalId) {
|
|
23998
|
+
const submitting = deps.adapterOf(terminalId);
|
|
23999
|
+
const isBlind = () => submitting !== null && deps.conversations.isBlindToApprovals(terminalId, submitting);
|
|
24000
|
+
const guardsPending = submitting?.capabilities.permissionCycle?.approvesPendingOnCycle === true;
|
|
24001
|
+
const blocksWhileWaiting = submitting?.capabilities.waitingBlocksSubmit === true;
|
|
24002
|
+
if (submitting !== null) {
|
|
24003
|
+
const launchedAt = deps.launchedAtOf(terminalId);
|
|
24004
|
+
const blind = isBlind();
|
|
24005
|
+
const refusal = submitRefusal({
|
|
24006
|
+
label: submitting.label,
|
|
24007
|
+
approvesPendingOnCycle: guardsPending,
|
|
24008
|
+
waitingFor: deps.conversations.getWaitingFor(terminalId),
|
|
24009
|
+
waitingBlocksSubmit: blocksWhileWaiting,
|
|
24010
|
+
activity: deps.activityOf(terminalId),
|
|
24011
|
+
blind,
|
|
24012
|
+
openToolCall: blind && launchedAt !== null && deps.conversations.hasOpenToolCall(terminalId, launchedAt)
|
|
24013
|
+
});
|
|
24014
|
+
if (refusal !== null) return { refusal };
|
|
24015
|
+
}
|
|
24016
|
+
const approvalGuard = submitting !== null && (guardsPending || blocksWhileWaiting || isBlind()) ? async () => {
|
|
24017
|
+
const current = deps.launchedAtOf(terminalId);
|
|
24018
|
+
if (current === null) return true;
|
|
24019
|
+
if (blocksWhileWaiting && deps.activityOf(terminalId) === "waiting") return false;
|
|
24020
|
+
if (isBlind() && await deps.conversations.checkOpenToolCall(terminalId, current)) return false;
|
|
24021
|
+
return !guardsPending || await deps.conversations.checkWaitingFor(terminalId) === null;
|
|
24022
|
+
} : void 0;
|
|
24023
|
+
const input = deps.inputOf(terminalId);
|
|
24024
|
+
const startupDialog = input.startupDialog;
|
|
24025
|
+
const firstMessage = startupDialog !== void 0 && (deps.sessionIdOf(terminalId) ?? "") === "";
|
|
24026
|
+
let startupChecked = false;
|
|
24027
|
+
let stoppedByDialog = false;
|
|
24028
|
+
const pieceGuard = firstMessage || approvalGuard !== void 0 ? async () => {
|
|
24029
|
+
if (firstMessage && startupDialog !== void 0 && !startupChecked) {
|
|
24030
|
+
startupChecked = true;
|
|
24031
|
+
const screen = deps.watchScreen(terminalId);
|
|
24032
|
+
if (screen !== null) {
|
|
24033
|
+
try {
|
|
24034
|
+
stoppedByDialog = screenShowsDialog(await screen.lines(), startupDialog);
|
|
24035
|
+
} finally {
|
|
24036
|
+
screen.dispose();
|
|
24037
|
+
}
|
|
24038
|
+
if (stoppedByDialog) return false;
|
|
24039
|
+
}
|
|
24040
|
+
}
|
|
24041
|
+
return approvalGuard === void 0 ? true : approvalGuard();
|
|
24042
|
+
} : void 0;
|
|
24043
|
+
return {
|
|
24044
|
+
terminalId,
|
|
24045
|
+
input,
|
|
24046
|
+
label: submitting?.label ?? null,
|
|
24047
|
+
blocksWhileWaiting,
|
|
24048
|
+
pieceGuard,
|
|
24049
|
+
stoppedByDialog: () => stoppedByDialog,
|
|
24050
|
+
promptQueueSource: submitting?.promptQueue?.source ?? null,
|
|
24051
|
+
hasQueuedSubmits: submitting?.queuedSubmits !== void 0
|
|
24052
|
+
};
|
|
24053
|
+
}
|
|
24054
|
+
async function runSubmit(deps, plan, request) {
|
|
24055
|
+
const { terminalId, input } = plan;
|
|
24056
|
+
let result = { kind: "dropped" };
|
|
24057
|
+
await deps.enqueue(
|
|
24058
|
+
terminalId,
|
|
24059
|
+
async (lane) => {
|
|
24060
|
+
result = { kind: "failed" };
|
|
24061
|
+
const content = await request.prepare(input);
|
|
24062
|
+
if (content === null) {
|
|
24063
|
+
result = { kind: "prepare-failed" };
|
|
24064
|
+
return;
|
|
24065
|
+
}
|
|
24066
|
+
const { fullText, imagePaths } = content;
|
|
24067
|
+
const pieces = buildSubmissionWrites(fullText, imagePaths, input, { send: request.send });
|
|
24068
|
+
if (pieces === null) {
|
|
24069
|
+
result = { kind: "nothing" };
|
|
24070
|
+
return;
|
|
24071
|
+
}
|
|
24072
|
+
deps.noteSubmitted(terminalId, fullText);
|
|
24073
|
+
const startAfterMs = submitStartDelayMs(deps.launchedAtOf(terminalId), deps.now(), input.readyAfterLaunchMs ?? 0);
|
|
24074
|
+
const outcome = await lane.writePieces(pieces, input.pieceGapMs, request.write, plan.pieceGuard, { startAfterMs });
|
|
24075
|
+
request.onWritten?.(outcome);
|
|
24076
|
+
if (outcome === "written" && request.send && plan.promptQueueSource === "app" && deps.activityOf(terminalId) === "busy" && !request.text.trimStart().startsWith("/")) {
|
|
24077
|
+
deps.conversations.noteQueued(terminalId, { text: request.text, images: request.imageCount, at: deps.now() });
|
|
24078
|
+
}
|
|
24079
|
+
if (outcome === "written" && request.send && plan.hasQueuedSubmits) {
|
|
24080
|
+
deps.queuedSubmits.note(terminalId, submissionPayload(fullText, imagePaths, input.imageReference) ?? fullText, deps.now(), fullText);
|
|
24081
|
+
}
|
|
24082
|
+
if (outcome === "written") result = { kind: "written" };
|
|
24083
|
+
else if (outcome === "refused") result = { kind: "refused" };
|
|
24084
|
+
else if (outcome === "interrupted") result = { kind: "interrupted" };
|
|
24085
|
+
else {
|
|
24086
|
+
result = {
|
|
24087
|
+
kind: "blocked",
|
|
24088
|
+
byDialog: plan.stoppedByDialog(),
|
|
24089
|
+
waitingNow: plan.blocksWhileWaiting && deps.activityOf(terminalId) === "waiting"
|
|
24090
|
+
};
|
|
24091
|
+
}
|
|
24092
|
+
},
|
|
24093
|
+
// Descartado por una interrupcion antes de empezar: queda `dropped`.
|
|
24094
|
+
{ onDropped: () => void 0 }
|
|
24095
|
+
);
|
|
24096
|
+
return result;
|
|
24097
|
+
}
|
|
24098
|
+
|
|
24099
|
+
// packages/server/src/vault/read-session.ts
|
|
24100
|
+
var MAX_POLLS = 5;
|
|
24101
|
+
var MAX_TOTAL_POLLS = 20;
|
|
24102
|
+
var UnsupportedHistoryError = class extends Error {
|
|
24103
|
+
constructor(label) {
|
|
24104
|
+
super(`${label}: the source doesn't declare that its follower respects the local copy's limits (wholeRead)`);
|
|
23332
24105
|
this.name = "UnsupportedHistoryError";
|
|
23333
24106
|
}
|
|
23334
24107
|
};
|
|
@@ -23685,14 +24458,14 @@ var TerminalWriteQueue = class {
|
|
|
23685
24458
|
};
|
|
23686
24459
|
|
|
23687
24460
|
// packages/server/src/vault/service.ts
|
|
23688
|
-
import { mkdir as
|
|
24461
|
+
import { mkdir as mkdir20, stat as stat43 } from "node:fs/promises";
|
|
23689
24462
|
import { homedir as homedir15 } from "node:os";
|
|
23690
|
-
import
|
|
24463
|
+
import path66 from "node:path";
|
|
23691
24464
|
|
|
23692
24465
|
// packages/server/src/vault/move.ts
|
|
23693
24466
|
import { constants as constants2 } from "node:fs";
|
|
23694
|
-
import { copyFile as copyFile2, lstat as lstat2, mkdir as
|
|
23695
|
-
import
|
|
24467
|
+
import { copyFile as copyFile2, lstat as lstat2, mkdir as mkdir18, readdir as readdir18, rm as rm7, stat as stat40, utimes, writeFile as writeFile11 } from "node:fs/promises";
|
|
24468
|
+
import path63 from "node:path";
|
|
23696
24469
|
var MOVED_ENTRIES = ["vault.json", "sessions", "memory"];
|
|
23697
24470
|
var OWN_TEMP_FILE = /\.\d+\.[0-9a-f]{12}\.tmp$/;
|
|
23698
24471
|
async function copyTree(source, target, result) {
|
|
@@ -23703,14 +24476,14 @@ async function copyTree(source, target, result) {
|
|
|
23703
24476
|
return;
|
|
23704
24477
|
}
|
|
23705
24478
|
if (info.isDirectory()) {
|
|
23706
|
-
await
|
|
23707
|
-
for (const name of await
|
|
23708
|
-
await copyTree(
|
|
24479
|
+
await mkdir18(target, { recursive: true });
|
|
24480
|
+
for (const name of await readdir18(source)) {
|
|
24481
|
+
await copyTree(path63.join(source, name), path63.join(target, name), result);
|
|
23709
24482
|
}
|
|
23710
24483
|
return;
|
|
23711
24484
|
}
|
|
23712
24485
|
if (!info.isFile() || OWN_TEMP_FILE.test(source)) return;
|
|
23713
|
-
await
|
|
24486
|
+
await mkdir18(path63.dirname(target), { recursive: true });
|
|
23714
24487
|
try {
|
|
23715
24488
|
await copyFile2(source, target, constants2.COPYFILE_EXCL);
|
|
23716
24489
|
} catch (error) {
|
|
@@ -23725,9 +24498,9 @@ async function copyTree(source, target, result) {
|
|
|
23725
24498
|
}
|
|
23726
24499
|
async function moveVault(from, to) {
|
|
23727
24500
|
const result = { copied: 0, skipped: 0 };
|
|
23728
|
-
await
|
|
24501
|
+
await mkdir18(to, { recursive: true });
|
|
23729
24502
|
for (const entry of MOVED_ENTRIES) {
|
|
23730
|
-
await copyTree(
|
|
24503
|
+
await copyTree(path63.join(from, entry), path63.join(to, entry), result);
|
|
23731
24504
|
}
|
|
23732
24505
|
return result;
|
|
23733
24506
|
}
|
|
@@ -23759,16 +24532,16 @@ function checkVaultTarget(current, target, options) {
|
|
|
23759
24532
|
return { kind: "ok" };
|
|
23760
24533
|
}
|
|
23761
24534
|
async function canWriteInto(dir) {
|
|
23762
|
-
const probe = temporaryPathFor(
|
|
24535
|
+
const probe = temporaryPathFor(path63.join(dir, ".escritura"));
|
|
23763
24536
|
try {
|
|
23764
|
-
await
|
|
23765
|
-
if (!(await
|
|
23766
|
-
await
|
|
24537
|
+
await mkdir18(dir, { recursive: true });
|
|
24538
|
+
if (!(await stat40(dir)).isDirectory()) return false;
|
|
24539
|
+
await writeFile11(probe, "");
|
|
23767
24540
|
return true;
|
|
23768
24541
|
} catch {
|
|
23769
24542
|
return false;
|
|
23770
24543
|
} finally {
|
|
23771
|
-
await
|
|
24544
|
+
await rm7(probe, { force: true }).catch(() => void 0);
|
|
23772
24545
|
}
|
|
23773
24546
|
}
|
|
23774
24547
|
|
|
@@ -23865,12 +24638,12 @@ function serializeSession(input) {
|
|
|
23865
24638
|
}
|
|
23866
24639
|
|
|
23867
24640
|
// packages/server/src/vault/writer.ts
|
|
23868
|
-
import { readdir as
|
|
23869
|
-
import
|
|
24641
|
+
import { readdir as readdir20, rm as rm8, stat as stat42 } from "node:fs/promises";
|
|
24642
|
+
import path65 from "node:path";
|
|
23870
24643
|
|
|
23871
24644
|
// packages/server/src/vault/memory-copy.ts
|
|
23872
|
-
import { mkdir as
|
|
23873
|
-
import
|
|
24645
|
+
import { mkdir as mkdir19, readdir as readdir19, readFile as readFile28, stat as stat41, utimes as utimes2 } from "node:fs/promises";
|
|
24646
|
+
import path64 from "node:path";
|
|
23874
24647
|
var MEMORY_FILE_MAX_BYTES = 1024 * 1024;
|
|
23875
24648
|
var MEMORY_MAX_FILES = 500;
|
|
23876
24649
|
var MEMORY_SOURCE_FILE = "source.json";
|
|
@@ -23885,7 +24658,7 @@ async function listMemoryNotes(cwd) {
|
|
|
23885
24658
|
}
|
|
23886
24659
|
let entries;
|
|
23887
24660
|
try {
|
|
23888
|
-
entries = await
|
|
24661
|
+
entries = await readdir19(folder, { withFileTypes: true });
|
|
23889
24662
|
} catch {
|
|
23890
24663
|
return [];
|
|
23891
24664
|
}
|
|
@@ -23895,7 +24668,7 @@ async function listMemoryNotes(cwd) {
|
|
|
23895
24668
|
let absolute;
|
|
23896
24669
|
try {
|
|
23897
24670
|
absolute = await resolveInside(cwd, `${MEMORY_DIR}/${name}`, { mustExist: true });
|
|
23898
|
-
const info = await
|
|
24671
|
+
const info = await stat41(absolute);
|
|
23899
24672
|
if (!info.isFile() || info.size > MEMORY_FILE_MAX_BYTES) continue;
|
|
23900
24673
|
notes.push({ name, absolute, size: info.size, mtimeMs: info.mtimeMs });
|
|
23901
24674
|
} catch {
|
|
@@ -23909,7 +24682,7 @@ async function measureProjectMemory(cwd) {
|
|
|
23909
24682
|
}
|
|
23910
24683
|
async function isUpToDate(target, note) {
|
|
23911
24684
|
try {
|
|
23912
|
-
const info = await
|
|
24685
|
+
const info = await stat41(target);
|
|
23913
24686
|
return info.isFile() && info.size === note.size && Math.abs(info.mtimeMs - note.mtimeMs) < SAME_MTIME_MS;
|
|
23914
24687
|
} catch {
|
|
23915
24688
|
return false;
|
|
@@ -23917,7 +24690,7 @@ async function isUpToDate(target, note) {
|
|
|
23917
24690
|
}
|
|
23918
24691
|
async function exists3(file) {
|
|
23919
24692
|
try {
|
|
23920
|
-
await
|
|
24693
|
+
await stat41(file);
|
|
23921
24694
|
return true;
|
|
23922
24695
|
} catch {
|
|
23923
24696
|
return false;
|
|
@@ -23933,14 +24706,14 @@ async function copyProjectMemory(dir, cwd, platform, deps = DEFAULT_WRITE_DEPS)
|
|
|
23933
24706
|
if (prepared) return;
|
|
23934
24707
|
prepared = true;
|
|
23935
24708
|
await ensureVaultMarker(dir, deps);
|
|
23936
|
-
await
|
|
24709
|
+
await mkdir19(target, { recursive: true });
|
|
23937
24710
|
};
|
|
23938
24711
|
for (const note of notes) {
|
|
23939
|
-
const destination =
|
|
24712
|
+
const destination = path64.join(target, note.name);
|
|
23940
24713
|
if (await isUpToDate(destination, note)) continue;
|
|
23941
24714
|
let content;
|
|
23942
24715
|
try {
|
|
23943
|
-
content = await
|
|
24716
|
+
content = await readFile28(note.absolute);
|
|
23944
24717
|
} catch {
|
|
23945
24718
|
continue;
|
|
23946
24719
|
}
|
|
@@ -23952,7 +24725,7 @@ async function copyProjectMemory(dir, cwd, platform, deps = DEFAULT_WRITE_DEPS)
|
|
|
23952
24725
|
result.files += 1;
|
|
23953
24726
|
result.bytes += content.length;
|
|
23954
24727
|
}
|
|
23955
|
-
const source =
|
|
24728
|
+
const source = path64.join(target, MEMORY_SOURCE_FILE);
|
|
23956
24729
|
if (result.files > 0 || !await exists3(source)) {
|
|
23957
24730
|
await prepare();
|
|
23958
24731
|
await writeFileAtomic(source, `${JSON.stringify({ cwd, copiedAt: deps.now() })}
|
|
@@ -24446,7 +25219,7 @@ var VaultWriter = class {
|
|
|
24446
25219
|
}
|
|
24447
25220
|
async assetOnDisk(dir, agent, sessionId, asset, bytes) {
|
|
24448
25221
|
try {
|
|
24449
|
-
const info = await
|
|
25222
|
+
const info = await stat42(assetFile(dir, agent, sessionId, asset));
|
|
24450
25223
|
return info.isFile() && info.size === bytes;
|
|
24451
25224
|
} catch {
|
|
24452
25225
|
return false;
|
|
@@ -24546,30 +25319,30 @@ var VaultWriter = class {
|
|
|
24546
25319
|
const folders = [dir];
|
|
24547
25320
|
const subfolders = async (parent) => {
|
|
24548
25321
|
try {
|
|
24549
|
-
return (await
|
|
25322
|
+
return (await readdir20(parent, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => path65.join(parent, entry.name));
|
|
24550
25323
|
} catch {
|
|
24551
25324
|
return [];
|
|
24552
25325
|
}
|
|
24553
25326
|
};
|
|
24554
25327
|
for (const agentDir of await subfolders(sessionsRoot2(dir))) {
|
|
24555
|
-
if (!SESSION_AGENT_IDS.includes(
|
|
25328
|
+
if (!SESSION_AGENT_IDS.includes(path65.basename(agentDir))) continue;
|
|
24556
25329
|
folders.push(agentDir);
|
|
24557
25330
|
folders.push(...(await subfolders(agentDir)).filter((folder) => folder.endsWith(".assets")));
|
|
24558
25331
|
}
|
|
24559
|
-
folders.push(...await subfolders(
|
|
25332
|
+
folders.push(...await subfolders(path65.join(dir, "memory")));
|
|
24560
25333
|
for (const folder of folders) {
|
|
24561
25334
|
let names;
|
|
24562
25335
|
try {
|
|
24563
|
-
names = await
|
|
25336
|
+
names = await readdir20(folder);
|
|
24564
25337
|
} catch {
|
|
24565
25338
|
continue;
|
|
24566
25339
|
}
|
|
24567
25340
|
for (const name of names) {
|
|
24568
25341
|
if (!OWN_TEMP_FILE2.test(name)) continue;
|
|
24569
|
-
const file =
|
|
25342
|
+
const file = path65.join(folder, name);
|
|
24570
25343
|
try {
|
|
24571
|
-
const info = await
|
|
24572
|
-
if (info.isFile() && now - info.mtimeMs > TEMP_MAX_AGE_MS) await
|
|
25344
|
+
const info = await stat42(file);
|
|
25345
|
+
if (info.isFile() && now - info.mtimeMs > TEMP_MAX_AGE_MS) await rm8(file, { force: true });
|
|
24573
25346
|
} catch {
|
|
24574
25347
|
}
|
|
24575
25348
|
}
|
|
@@ -24756,14 +25529,14 @@ var VaultService = class {
|
|
|
24756
25529
|
async setDir(target) {
|
|
24757
25530
|
const { platform, agents, settings: settings2, catalog } = this.options;
|
|
24758
25531
|
const current = this.dir();
|
|
24759
|
-
const protectedDirs = [...agents.protectedDirs(),
|
|
25532
|
+
const protectedDirs = [...agents.protectedDirs(), path66.join(this.options.homeDir ?? homedir15(), ".gemini")];
|
|
24760
25533
|
const check = checkVaultTarget(current, target, { protectedDirs, platform });
|
|
24761
25534
|
if (check.kind === "refused") throw new VaultError(check.text);
|
|
24762
25535
|
if (check.kind === "same") return;
|
|
24763
25536
|
try {
|
|
24764
25537
|
await this.writer.exclusive("moving", async () => {
|
|
24765
25538
|
if (!await canWriteInto(target)) throw new VaultError(serverText("vaultCannotWrite"));
|
|
24766
|
-
const hadCopy = await
|
|
25539
|
+
const hadCopy = await stat43(vaultMarkerFile(current)).then(
|
|
24767
25540
|
() => true,
|
|
24768
25541
|
() => false
|
|
24769
25542
|
);
|
|
@@ -24797,7 +25570,7 @@ var VaultService = class {
|
|
|
24797
25570
|
/** Abre la carpeta de la copia con el explorador del sistema. */
|
|
24798
25571
|
async reveal() {
|
|
24799
25572
|
const dir = this.activeDir();
|
|
24800
|
-
const isFolder = await
|
|
25573
|
+
const isFolder = await stat43(dir).then(
|
|
24801
25574
|
(info) => info.isDirectory(),
|
|
24802
25575
|
() => false
|
|
24803
25576
|
);
|
|
@@ -24821,7 +25594,7 @@ var VaultService = class {
|
|
|
24821
25594
|
fromCopy: true
|
|
24822
25595
|
});
|
|
24823
25596
|
const file = sessionExportFile(dir, agent, sessionId);
|
|
24824
|
-
await
|
|
25597
|
+
await mkdir20(path66.dirname(file), { recursive: true });
|
|
24825
25598
|
await writeFileAtomic(file, text, this.writeDeps);
|
|
24826
25599
|
this.openWithSystem(file);
|
|
24827
25600
|
return file;
|
|
@@ -24908,7 +25681,7 @@ var VaultService = class {
|
|
|
24908
25681
|
const project = this.options.index.getProjects().find((candidate) => candidate.key === projectKey);
|
|
24909
25682
|
if (project === void 0) throw new VaultError(serverText("vaultProjectGone"));
|
|
24910
25683
|
const folder = projectExportDirFor(this.activeDir(), project, this.options.platform);
|
|
24911
|
-
await
|
|
25684
|
+
await mkdir20(folder, { recursive: true });
|
|
24912
25685
|
const entries = [];
|
|
24913
25686
|
const names = /* @__PURE__ */ new Set();
|
|
24914
25687
|
let skipped = 0;
|
|
@@ -24925,7 +25698,7 @@ var VaultService = class {
|
|
|
24925
25698
|
continue;
|
|
24926
25699
|
}
|
|
24927
25700
|
const fileName = this.exportFileName(summary, names);
|
|
24928
|
-
await writeFileAtomic(
|
|
25701
|
+
await writeFileAtomic(path66.join(folder, fileName), text, this.writeDeps);
|
|
24929
25702
|
entries.push({
|
|
24930
25703
|
updatedAt: summary.updatedAt,
|
|
24931
25704
|
agentLabel: this.labelOf(summary.agent),
|
|
@@ -24936,9 +25709,9 @@ var VaultService = class {
|
|
|
24936
25709
|
});
|
|
24937
25710
|
await nextTurn3();
|
|
24938
25711
|
}
|
|
24939
|
-
const name = project.cwd.length > 0 ?
|
|
25712
|
+
const name = project.cwd.length > 0 ? path66.basename(project.cwd) || project.cwd : project.fallbackName;
|
|
24940
25713
|
const index = renderProjectIndex({ name, cwd: project.cwd, exportedAt: this.now(), entries, skipped });
|
|
24941
|
-
await writeFileAtomic(
|
|
25714
|
+
await writeFileAtomic(path66.join(folder, "index.md"), index, this.writeDeps);
|
|
24942
25715
|
this.openWithSystem(folder);
|
|
24943
25716
|
return { sessions: entries.length, skipped, folder };
|
|
24944
25717
|
}
|
|
@@ -25076,14 +25849,14 @@ var VaultService = class {
|
|
|
25076
25849
|
// packages/server/src/worktrees/service.ts
|
|
25077
25850
|
import { randomBytes as randomBytes6, randomUUID as randomUUID7 } from "node:crypto";
|
|
25078
25851
|
import { EventEmitter as EventEmitter7 } from "node:events";
|
|
25079
|
-
import { lstat as lstat5, mkdir as
|
|
25080
|
-
import
|
|
25852
|
+
import { lstat as lstat5, mkdir as mkdir24, rename as rename7, rm as rm12, rmdir, stat as stat46 } from "node:fs/promises";
|
|
25853
|
+
import path74 from "node:path";
|
|
25081
25854
|
|
|
25082
25855
|
// packages/server/src/worktrees/git.ts
|
|
25083
25856
|
import { execFile as execFile6 } from "node:child_process";
|
|
25084
|
-
import { mkdtemp, rm as
|
|
25857
|
+
import { mkdtemp, rm as rm9, stat as stat44 } from "node:fs/promises";
|
|
25085
25858
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
25086
|
-
import
|
|
25859
|
+
import path67 from "node:path";
|
|
25087
25860
|
var READ_TIMEOUT_MS = 15e3;
|
|
25088
25861
|
var WRITE_TIMEOUT_MS = 12e4;
|
|
25089
25862
|
var MAX_BUFFER = 32 * 1024 * 1024;
|
|
@@ -25129,10 +25902,10 @@ async function repoLayout(cwd) {
|
|
|
25129
25902
|
if (result.code !== 0) return null;
|
|
25130
25903
|
const [toplevel, gitDir, commonDir] = result.stdout.split(/\r?\n/).map((line) => line.trim());
|
|
25131
25904
|
if (!toplevel || !gitDir || !commonDir) return null;
|
|
25132
|
-
const absoluteCommon =
|
|
25133
|
-
const linked =
|
|
25134
|
-
const mainRoot =
|
|
25135
|
-
return { toplevel:
|
|
25905
|
+
const absoluteCommon = path67.resolve(cwd, commonDir);
|
|
25906
|
+
const linked = path67.resolve(gitDir).toLowerCase() !== absoluteCommon.toLowerCase();
|
|
25907
|
+
const mainRoot = path67.basename(absoluteCommon).toLowerCase() === ".git" ? path67.dirname(absoluteCommon) : null;
|
|
25908
|
+
return { toplevel: path67.resolve(toplevel), mainRoot, linked };
|
|
25136
25909
|
}
|
|
25137
25910
|
async function currentBranch(cwd) {
|
|
25138
25911
|
const result = await read(cwd, ["symbolic-ref", "--quiet", "HEAD"]);
|
|
@@ -25171,18 +25944,18 @@ async function uncommittedPaths(cwd) {
|
|
|
25171
25944
|
async function hasLoadedSubmodule(cwd) {
|
|
25172
25945
|
const [modules, staged] = await Promise.all([read(cwd, ["rev-parse", "--git-path", "modules"]), read(cwd, ["ls-files", "--stage", "-z"])]);
|
|
25173
25946
|
if (modules.code !== 0 || staged.code !== 0) return null;
|
|
25174
|
-
if (await isDirectory4(
|
|
25947
|
+
if (await isDirectory4(path67.resolve(cwd, modules.stdout.trim()))) return true;
|
|
25175
25948
|
const submodules = staged.stdout.split("\0").filter((line) => line.startsWith("160000 ")).map((line) => line.slice(line.indexOf(" ") + 1));
|
|
25176
25949
|
for (const submodule of submodules) {
|
|
25177
|
-
if (await exists4(
|
|
25950
|
+
if (await exists4(path67.join(cwd, submodule, ".git"))) return true;
|
|
25178
25951
|
}
|
|
25179
25952
|
return false;
|
|
25180
25953
|
}
|
|
25181
25954
|
async function isDirectory4(candidate) {
|
|
25182
|
-
return (await
|
|
25955
|
+
return (await stat44(candidate).catch(() => null))?.isDirectory() === true;
|
|
25183
25956
|
}
|
|
25184
25957
|
async function exists4(candidate) {
|
|
25185
|
-
return await
|
|
25958
|
+
return await stat44(candidate).catch(() => null) !== null;
|
|
25186
25959
|
}
|
|
25187
25960
|
async function isIntegrated(cwd, branch, base) {
|
|
25188
25961
|
const result = await read(cwd, ["merge-base", "--is-ancestor", `refs/heads/${branch}`, `refs/heads/${base}`]);
|
|
@@ -25330,7 +26103,7 @@ async function branchFileDiff(cwd, base, branch, file, oldPath) {
|
|
|
25330
26103
|
async function objectsDir(mainRoot) {
|
|
25331
26104
|
const result = await read(mainRoot, ["rev-parse", "--git-path", "objects"]);
|
|
25332
26105
|
const answer = result.stdout.trim();
|
|
25333
|
-
return result.code === 0 && answer.length > 0 ?
|
|
26106
|
+
return result.code === 0 && answer.length > 0 ? path67.resolve(mainRoot, answer) : null;
|
|
25334
26107
|
}
|
|
25335
26108
|
function alternatesValue(folder, platform = process.platform) {
|
|
25336
26109
|
const separator = platform === "win32" ? ";" : ":";
|
|
@@ -25340,7 +26113,7 @@ function alternatesValue(folder, platform = process.platform) {
|
|
|
25340
26113
|
async function mergeTreeCheck(mainRoot, base, branch) {
|
|
25341
26114
|
const objects = await objectsDir(mainRoot);
|
|
25342
26115
|
if (objects === null) return null;
|
|
25343
|
-
const scratch = await mkdtemp(
|
|
26116
|
+
const scratch = await mkdtemp(path67.join(tmpdir5(), "aw-merge-tree-")).catch(() => null);
|
|
25344
26117
|
if (scratch === null) return null;
|
|
25345
26118
|
try {
|
|
25346
26119
|
const result = await read(
|
|
@@ -25351,7 +26124,7 @@ async function mergeTreeCheck(mainRoot, base, branch) {
|
|
|
25351
26124
|
);
|
|
25352
26125
|
return parseMergeTree(result);
|
|
25353
26126
|
} finally {
|
|
25354
|
-
await
|
|
26127
|
+
await rm9(scratch, { recursive: true, force: true, maxRetries: 3 }).catch(() => void 0);
|
|
25355
26128
|
}
|
|
25356
26129
|
}
|
|
25357
26130
|
function parseMergeTree(result) {
|
|
@@ -25392,7 +26165,7 @@ function fastForward(baseFolder, target) {
|
|
|
25392
26165
|
|
|
25393
26166
|
// packages/server/src/worktrees/holders.ts
|
|
25394
26167
|
import { spawn as spawn4 } from "node:child_process";
|
|
25395
|
-
import
|
|
26168
|
+
import path68 from "node:path";
|
|
25396
26169
|
var SCAN_MAX_ENTRIES = 2e5;
|
|
25397
26170
|
var SCAN_MAX_MS = 4e3;
|
|
25398
26171
|
var HOLDERS_TIMEOUT_MS = 15e3;
|
|
@@ -25545,7 +26318,7 @@ public static class AwFolderHolders {
|
|
|
25545
26318
|
}
|
|
25546
26319
|
`;
|
|
25547
26320
|
function longPath(folder) {
|
|
25548
|
-
const absolute =
|
|
26321
|
+
const absolute = path68.win32.resolve(folder);
|
|
25549
26322
|
if (absolute.startsWith("\\\\?\\")) return absolute;
|
|
25550
26323
|
return absolute.startsWith("\\\\") ? `\\\\?\\UNC\\${absolute.slice(2)}` : `\\\\?\\${absolute}`;
|
|
25551
26324
|
}
|
|
@@ -25580,7 +26353,7 @@ function parseHoldersOutput(output, selfPid) {
|
|
|
25580
26353
|
}
|
|
25581
26354
|
function powershellPath() {
|
|
25582
26355
|
const systemRoot = process.env["SystemRoot"] ?? process.env["windir"] ?? "C:\\Windows";
|
|
25583
|
-
return
|
|
26356
|
+
return path68.win32.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
25584
26357
|
}
|
|
25585
26358
|
function runPowerShell(script) {
|
|
25586
26359
|
return new Promise((resolve) => {
|
|
@@ -25629,8 +26402,8 @@ async function folderHolders(folder) {
|
|
|
25629
26402
|
|
|
25630
26403
|
// packages/server/src/worktrees/include.ts
|
|
25631
26404
|
import { createHash as createHash6 } from "node:crypto";
|
|
25632
|
-
import { lstat as lstat3, mkdir as
|
|
25633
|
-
import
|
|
26405
|
+
import { lstat as lstat3, mkdir as mkdir21, readdir as readdir21, readFile as readFile29, writeFile as writeFile12 } from "node:fs/promises";
|
|
26406
|
+
import path69 from "node:path";
|
|
25634
26407
|
|
|
25635
26408
|
// packages/server/src/worktrees/merge-notes.ts
|
|
25636
26409
|
var MERGE_MAX_EDITS = 2e3;
|
|
@@ -25806,7 +26579,7 @@ var CLI_CONFIG_DIRS = /* @__PURE__ */ new Set([".claude", ".codex", ".gemini", "
|
|
|
25806
26579
|
var CLI_CONFIG_FILES = /* @__PURE__ */ new Set([".mcp.json", "opencode.json", "opencode.jsonc"]);
|
|
25807
26580
|
var INCLUDE_FILE = ".worktreeinclude";
|
|
25808
26581
|
var INCLUDE_FILE_MAX_BYTES = 64 * 1024;
|
|
25809
|
-
var toPosix2 = (value) => value.split(
|
|
26582
|
+
var toPosix2 = (value) => value.split(path69.sep).join("/");
|
|
25810
26583
|
function hashOf(content) {
|
|
25811
26584
|
return createHash6("sha256").update(content).digest("hex");
|
|
25812
26585
|
}
|
|
@@ -25857,7 +26630,7 @@ async function walkEntry(mainRoot, relative, out, budget) {
|
|
|
25857
26630
|
if (!info.isDirectory()) return;
|
|
25858
26631
|
let names;
|
|
25859
26632
|
try {
|
|
25860
|
-
names = (await
|
|
26633
|
+
names = (await readdir21(absolute)).sort();
|
|
25861
26634
|
} catch {
|
|
25862
26635
|
return;
|
|
25863
26636
|
}
|
|
@@ -25911,10 +26684,10 @@ async function copyToWorktree(mainRoot, folder, files, bases) {
|
|
|
25911
26684
|
const source = await resolveInside(mainRoot, file.path, { mustExist: true });
|
|
25912
26685
|
const info = await lstat3(source);
|
|
25913
26686
|
if (!info.isFile()) continue;
|
|
25914
|
-
content = await
|
|
26687
|
+
content = await readFile29(source);
|
|
25915
26688
|
const target = await resolveInside(folder, file.path);
|
|
25916
|
-
await
|
|
25917
|
-
await
|
|
26689
|
+
await mkdir21(path69.dirname(target), { recursive: true });
|
|
26690
|
+
await writeFile12(target, content, { flag: "wx" });
|
|
25918
26691
|
copied.push({ path: file.path, hash: hashOf(content) });
|
|
25919
26692
|
} catch (error) {
|
|
25920
26693
|
console.warn(`[worktrees] ${file.path} wasn't copied to the branch folder:`, error);
|
|
@@ -25931,7 +26704,7 @@ async function readInside(root, relative) {
|
|
|
25931
26704
|
const absolute = await resolveInside(root, relative, { mustExist: true });
|
|
25932
26705
|
const info = await lstat3(absolute);
|
|
25933
26706
|
if (!info.isFile()) return null;
|
|
25934
|
-
return await
|
|
26707
|
+
return await readFile29(absolute);
|
|
25935
26708
|
} catch {
|
|
25936
26709
|
return null;
|
|
25937
26710
|
}
|
|
@@ -26000,7 +26773,7 @@ async function filesUnder(folder, relative, out) {
|
|
|
26000
26773
|
return;
|
|
26001
26774
|
}
|
|
26002
26775
|
if (!info.isDirectory()) return;
|
|
26003
|
-
for (const name of (await
|
|
26776
|
+
for (const name of (await readdir21(absolute).catch(() => [])).sort()) {
|
|
26004
26777
|
await filesUnder(folder, `${relative}/${name}`, out);
|
|
26005
26778
|
}
|
|
26006
26779
|
}
|
|
@@ -26053,7 +26826,7 @@ async function returnNotes(mainRoot, folder, files, bases, options = {}) {
|
|
|
26053
26826
|
result.skipped.push(file);
|
|
26054
26827
|
continue;
|
|
26055
26828
|
}
|
|
26056
|
-
await
|
|
26829
|
+
await mkdir21(path69.dirname(target), { recursive: true });
|
|
26057
26830
|
await writeAtomically(target, outcome.content, {
|
|
26058
26831
|
beforeRename: async () => {
|
|
26059
26832
|
const now = await readInside(mainRoot, file);
|
|
@@ -26075,15 +26848,15 @@ async function returnNotes(mainRoot, folder, files, bases, options = {}) {
|
|
|
26075
26848
|
}
|
|
26076
26849
|
|
|
26077
26850
|
// packages/server/src/worktrees/names.ts
|
|
26078
|
-
import
|
|
26079
|
-
function worktreeFolderFor(mainRoot, branch, pathApi =
|
|
26851
|
+
import path70 from "node:path";
|
|
26852
|
+
function worktreeFolderFor(mainRoot, branch, pathApi = path70) {
|
|
26080
26853
|
const parent = pathApi.dirname(mainRoot);
|
|
26081
26854
|
return pathApi.join(parent, worktreesDirName(pathApi.basename(mainRoot)), worktreeFolderName(branch));
|
|
26082
26855
|
}
|
|
26083
|
-
function worktreesParentFor(mainRoot, pathApi =
|
|
26856
|
+
function worktreesParentFor(mainRoot, pathApi = path70) {
|
|
26084
26857
|
return pathApi.join(pathApi.dirname(mainRoot), worktreesDirName(pathApi.basename(mainRoot)));
|
|
26085
26858
|
}
|
|
26086
|
-
function mainRootFromShape(folder, pathApi =
|
|
26859
|
+
function mainRootFromShape(folder, pathApi = path70) {
|
|
26087
26860
|
const parent = pathApi.dirname(folder);
|
|
26088
26861
|
const parentName = pathApi.basename(parent);
|
|
26089
26862
|
if (parentName.length > ".worktrees".length && parentName.toLowerCase().endsWith(".worktrees")) {
|
|
@@ -26098,8 +26871,8 @@ function mainRootFromShape(folder, pathApi = path68) {
|
|
|
26098
26871
|
|
|
26099
26872
|
// packages/server/src/worktrees/note-bases.ts
|
|
26100
26873
|
import { createHash as createHash7 } from "node:crypto";
|
|
26101
|
-
import { mkdir as
|
|
26102
|
-
import
|
|
26874
|
+
import { mkdir as mkdir22, readdir as readdir22, readFile as readFile30, rm as rm10, stat as stat45, utimes as utimes3 } from "node:fs/promises";
|
|
26875
|
+
import path71 from "node:path";
|
|
26103
26876
|
var TEXT_NOTE_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".mdx", ".markdown", ".txt", ".rst", ".adoc"]);
|
|
26104
26877
|
var NOTE_BASE_MAX_BYTES = 2 * 1024 * 1024;
|
|
26105
26878
|
var PRUNE_GRACE_MS = 10 * 6e4;
|
|
@@ -26108,7 +26881,7 @@ function hashOf2(content) {
|
|
|
26108
26881
|
return createHash7("sha256").update(content).digest("hex");
|
|
26109
26882
|
}
|
|
26110
26883
|
function keepsNoteBase(relative, content) {
|
|
26111
|
-
return TEXT_NOTE_EXTENSIONS.has(
|
|
26884
|
+
return TEXT_NOTE_EXTENSIONS.has(path71.posix.extname(relative).toLowerCase()) && content.length <= NOTE_BASE_MAX_BYTES && !content.subarray(0, 8e3).includes(0);
|
|
26112
26885
|
}
|
|
26113
26886
|
var NoteBases = class {
|
|
26114
26887
|
constructor(dir = worktreeNotesDir(), options = {}) {
|
|
@@ -26128,21 +26901,21 @@ var NoteBases = class {
|
|
|
26128
26901
|
async save(relative, content) {
|
|
26129
26902
|
if (!keepsNoteBase(relative, content)) return;
|
|
26130
26903
|
const hash = hashOf2(content);
|
|
26131
|
-
const file =
|
|
26132
|
-
const current = await
|
|
26904
|
+
const file = path71.join(this.dir, hash);
|
|
26905
|
+
const current = await readFile30(file).catch(() => null);
|
|
26133
26906
|
if (current !== null && hashOf2(current) === hash) {
|
|
26134
26907
|
const now = new Date(this.now());
|
|
26135
26908
|
await utimes3(file, now, now).catch(() => void 0);
|
|
26136
26909
|
return;
|
|
26137
26910
|
}
|
|
26138
|
-
await
|
|
26911
|
+
await mkdir22(this.dir, { recursive: true });
|
|
26139
26912
|
await writeAtomically(file, content);
|
|
26140
26913
|
}
|
|
26141
26914
|
/** La copia de origen con esa huella, o null si no hay (o lo que hay no es eso). */
|
|
26142
26915
|
async load(hash) {
|
|
26143
26916
|
if (!HASH.test(hash)) return null;
|
|
26144
26917
|
try {
|
|
26145
|
-
const content = await
|
|
26918
|
+
const content = await readFile30(path71.join(this.dir, hash));
|
|
26146
26919
|
return hashOf2(content) === hash ? content : null;
|
|
26147
26920
|
} catch {
|
|
26148
26921
|
return null;
|
|
@@ -26152,18 +26925,18 @@ var NoteBases = class {
|
|
|
26152
26925
|
async prune(keep) {
|
|
26153
26926
|
let names;
|
|
26154
26927
|
try {
|
|
26155
|
-
names = await
|
|
26928
|
+
names = await readdir22(this.dir);
|
|
26156
26929
|
} catch {
|
|
26157
26930
|
return;
|
|
26158
26931
|
}
|
|
26159
26932
|
const now = this.now();
|
|
26160
26933
|
for (const name of names) {
|
|
26161
26934
|
if (!HASH.test(name) || keep.has(name)) continue;
|
|
26162
|
-
const file =
|
|
26935
|
+
const file = path71.join(this.dir, name);
|
|
26163
26936
|
try {
|
|
26164
|
-
const info = await
|
|
26937
|
+
const info = await stat45(file);
|
|
26165
26938
|
if (!info.isFile() || now - info.mtimeMs < this.graceMs) continue;
|
|
26166
|
-
await
|
|
26939
|
+
await rm10(file, { force: true });
|
|
26167
26940
|
} catch {
|
|
26168
26941
|
}
|
|
26169
26942
|
}
|
|
@@ -26248,9 +27021,9 @@ function readVerdict(reply) {
|
|
|
26248
27021
|
}
|
|
26249
27022
|
|
|
26250
27023
|
// packages/server/src/worktrees/store.ts
|
|
26251
|
-
import { mkdir as
|
|
26252
|
-
import
|
|
26253
|
-
var
|
|
27024
|
+
import { mkdir as mkdir23, readFile as readFile31 } from "node:fs/promises";
|
|
27025
|
+
import path72 from "node:path";
|
|
27026
|
+
var STATE_VERSION6 = 1;
|
|
26254
27027
|
var MAX_REMOVED = 300;
|
|
26255
27028
|
var MAX_REVIEW_SESSIONS = 20;
|
|
26256
27029
|
function parseNoteRequest(value) {
|
|
@@ -26330,7 +27103,7 @@ function parseWorktreeRecord(value) {
|
|
|
26330
27103
|
function parseWorktreesState(raw) {
|
|
26331
27104
|
try {
|
|
26332
27105
|
const parsed = asRecord(JSON.parse(raw));
|
|
26333
|
-
if (parsed === null || parsed["version"] !==
|
|
27106
|
+
if (parsed === null || parsed["version"] !== STATE_VERSION6) return [];
|
|
26334
27107
|
return asArrayFiltered(parsed["worktrees"], parseWorktreeRecord) ?? [];
|
|
26335
27108
|
} catch {
|
|
26336
27109
|
return [];
|
|
@@ -26345,7 +27118,7 @@ var WorktreeStore = class {
|
|
|
26345
27118
|
writing = Promise.resolve();
|
|
26346
27119
|
async load() {
|
|
26347
27120
|
try {
|
|
26348
|
-
this.records = parseWorktreesState(await
|
|
27121
|
+
this.records = parseWorktreesState(await readFile31(this.file, "utf8"));
|
|
26349
27122
|
} catch {
|
|
26350
27123
|
this.records = [];
|
|
26351
27124
|
}
|
|
@@ -26385,9 +27158,9 @@ var WorktreeStore = class {
|
|
|
26385
27158
|
);
|
|
26386
27159
|
this.records = this.records.filter((record) => !forget.has(record.id));
|
|
26387
27160
|
}
|
|
26388
|
-
const content = Buffer.from(JSON.stringify({ version:
|
|
27161
|
+
const content = Buffer.from(JSON.stringify({ version: STATE_VERSION6, worktrees: this.records }, null, 2), "utf8");
|
|
26389
27162
|
this.writing = this.writing.catch(() => void 0).then(async () => {
|
|
26390
|
-
await
|
|
27163
|
+
await mkdir23(path72.dirname(this.file), { recursive: true });
|
|
26391
27164
|
await writeAtomically(this.file, content);
|
|
26392
27165
|
});
|
|
26393
27166
|
return this.writing;
|
|
@@ -26396,8 +27169,8 @@ var WorktreeStore = class {
|
|
|
26396
27169
|
|
|
26397
27170
|
// packages/server/src/worktrees/untracked.ts
|
|
26398
27171
|
import { createHash as createHash8 } from "node:crypto";
|
|
26399
|
-
import { lstat as lstat4, readdir as
|
|
26400
|
-
import
|
|
27172
|
+
import { lstat as lstat4, readdir as readdir23, realpath as realpath7, rm as rm11 } from "node:fs/promises";
|
|
27173
|
+
import path73 from "node:path";
|
|
26401
27174
|
var sameName = (a, b) => a.toLowerCase() === b.toLowerCase();
|
|
26402
27175
|
function cleanEntry(entry) {
|
|
26403
27176
|
const clean = entry.replace(/\/+$/, "");
|
|
@@ -26414,9 +27187,9 @@ async function scanUntracked(folder, entries, maxFiles = WORKTREE_UNTRACKED_MAX_
|
|
|
26414
27187
|
let target;
|
|
26415
27188
|
let real;
|
|
26416
27189
|
try {
|
|
26417
|
-
const parent = await resolveInside(folder,
|
|
26418
|
-
target =
|
|
26419
|
-
real =
|
|
27190
|
+
const parent = await resolveInside(folder, path73.posix.dirname(clean), { mustExist: true });
|
|
27191
|
+
target = path73.join(parent, path73.posix.basename(clean));
|
|
27192
|
+
real = path73.join(await realpath7(parent), path73.posix.basename(clean));
|
|
26420
27193
|
} catch {
|
|
26421
27194
|
return null;
|
|
26422
27195
|
}
|
|
@@ -26452,7 +27225,7 @@ async function visit(absolute, real, relative, walk2) {
|
|
|
26452
27225
|
`);
|
|
26453
27226
|
let names;
|
|
26454
27227
|
try {
|
|
26455
|
-
names = (await
|
|
27228
|
+
names = (await readdir23(absolute)).sort();
|
|
26456
27229
|
} catch {
|
|
26457
27230
|
walk2.hash.update(`${relative}\0!
|
|
26458
27231
|
`);
|
|
@@ -26463,14 +27236,14 @@ async function visit(absolute, real, relative, walk2) {
|
|
|
26463
27236
|
return;
|
|
26464
27237
|
}
|
|
26465
27238
|
for (const name of names) {
|
|
26466
|
-
await visit(
|
|
27239
|
+
await visit(path73.join(absolute, name), path73.join(real, name), `${relative}/${name}`, walk2);
|
|
26467
27240
|
if (walk2.blocked !== null) return;
|
|
26468
27241
|
}
|
|
26469
27242
|
}
|
|
26470
27243
|
async function deleteUntracked(targets) {
|
|
26471
27244
|
for (const target of targets) {
|
|
26472
27245
|
try {
|
|
26473
|
-
await
|
|
27246
|
+
await rm11(target, { recursive: true, maxRetries: 3 });
|
|
26474
27247
|
} catch (error) {
|
|
26475
27248
|
if (error.code !== "ENOENT") throw error;
|
|
26476
27249
|
}
|
|
@@ -26496,19 +27269,19 @@ var REPLY_EVENTS = 300;
|
|
|
26496
27269
|
var REPLY_CACHE_MAX = 50;
|
|
26497
27270
|
var REVIEW_ACTIVITY_DELAY_MS = 1e3;
|
|
26498
27271
|
async function leftWithoutGit(folder) {
|
|
26499
|
-
return await exists5(folder) && await lstat5(
|
|
27272
|
+
return await exists5(folder) && await lstat5(path74.join(folder, ".git")).catch(() => null) === null;
|
|
26500
27273
|
}
|
|
26501
27274
|
async function exists5(candidate) {
|
|
26502
27275
|
try {
|
|
26503
|
-
await
|
|
27276
|
+
await stat46(candidate);
|
|
26504
27277
|
return true;
|
|
26505
27278
|
} catch {
|
|
26506
27279
|
return false;
|
|
26507
27280
|
}
|
|
26508
27281
|
}
|
|
26509
27282
|
function isInside4(outer, inner) {
|
|
26510
|
-
const relative =
|
|
26511
|
-
return relative.length === 0 || !relative.startsWith("..") && !
|
|
27283
|
+
const relative = path74.relative(path74.resolve(outer), path74.resolve(inner));
|
|
27284
|
+
return relative.length === 0 || !relative.startsWith("..") && !path74.isAbsolute(relative);
|
|
26512
27285
|
}
|
|
26513
27286
|
function displayCommand(args) {
|
|
26514
27287
|
return ["git", ...args].map((arg) => /[\s"]/.test(arg) ? `"${arg.replace(/"/g, '\\"')}"` : arg).join(" ");
|
|
@@ -26583,7 +27356,7 @@ var WorktreeService = class extends EventEmitter7 {
|
|
|
26583
27356
|
return {
|
|
26584
27357
|
mainRoot,
|
|
26585
27358
|
parentDir: worktreesParentFor(mainRoot),
|
|
26586
|
-
separator:
|
|
27359
|
+
separator: path74.sep,
|
|
26587
27360
|
currentBranch: current,
|
|
26588
27361
|
branches: branches.map((name) => ({ name, checkedOut: checkedOut.has(name) })),
|
|
26589
27362
|
mainChanges: changes?.length ?? 0,
|
|
@@ -26607,7 +27380,7 @@ var WorktreeService = class extends EventEmitter7 {
|
|
|
26607
27380
|
const folder = worktreeFolderFor(mainRoot, branch);
|
|
26608
27381
|
if (await exists5(folder)) throw new WorktreeError(serverText("worktreeFolderExists", { folder }));
|
|
26609
27382
|
const copy = await planCopy(mainRoot);
|
|
26610
|
-
await
|
|
27383
|
+
await mkdir24(path74.dirname(folder), { recursive: true });
|
|
26611
27384
|
const added = await addWorktree(mainRoot, folder, branch, mode === "new" ? base : null);
|
|
26612
27385
|
if (added.code !== 0) throw new WorktreeError(serverText("worktreeGitFailed", { detail: gitFailureText(added) }));
|
|
26613
27386
|
const { copied, copiedDirs } = await this.copyInto(mainRoot, folder, copy, request.copyNotes);
|
|
@@ -26658,7 +27431,7 @@ var WorktreeService = class extends EventEmitter7 {
|
|
|
26658
27431
|
const mainRoot = layout.mainRoot;
|
|
26659
27432
|
const tips = await branchTips(mainRoot);
|
|
26660
27433
|
const tipsStamp = [...tips].map(([branch, commit]) => `${branch}\0${commit}`).join("\n");
|
|
26661
|
-
const cacheKey2 =
|
|
27434
|
+
const cacheKey2 = path74.resolve(mainRoot).toLowerCase();
|
|
26662
27435
|
const cached = this.rowsCache.get(cacheKey2);
|
|
26663
27436
|
const fresh = cached !== void 0 && cached.tips === tipsStamp && this.now() - cached.at < ROWS_TTL_MS;
|
|
26664
27437
|
const generation = this.rowsGeneration;
|
|
@@ -26719,7 +27492,7 @@ var WorktreeService = class extends EventEmitter7 {
|
|
|
26719
27492
|
const baseTip = tips.get(base);
|
|
26720
27493
|
const branchTip = tips.get(branch);
|
|
26721
27494
|
if (baseTip === void 0 || branchTip === void 0) return Promise.resolve(null);
|
|
26722
|
-
const key = [
|
|
27495
|
+
const key = [path74.resolve(mainRoot).toLowerCase(), base, branch, baseTip, branchTip].join("\0");
|
|
26723
27496
|
const cached = this.pairs.get(key);
|
|
26724
27497
|
if (cached !== void 0) {
|
|
26725
27498
|
this.pairs.delete(key);
|
|
@@ -26769,7 +27542,7 @@ var WorktreeService = class extends EventEmitter7 {
|
|
|
26769
27542
|
}
|
|
26770
27543
|
/** Las pestañas y consolas abiertas dentro de la carpeta de la rama. */
|
|
26771
27544
|
tabsIn(folder) {
|
|
26772
|
-
return this.deps.registry.list().filter((tab) => isInside4(folder, tab.cwd)).map((tab) => ({ terminalId: tab.terminalId, kind: tab.kind, label: tab.label ||
|
|
27545
|
+
return this.deps.registry.list().filter((tab) => isInside4(folder, tab.cwd)).map((tab) => ({ terminalId: tab.terminalId, kind: tab.kind, label: tab.label || path74.basename(tab.cwd) }));
|
|
26773
27546
|
}
|
|
26774
27547
|
/**
|
|
26775
27548
|
* Las pestañas de una carpeta de trabajo, sin las de otra que viva adentro
|
|
@@ -26802,7 +27575,7 @@ var WorktreeService = class extends EventEmitter7 {
|
|
|
26802
27575
|
});
|
|
26803
27576
|
const copied = [];
|
|
26804
27577
|
for (const file of record.copied) {
|
|
26805
|
-
if (entryOf(file.path) && await exists5(
|
|
27578
|
+
if (entryOf(file.path) && await exists5(path74.join(record.path, file.path))) copied.push(file.path);
|
|
26806
27579
|
}
|
|
26807
27580
|
if (copied.length > 0) {
|
|
26808
27581
|
const ignored = await ignoredAmong(record.path, copied);
|
|
@@ -26950,11 +27723,11 @@ var WorktreeService = class extends EventEmitter7 {
|
|
|
26950
27723
|
* contenga, ni una carpeta que git todavía tenga registrada.
|
|
26951
27724
|
*/
|
|
26952
27725
|
async removeLeftovers(record) {
|
|
26953
|
-
const folder =
|
|
27726
|
+
const folder = path74.resolve(record.path);
|
|
26954
27727
|
if (!await exists5(folder)) return this.removeEmptyParent(folder);
|
|
26955
|
-
if (isInside4(folder, record.mainRoot) ||
|
|
27728
|
+
if (isInside4(folder, record.mainRoot) || path74.dirname(folder) === folder) return;
|
|
26956
27729
|
if ((await listWorktrees(record.mainRoot)).some((entry) => samePathShape(entry.path, folder))) return;
|
|
26957
|
-
await
|
|
27730
|
+
await rm12(folder, { recursive: true, maxRetries: 3 });
|
|
26958
27731
|
await this.removeEmptyParent(folder);
|
|
26959
27732
|
}
|
|
26960
27733
|
/**
|
|
@@ -26977,13 +27750,13 @@ var WorktreeService = class extends EventEmitter7 {
|
|
|
26977
27750
|
for (const record of this.deps.store.list()) {
|
|
26978
27751
|
if (record.removedAt !== null || !samePathShape(record.mainRoot, mainRoot)) continue;
|
|
26979
27752
|
if (listed.some((entry) => samePathShape(entry.path, record.path)) || !await leftWithoutGit(record.path)) continue;
|
|
26980
|
-
released.push({ path:
|
|
27753
|
+
released.push({ path: path74.resolve(record.path), branch: record.branch, isMain: false, bare: false, locked: false, prunable: true });
|
|
26981
27754
|
}
|
|
26982
27755
|
return released;
|
|
26983
27756
|
}
|
|
26984
27757
|
/** La carpeta `<proyecto>.worktrees`, sólo si quedó vacía. */
|
|
26985
27758
|
async removeEmptyParent(folder) {
|
|
26986
|
-
await rmdir(
|
|
27759
|
+
await rmdir(path74.dirname(folder)).catch(() => void 0);
|
|
26987
27760
|
}
|
|
26988
27761
|
/**
|
|
26989
27762
|
* Vuelve a abrir la rama de una sesión cuya carpeta se quitó: la saca otra
|
|
@@ -27007,7 +27780,7 @@ var WorktreeService = class extends EventEmitter7 {
|
|
|
27007
27780
|
throw new WorktreeError(serverText("worktreeBranchInUse", { branch: record.branch }));
|
|
27008
27781
|
}
|
|
27009
27782
|
const copy = await planCopy(record.mainRoot);
|
|
27010
|
-
await
|
|
27783
|
+
await mkdir24(path74.dirname(record.path), { recursive: true });
|
|
27011
27784
|
const added = await addWorktree(record.mainRoot, record.path, record.branch, recreateAt);
|
|
27012
27785
|
if (added.code !== 0) throw new WorktreeError(serverText("worktreeGitFailed", { detail: gitFailureText(added) }));
|
|
27013
27786
|
const { copied, copiedDirs } = await this.copyInto(record.mainRoot, record.path, copy, record.copyNotes);
|
|
@@ -27094,11 +27867,11 @@ var WorktreeService = class extends EventEmitter7 {
|
|
|
27094
27867
|
return { branch: plan.branch };
|
|
27095
27868
|
}
|
|
27096
27869
|
removedRecordAbove(cwd) {
|
|
27097
|
-
let current =
|
|
27870
|
+
let current = path74.resolve(cwd);
|
|
27098
27871
|
for (let depth = 0; depth < 24; depth += 1) {
|
|
27099
27872
|
const record = this.deps.store.byPath(current);
|
|
27100
27873
|
if (record !== null) return record.removedAt === null ? null : record;
|
|
27101
|
-
const parent =
|
|
27874
|
+
const parent = path74.dirname(current);
|
|
27102
27875
|
if (parent === current) return null;
|
|
27103
27876
|
current = parent;
|
|
27104
27877
|
}
|
|
@@ -27207,8 +27980,8 @@ var WorktreeService = class extends EventEmitter7 {
|
|
|
27207
27980
|
* reanudada; y si tampoco hay, una nueva en la carpeta de la rama.
|
|
27208
27981
|
*/
|
|
27209
27982
|
async authorTarget(record) {
|
|
27210
|
-
const
|
|
27211
|
-
if (
|
|
27983
|
+
const open10 = this.authorTab(record);
|
|
27984
|
+
if (open10 !== null) return { terminal: open10, opened: false };
|
|
27212
27985
|
const session = this.authorSession(record);
|
|
27213
27986
|
if (session !== null && isAgentId(session.agent) && session.cwd.length > 0 && await exists5(session.cwd)) {
|
|
27214
27987
|
const terminal = await this.deps.registry.open({ cwd: session.cwd, agent: session.agent, resumeSessionId: session.sessionId });
|
|
@@ -27547,6 +28320,549 @@ var WorktreeService = class extends EventEmitter7 {
|
|
|
27547
28320
|
}
|
|
27548
28321
|
};
|
|
27549
28322
|
|
|
28323
|
+
// packages/server/src/scheduled-messages/scheduler.ts
|
|
28324
|
+
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
28325
|
+
var SCHEDULER_TICK_MS = 3e4;
|
|
28326
|
+
var SCHEDULER_NUDGE_MS = 300;
|
|
28327
|
+
var SCHEDULED_DELIVERY_TIMEOUT_MS = 6e4;
|
|
28328
|
+
var SCHEDULED_COOLDOWN_MS = 1e4;
|
|
28329
|
+
var SCHEDULED_OTHER_INSTANCE_GRACE_MS = 6e4;
|
|
28330
|
+
function scheduleCreateRefusal(input) {
|
|
28331
|
+
if (!input.enabled) return serverText("scheduleOff");
|
|
28332
|
+
if (input.owner === null || input.agentLabel === null) return serverText("scheduleNoConversation");
|
|
28333
|
+
if (!input.capable) return serverText("scheduleUnsupported", { label: input.agentLabel });
|
|
28334
|
+
if (input.text.trim().length === 0) return serverText("scheduleEmpty");
|
|
28335
|
+
if (input.text.length > MAX_SCHEDULED_TEXT_CHARS) return serverText("scheduleTooLong", { max: MAX_SCHEDULED_TEXT_CHARS });
|
|
28336
|
+
const problem = scheduleTimeProblem(input.sendAt, input.now);
|
|
28337
|
+
if (problem === "past") return serverText("schedulePast");
|
|
28338
|
+
if (problem === "too-far") return serverText("scheduleTooFar", { days: Math.round(MAX_SCHEDULE_AHEAD_MS / 864e5) });
|
|
28339
|
+
if (input.count >= MAX_SCHEDULED_MESSAGES) return serverText("scheduleTooMany", { max: MAX_SCHEDULED_MESSAGES });
|
|
28340
|
+
return null;
|
|
28341
|
+
}
|
|
28342
|
+
function scheduledFailureFor(outcome) {
|
|
28343
|
+
switch (outcome.kind) {
|
|
28344
|
+
case "unconfirmed":
|
|
28345
|
+
return serverText("scheduledUnconfirmed");
|
|
28346
|
+
case "sent":
|
|
28347
|
+
case "nothing":
|
|
28348
|
+
return null;
|
|
28349
|
+
case "gone":
|
|
28350
|
+
case "no-agent":
|
|
28351
|
+
return serverText("scheduledNoTab");
|
|
28352
|
+
case "no-session":
|
|
28353
|
+
case "no-ready-signal":
|
|
28354
|
+
case "no-images":
|
|
28355
|
+
case "not-ready":
|
|
28356
|
+
case "image-failed":
|
|
28357
|
+
return serverText("scheduledCliNotReady");
|
|
28358
|
+
case "interrupted":
|
|
28359
|
+
case "refused":
|
|
28360
|
+
return serverText("scheduledUncertainWrite");
|
|
28361
|
+
}
|
|
28362
|
+
}
|
|
28363
|
+
var defaultTimer2 = (callback, ms) => {
|
|
28364
|
+
const timer = setTimeout(callback, ms);
|
|
28365
|
+
timer.unref();
|
|
28366
|
+
return () => clearTimeout(timer);
|
|
28367
|
+
};
|
|
28368
|
+
var MessageScheduler = class {
|
|
28369
|
+
constructor(deps) {
|
|
28370
|
+
this.deps = deps;
|
|
28371
|
+
this.setTimer = deps.setTimer ?? defaultTimer2;
|
|
28372
|
+
}
|
|
28373
|
+
deps;
|
|
28374
|
+
/** Lo que esta pasando con cada mensaje en este proceso: esperando, mandandose. No se guarda. */
|
|
28375
|
+
runtime = /* @__PURE__ */ new Map();
|
|
28376
|
+
/** La primera vez que este proceso vio vencido cada mensaje: de ahi cuenta la hora de margen. */
|
|
28377
|
+
firstDue = /* @__PURE__ */ new Map();
|
|
28378
|
+
/** Los que se estan mandando ahora, desde este proceso. */
|
|
28379
|
+
inFlight = /* @__PURE__ */ new Set();
|
|
28380
|
+
/** La pestana de cada uno que se esta mandando: de a uno por pestana. */
|
|
28381
|
+
inFlightTabs = /* @__PURE__ */ new Map();
|
|
28382
|
+
/** Hasta cuando no se le manda otro a cada pestana (`SCHEDULED_COOLDOWN_MS`). */
|
|
28383
|
+
cooldownUntil = /* @__PURE__ */ new Map();
|
|
28384
|
+
/** Hito 52. Hasta cuando espera cada mensaje a la otra instancia (`SCHEDULED_OTHER_INSTANCE_GRACE_MS`). */
|
|
28385
|
+
graceUntil = /* @__PURE__ */ new Map();
|
|
28386
|
+
ticking = false;
|
|
28387
|
+
again = false;
|
|
28388
|
+
stopped = true;
|
|
28389
|
+
cancelTimer = null;
|
|
28390
|
+
timerAt = Number.POSITIVE_INFINITY;
|
|
28391
|
+
lastPublished = "";
|
|
28392
|
+
setTimer;
|
|
28393
|
+
/** Arranca: despues de restaurar las pestanas, o no sabria cual es la de cada mensaje. */
|
|
28394
|
+
start() {
|
|
28395
|
+
if (!this.stopped) return;
|
|
28396
|
+
this.stopped = false;
|
|
28397
|
+
void this.tick();
|
|
28398
|
+
}
|
|
28399
|
+
/** Para el reloj y suelta lo reservado: una instancia que se apaga no se queda con nada. */
|
|
28400
|
+
async stop() {
|
|
28401
|
+
this.stopped = true;
|
|
28402
|
+
this.cancelTimer?.();
|
|
28403
|
+
this.cancelTimer = null;
|
|
28404
|
+
this.timerAt = Number.POSITIVE_INFINITY;
|
|
28405
|
+
await this.deps.store.releaseReserved();
|
|
28406
|
+
}
|
|
28407
|
+
/** Algo cambio (el agente quedo libre, se toco un ajuste): una vuelta pronto. */
|
|
28408
|
+
nudge() {
|
|
28409
|
+
if (this.stopped) return;
|
|
28410
|
+
this.schedule(SCHEDULER_NUDGE_MS);
|
|
28411
|
+
}
|
|
28412
|
+
/** La lista tal como va a las ventanas, con la pestana de hoy de cada conversacion. */
|
|
28413
|
+
snapshot() {
|
|
28414
|
+
const tabs = this.deps.tabs();
|
|
28415
|
+
const now = this.deps.now();
|
|
28416
|
+
const enabled2 = this.deps.enabled();
|
|
28417
|
+
return this.deps.store.list().sort((a, b) => a.sendAt - b.sendAt || a.createdAt - b.createdAt).map((message) => {
|
|
28418
|
+
const tab = tabs.find((entry) => entry.agent === message.agent && entry.sessionId === message.sessionId);
|
|
28419
|
+
const running = this.runtime.get(message.id);
|
|
28420
|
+
let state;
|
|
28421
|
+
let note = null;
|
|
28422
|
+
if (message.failure !== void 0) {
|
|
28423
|
+
state = "failed";
|
|
28424
|
+
note = message.failure;
|
|
28425
|
+
} else if (this.inFlight.has(message.id)) {
|
|
28426
|
+
state = "sending";
|
|
28427
|
+
} else if (!enabled2) {
|
|
28428
|
+
state = "paused";
|
|
28429
|
+
} else if (running !== void 0) {
|
|
28430
|
+
state = running.state;
|
|
28431
|
+
note = running.note;
|
|
28432
|
+
} else {
|
|
28433
|
+
state = now < message.sendAt ? "scheduled" : "waiting";
|
|
28434
|
+
}
|
|
28435
|
+
return {
|
|
28436
|
+
id: message.id,
|
|
28437
|
+
agent: message.agent,
|
|
28438
|
+
sessionId: message.sessionId,
|
|
28439
|
+
terminalId: tab?.terminalId ?? null,
|
|
28440
|
+
text: message.text,
|
|
28441
|
+
sendAt: message.sendAt,
|
|
28442
|
+
createdAt: message.createdAt,
|
|
28443
|
+
state,
|
|
28444
|
+
note
|
|
28445
|
+
};
|
|
28446
|
+
});
|
|
28447
|
+
}
|
|
28448
|
+
/** Avisa la lista si cambio desde la ultima vez. Tambien cuando cambian las pestanas: cambia la de cada uno. */
|
|
28449
|
+
publish() {
|
|
28450
|
+
const list = this.snapshot();
|
|
28451
|
+
const key = JSON.stringify(list);
|
|
28452
|
+
if (key === this.lastPublished) return;
|
|
28453
|
+
this.lastPublished = key;
|
|
28454
|
+
this.deps.onChange(list);
|
|
28455
|
+
}
|
|
28456
|
+
async create(owner, agent, text, sendAt) {
|
|
28457
|
+
const message = {
|
|
28458
|
+
id: randomUUID8(),
|
|
28459
|
+
agent,
|
|
28460
|
+
sessionId: owner.sessionId,
|
|
28461
|
+
text,
|
|
28462
|
+
sendAt,
|
|
28463
|
+
createdAt: this.deps.now()
|
|
28464
|
+
};
|
|
28465
|
+
await this.deps.store.add(message);
|
|
28466
|
+
this.publish();
|
|
28467
|
+
this.nudge();
|
|
28468
|
+
return message;
|
|
28469
|
+
}
|
|
28470
|
+
/** Lo saca, si no se esta mandando. Su texto vuelve al cuadro: lo hace quien llama. */
|
|
28471
|
+
async cancel(id) {
|
|
28472
|
+
await this.deps.store.refresh();
|
|
28473
|
+
if (this.deps.store.get(id) === null) return { kind: "gone" };
|
|
28474
|
+
if (this.inFlight.has(id)) return { kind: "sending" };
|
|
28475
|
+
const claim = await this.deps.store.claimOf(id);
|
|
28476
|
+
if (claim !== null && !claim.mine && claim.alive && claim.phase === "writing") return { kind: "sending" };
|
|
28477
|
+
const removed = await this.deps.store.remove(id);
|
|
28478
|
+
if (removed === null) return { kind: "gone" };
|
|
28479
|
+
await this.deps.store.release(id);
|
|
28480
|
+
this.forget(id);
|
|
28481
|
+
this.publish();
|
|
28482
|
+
return { kind: "removed", message: removed };
|
|
28483
|
+
}
|
|
28484
|
+
/** Lo pone a la hora de ahora y le borra la falla: se manda con las reglas de siempre. */
|
|
28485
|
+
async sendNow(id) {
|
|
28486
|
+
if (!this.deps.enabled()) return "off";
|
|
28487
|
+
await this.deps.store.refresh();
|
|
28488
|
+
if (this.deps.store.get(id) === null) return "gone";
|
|
28489
|
+
if (this.inFlight.has(id)) return "sending";
|
|
28490
|
+
const claim = await this.deps.store.claimOf(id);
|
|
28491
|
+
if (claim !== null && !claim.mine && claim.alive && claim.phase === "writing") return "sending";
|
|
28492
|
+
const updated = await this.deps.store.update(id, { sendAt: this.deps.now(), failure: null });
|
|
28493
|
+
if (updated === null) return "gone";
|
|
28494
|
+
this.forget(id);
|
|
28495
|
+
this.publish();
|
|
28496
|
+
this.nudge();
|
|
28497
|
+
return "ok";
|
|
28498
|
+
}
|
|
28499
|
+
/** Se cerro la pestana: lo de su conversacion se cancela (la web lo pregunto antes). */
|
|
28500
|
+
async dropOwner(owner) {
|
|
28501
|
+
const removed = await this.deps.store.removeOwner(owner);
|
|
28502
|
+
for (const message of removed) {
|
|
28503
|
+
if (!this.inFlight.has(message.id)) await this.deps.store.release(message.id);
|
|
28504
|
+
this.forget(message.id);
|
|
28505
|
+
}
|
|
28506
|
+
if (removed.length > 0) this.publish();
|
|
28507
|
+
}
|
|
28508
|
+
/** La pestana cambio de conversacion: lo programado va con ella, como el borrador. */
|
|
28509
|
+
async moveOwner(from, to) {
|
|
28510
|
+
if (await this.deps.store.move(from, to) > 0) {
|
|
28511
|
+
this.publish();
|
|
28512
|
+
this.nudge();
|
|
28513
|
+
}
|
|
28514
|
+
}
|
|
28515
|
+
/** Una vuelta. Nunca dos a la vez: la que llega mientras corre otra se hace despues. */
|
|
28516
|
+
async tick() {
|
|
28517
|
+
if (this.stopped) return;
|
|
28518
|
+
if (this.ticking) {
|
|
28519
|
+
this.again = true;
|
|
28520
|
+
return;
|
|
28521
|
+
}
|
|
28522
|
+
this.ticking = true;
|
|
28523
|
+
try {
|
|
28524
|
+
await this.pass();
|
|
28525
|
+
} catch (error) {
|
|
28526
|
+
this.deps.log?.(`[scheduled] a pass failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
28527
|
+
} finally {
|
|
28528
|
+
this.ticking = false;
|
|
28529
|
+
this.publish();
|
|
28530
|
+
if (this.again) {
|
|
28531
|
+
this.again = false;
|
|
28532
|
+
this.schedule(0);
|
|
28533
|
+
} else {
|
|
28534
|
+
this.scheduleNext();
|
|
28535
|
+
}
|
|
28536
|
+
}
|
|
28537
|
+
}
|
|
28538
|
+
// ---- por dentro ----
|
|
28539
|
+
forget(id) {
|
|
28540
|
+
this.runtime.delete(id);
|
|
28541
|
+
this.firstDue.delete(id);
|
|
28542
|
+
this.graceUntil.delete(id);
|
|
28543
|
+
}
|
|
28544
|
+
set(id, state, note) {
|
|
28545
|
+
this.runtime.set(id, { state, note });
|
|
28546
|
+
}
|
|
28547
|
+
/** true si a esa pestana ya se le esta mandando uno, o se le mando hace muy poco. */
|
|
28548
|
+
tabCooling(terminalId, now) {
|
|
28549
|
+
if ((this.cooldownUntil.get(terminalId) ?? 0) > now) return true;
|
|
28550
|
+
for (const tab of this.inFlightTabs.values()) {
|
|
28551
|
+
if (tab === terminalId) return true;
|
|
28552
|
+
}
|
|
28553
|
+
return false;
|
|
28554
|
+
}
|
|
28555
|
+
async fail(id, note) {
|
|
28556
|
+
await this.deps.store.update(id, { failure: note });
|
|
28557
|
+
await this.deps.store.release(id);
|
|
28558
|
+
this.set(id, "failed", note);
|
|
28559
|
+
this.firstDue.delete(id);
|
|
28560
|
+
}
|
|
28561
|
+
/** Una vuelta dentro de `ms`, salvo que ya haya una antes. */
|
|
28562
|
+
schedule(ms) {
|
|
28563
|
+
if (this.stopped) return;
|
|
28564
|
+
const at = this.deps.now() + ms;
|
|
28565
|
+
if (this.cancelTimer !== null && this.timerAt <= at) return;
|
|
28566
|
+
this.cancelTimer?.();
|
|
28567
|
+
this.timerAt = at;
|
|
28568
|
+
this.cancelTimer = this.setTimer(() => {
|
|
28569
|
+
this.cancelTimer = null;
|
|
28570
|
+
this.timerAt = Number.POSITIVE_INFINITY;
|
|
28571
|
+
void this.tick();
|
|
28572
|
+
}, ms);
|
|
28573
|
+
}
|
|
28574
|
+
/** La proxima vuelta: a la hora del proximo mensaje, o a los 30 s, lo que llegue antes. */
|
|
28575
|
+
scheduleNext() {
|
|
28576
|
+
const now = this.deps.now();
|
|
28577
|
+
let wait = SCHEDULER_TICK_MS;
|
|
28578
|
+
if (this.deps.enabled()) {
|
|
28579
|
+
for (const message of this.deps.store.list()) {
|
|
28580
|
+
if (message.failure !== void 0 || message.sendAt <= now) continue;
|
|
28581
|
+
wait = Math.min(wait, message.sendAt - now + 20);
|
|
28582
|
+
}
|
|
28583
|
+
for (const until of this.cooldownUntil.values()) {
|
|
28584
|
+
if (until > now) wait = Math.min(wait, until - now + 20);
|
|
28585
|
+
}
|
|
28586
|
+
for (const until of this.graceUntil.values()) {
|
|
28587
|
+
if (until > now) wait = Math.min(wait, until - now + 20);
|
|
28588
|
+
}
|
|
28589
|
+
}
|
|
28590
|
+
this.schedule(Math.max(0, wait));
|
|
28591
|
+
}
|
|
28592
|
+
async pass() {
|
|
28593
|
+
const { store } = this.deps;
|
|
28594
|
+
await store.refresh();
|
|
28595
|
+
await this.deps.refreshEnabled?.();
|
|
28596
|
+
const messages = store.list().sort((a, b) => a.sendAt - b.sendAt || a.createdAt - b.createdAt);
|
|
28597
|
+
const present = new Set(messages.map((message) => message.id));
|
|
28598
|
+
for (const id of [...this.runtime.keys()]) {
|
|
28599
|
+
if (!present.has(id)) this.forget(id);
|
|
28600
|
+
}
|
|
28601
|
+
for (const id of await store.claimedIds()) {
|
|
28602
|
+
if (present.has(id) || this.inFlight.has(id)) continue;
|
|
28603
|
+
await store.release(id);
|
|
28604
|
+
await store.discardOrphan(id);
|
|
28605
|
+
}
|
|
28606
|
+
const tabs = this.deps.tabs();
|
|
28607
|
+
for (const message of messages) {
|
|
28608
|
+
if (this.stopped) return;
|
|
28609
|
+
if (this.inFlight.has(message.id)) continue;
|
|
28610
|
+
await this.consider(message, tabs);
|
|
28611
|
+
}
|
|
28612
|
+
}
|
|
28613
|
+
async consider(message, tabs) {
|
|
28614
|
+
const { store } = this.deps;
|
|
28615
|
+
const { id } = message;
|
|
28616
|
+
const now = this.deps.now();
|
|
28617
|
+
if (message.failure !== void 0) {
|
|
28618
|
+
this.set(id, "failed", message.failure);
|
|
28619
|
+
return;
|
|
28620
|
+
}
|
|
28621
|
+
if (!this.deps.enabled()) {
|
|
28622
|
+
this.firstDue.delete(id);
|
|
28623
|
+
await store.release(id);
|
|
28624
|
+
this.set(id, "paused", null);
|
|
28625
|
+
return;
|
|
28626
|
+
}
|
|
28627
|
+
if (now < message.sendAt) {
|
|
28628
|
+
this.firstDue.delete(id);
|
|
28629
|
+
this.set(id, "scheduled", null);
|
|
28630
|
+
return;
|
|
28631
|
+
}
|
|
28632
|
+
const firstDue = this.firstDue.get(id) ?? now;
|
|
28633
|
+
this.firstDue.set(id, firstDue);
|
|
28634
|
+
const claim = await store.claimOf(id);
|
|
28635
|
+
if (claim !== null && !claim.mine) {
|
|
28636
|
+
if (claim.alive) {
|
|
28637
|
+
this.set(id, "waiting", serverText("scheduledWaitOtherInstance"));
|
|
28638
|
+
return;
|
|
28639
|
+
}
|
|
28640
|
+
if (claim.phase === "writing") {
|
|
28641
|
+
await this.fail(id, serverText("scheduledUncertain"));
|
|
28642
|
+
await store.discardOrphan(id);
|
|
28643
|
+
return;
|
|
28644
|
+
}
|
|
28645
|
+
}
|
|
28646
|
+
const mine = claim?.mine === true;
|
|
28647
|
+
if (!mine && firstDue - message.sendAt > SCHEDULE_LATE_LIMIT_MS) {
|
|
28648
|
+
await this.fail(id, serverText("scheduledMissed"));
|
|
28649
|
+
return;
|
|
28650
|
+
}
|
|
28651
|
+
const overdue = now - message.sendAt > SCHEDULE_LATE_LIMIT_MS;
|
|
28652
|
+
const tab = tabs.find((entry) => entry.agent === message.agent && entry.sessionId === message.sessionId) ?? null;
|
|
28653
|
+
if (tab === null) {
|
|
28654
|
+
if (mine) await store.release(id);
|
|
28655
|
+
if (overdue) await this.fail(id, serverText("scheduledNoTab"));
|
|
28656
|
+
else this.set(id, "waiting", serverText("scheduledWaitNoTab"));
|
|
28657
|
+
return;
|
|
28658
|
+
}
|
|
28659
|
+
const agent = this.deps.agentOf(tab.agent);
|
|
28660
|
+
if (agent === null || !agent.scheduledSend) {
|
|
28661
|
+
await this.fail(id, serverText("scheduleUnsupported", { label: agent?.label ?? tab.agent }));
|
|
28662
|
+
return;
|
|
28663
|
+
}
|
|
28664
|
+
if (tab.alive) {
|
|
28665
|
+
if (await store.claim(id) !== "mine") {
|
|
28666
|
+
this.set(id, "waiting", serverText("scheduledWaitOtherInstance"));
|
|
28667
|
+
return;
|
|
28668
|
+
}
|
|
28669
|
+
const free = await this.freeNow(tab);
|
|
28670
|
+
if (free === "idle" && !this.tabCooling(tab.terminalId, now)) {
|
|
28671
|
+
this.startDelivery(message, tab, false);
|
|
28672
|
+
return;
|
|
28673
|
+
}
|
|
28674
|
+
this.set(id, "waiting", free === "waiting" ? serverText("scheduledWaitAnswer") : serverText("scheduledWaitBusy"));
|
|
28675
|
+
return;
|
|
28676
|
+
}
|
|
28677
|
+
if (this.tabCooling(tab.terminalId, now)) {
|
|
28678
|
+
this.set(id, "waiting", serverText("scheduledWaitBusy"));
|
|
28679
|
+
return;
|
|
28680
|
+
}
|
|
28681
|
+
if (!mine && this.deps.tellsOpenElsewhere?.(tab.agent) === false && await this.deps.othersRunning?.() === true) {
|
|
28682
|
+
const graceEnd = firstDue + SCHEDULED_OTHER_INSTANCE_GRACE_MS;
|
|
28683
|
+
if (now < graceEnd) {
|
|
28684
|
+
this.graceUntil.set(id, graceEnd);
|
|
28685
|
+
this.set(id, "waiting", serverText("scheduledWaitGrace"));
|
|
28686
|
+
return;
|
|
28687
|
+
}
|
|
28688
|
+
}
|
|
28689
|
+
this.graceUntil.delete(id);
|
|
28690
|
+
if (await this.deps.isOpenElsewhere(tab.agent, tab.sessionId)) {
|
|
28691
|
+
if (mine) await store.release(id);
|
|
28692
|
+
if (overdue) await this.fail(id, serverText("scheduledElsewhereTimeout"));
|
|
28693
|
+
else this.set(id, "waiting", serverText("scheduledWaitElsewhere"));
|
|
28694
|
+
return;
|
|
28695
|
+
}
|
|
28696
|
+
const claimed = await store.claim(id);
|
|
28697
|
+
if (claimed === "orphan-writing") {
|
|
28698
|
+
await this.fail(id, serverText("scheduledUncertain"));
|
|
28699
|
+
await store.discardOrphan(id);
|
|
28700
|
+
return;
|
|
28701
|
+
}
|
|
28702
|
+
if (claimed !== "mine") {
|
|
28703
|
+
this.set(id, "waiting", serverText("scheduledWaitOtherInstance"));
|
|
28704
|
+
return;
|
|
28705
|
+
}
|
|
28706
|
+
this.startDelivery(message, tab, true);
|
|
28707
|
+
}
|
|
28708
|
+
/** Si la CLI de la pestana esta libre: `freeNow`, o la actividad del registro. */
|
|
28709
|
+
async freeNow(tab) {
|
|
28710
|
+
if (this.deps.freeNow !== void 0) return this.deps.freeNow(tab);
|
|
28711
|
+
const activity = this.deps.activityOf(tab.terminalId);
|
|
28712
|
+
return activity === "idle" || activity === "busy" || activity === "waiting" ? activity : "unknown";
|
|
28713
|
+
}
|
|
28714
|
+
startDelivery(message, tab, wake) {
|
|
28715
|
+
const { store } = this.deps;
|
|
28716
|
+
const { id } = message;
|
|
28717
|
+
this.inFlight.add(id);
|
|
28718
|
+
this.inFlightTabs.set(id, tab.terminalId);
|
|
28719
|
+
this.runtime.delete(id);
|
|
28720
|
+
this.publish();
|
|
28721
|
+
void (async () => {
|
|
28722
|
+
let failure = null;
|
|
28723
|
+
let delivered = false;
|
|
28724
|
+
let cancelled = false;
|
|
28725
|
+
let stillBusy = false;
|
|
28726
|
+
try {
|
|
28727
|
+
await store.refresh();
|
|
28728
|
+
if (store.get(id) === null) {
|
|
28729
|
+
cancelled = true;
|
|
28730
|
+
} else {
|
|
28731
|
+
await store.setPhase(id, "writing");
|
|
28732
|
+
if (wake && !await this.deps.wake(tab.terminalId)) failure = serverText("scheduledNoTab");
|
|
28733
|
+
if (failure === null) {
|
|
28734
|
+
const outcome = await this.deps.deliver(tab.terminalId, message.text);
|
|
28735
|
+
if (outcome.kind === "busy") {
|
|
28736
|
+
stillBusy = true;
|
|
28737
|
+
} else {
|
|
28738
|
+
failure = scheduledFailureFor(outcome);
|
|
28739
|
+
delivered = failure === null;
|
|
28740
|
+
}
|
|
28741
|
+
}
|
|
28742
|
+
}
|
|
28743
|
+
} catch (error) {
|
|
28744
|
+
this.deps.log?.(`[scheduled] couldn't open the CLI for ${id.slice(0, 8)}: ${error instanceof Error ? error.message : String(error)}`);
|
|
28745
|
+
failure = serverText("scheduledCliOpenFailed");
|
|
28746
|
+
}
|
|
28747
|
+
try {
|
|
28748
|
+
if (delivered) await store.remove(id);
|
|
28749
|
+
else if (!cancelled && failure !== null) await store.update(id, { failure });
|
|
28750
|
+
} catch (error) {
|
|
28751
|
+
this.deps.log?.(`[scheduled] couldn't save what happened to ${id.slice(0, 8)}: ${error instanceof Error ? error.message : String(error)}`);
|
|
28752
|
+
} finally {
|
|
28753
|
+
await store.release(id).catch(() => void 0);
|
|
28754
|
+
this.inFlight.delete(id);
|
|
28755
|
+
this.inFlightTabs.delete(id);
|
|
28756
|
+
if (!cancelled) this.cooldownUntil.set(tab.terminalId, this.deps.now() + SCHEDULED_COOLDOWN_MS);
|
|
28757
|
+
this.forget(id);
|
|
28758
|
+
if (stillBusy) this.set(id, "waiting", serverText("scheduledWaitBusy"));
|
|
28759
|
+
this.publish();
|
|
28760
|
+
this.nudge();
|
|
28761
|
+
}
|
|
28762
|
+
})();
|
|
28763
|
+
}
|
|
28764
|
+
};
|
|
28765
|
+
|
|
28766
|
+
// packages/server/src/scheduled-messages/delivery.ts
|
|
28767
|
+
var SCHEDULED_POLL_MS = 250;
|
|
28768
|
+
function normalized(text) {
|
|
28769
|
+
return text.replace(/\s+/g, " ").trim();
|
|
28770
|
+
}
|
|
28771
|
+
function isUserMessageWith(event2, text) {
|
|
28772
|
+
if (event2.role !== "user") return false;
|
|
28773
|
+
const said = normalized(event2.parts.map((part) => part.kind === "text" ? part.text : "").join(" "));
|
|
28774
|
+
const sent = normalized(text);
|
|
28775
|
+
if (said.length === 0 || sent.length === 0) return false;
|
|
28776
|
+
const head = sent.slice(0, 200);
|
|
28777
|
+
return said === sent || said.includes(head) || said.length >= 40 && sent.startsWith(said.slice(0, 200));
|
|
28778
|
+
}
|
|
28779
|
+
async function freenessOf(hasStatus, activity, follower, launchedAt) {
|
|
28780
|
+
if (hasStatus) {
|
|
28781
|
+
if (activity === "idle") return "idle";
|
|
28782
|
+
if (activity === "busy") return "busy";
|
|
28783
|
+
if (activity === "waiting") return "waiting";
|
|
28784
|
+
return "unknown";
|
|
28785
|
+
}
|
|
28786
|
+
if (follower === null || launchedAt === null) return "unknown";
|
|
28787
|
+
await follower.poll().catch(() => null);
|
|
28788
|
+
if (follower.hasOpenTurn?.(launchedAt) === true || follower.hasOpenToolCall(launchedAt)) return "busy";
|
|
28789
|
+
return "idle";
|
|
28790
|
+
}
|
|
28791
|
+
async function deliverScheduled(deps, request) {
|
|
28792
|
+
const { terminalId, text, support } = request;
|
|
28793
|
+
const pollMs = request.pollMs ?? SCHEDULED_POLL_MS;
|
|
28794
|
+
const first = deps.tab(terminalId);
|
|
28795
|
+
if (first === null) return { kind: "gone" };
|
|
28796
|
+
if (!first.alive || first.sessionId.length === 0) return { kind: "not-ready" };
|
|
28797
|
+
const target = { cwd: first.cwd, sessionId: first.sessionId };
|
|
28798
|
+
const follower = deps.follow(target);
|
|
28799
|
+
await follower.start();
|
|
28800
|
+
await follower.poll().catch(() => null);
|
|
28801
|
+
const screen = support.kind === "screen" ? deps.screen(terminalId) : null;
|
|
28802
|
+
if (support.kind === "screen" && screen === null) return { kind: "not-ready" };
|
|
28803
|
+
try {
|
|
28804
|
+
const deadline = deps.now() + request.readyTimeoutMs;
|
|
28805
|
+
let previous = null;
|
|
28806
|
+
let last = "unknown";
|
|
28807
|
+
for (; ; ) {
|
|
28808
|
+
const tab = deps.tab(terminalId);
|
|
28809
|
+
if (tab === null) return { kind: "gone" };
|
|
28810
|
+
if (!tab.alive || tab.sessionId !== target.sessionId) return { kind: "not-ready" };
|
|
28811
|
+
last = await freenessOf(request.hasStatus, deps.activityOf(terminalId), follower, tab.launchedAt);
|
|
28812
|
+
if (last === "idle") {
|
|
28813
|
+
if (screen === null) break;
|
|
28814
|
+
const lines = await screen.lines();
|
|
28815
|
+
const key = lines.join("\n");
|
|
28816
|
+
if (support.kind === "screen" && support.readyScreen(lines)) {
|
|
28817
|
+
if (key === previous) break;
|
|
28818
|
+
previous = key;
|
|
28819
|
+
} else {
|
|
28820
|
+
previous = null;
|
|
28821
|
+
}
|
|
28822
|
+
} else {
|
|
28823
|
+
previous = null;
|
|
28824
|
+
}
|
|
28825
|
+
if (deps.now() >= deadline) return last === "busy" || last === "waiting" ? { kind: "busy" } : { kind: "not-ready" };
|
|
28826
|
+
await deps.wait(pollMs);
|
|
28827
|
+
}
|
|
28828
|
+
} finally {
|
|
28829
|
+
screen?.dispose();
|
|
28830
|
+
}
|
|
28831
|
+
const writtenAt = deps.now();
|
|
28832
|
+
if (support.kind === "api") {
|
|
28833
|
+
const outcome = await support.deliver(target, text).catch(() => "failed");
|
|
28834
|
+
if (outcome === "not-sent") return { kind: "not-ready" };
|
|
28835
|
+
} else {
|
|
28836
|
+
const result = await deps.submit(terminalId, text);
|
|
28837
|
+
switch (result.kind) {
|
|
28838
|
+
case "written":
|
|
28839
|
+
break;
|
|
28840
|
+
case "refusal":
|
|
28841
|
+
case "prepare-failed":
|
|
28842
|
+
case "dropped":
|
|
28843
|
+
return { kind: "not-ready" };
|
|
28844
|
+
case "nothing":
|
|
28845
|
+
return { kind: "nothing" };
|
|
28846
|
+
case "refused":
|
|
28847
|
+
return { kind: "refused" };
|
|
28848
|
+
case "blocked":
|
|
28849
|
+
case "interrupted":
|
|
28850
|
+
case "failed":
|
|
28851
|
+
return { kind: "interrupted" };
|
|
28852
|
+
}
|
|
28853
|
+
}
|
|
28854
|
+
const confirmDeadline = deps.now() + support.confirmTimeoutMs;
|
|
28855
|
+
for (; ; ) {
|
|
28856
|
+
const read2 = await follower.poll().catch(() => null);
|
|
28857
|
+
if (read2 !== null && read2.added.some((event2) => (event2.at === 0 || event2.at >= writtenAt - 5e3) && isUserMessageWith(event2, text))) {
|
|
28858
|
+
return { kind: "sent" };
|
|
28859
|
+
}
|
|
28860
|
+
if (request.hasStatus && deps.activityOf(terminalId) === "busy") return { kind: "sent" };
|
|
28861
|
+
if (deps.now() >= confirmDeadline) return { kind: "unconfirmed" };
|
|
28862
|
+
await deps.wait(pollMs);
|
|
28863
|
+
}
|
|
28864
|
+
}
|
|
28865
|
+
|
|
27550
28866
|
// packages/server/src/terminal-socket.ts
|
|
27551
28867
|
var NOTE_SEND_TIMEOUT_MS = 15e3;
|
|
27552
28868
|
var REWIND_EXTRA_STEPS = 30;
|
|
@@ -27593,7 +28909,8 @@ function attachTerminalSocket(options) {
|
|
|
27593
28909
|
defaultCwd,
|
|
27594
28910
|
vault,
|
|
27595
28911
|
remote,
|
|
27596
|
-
worktrees
|
|
28912
|
+
worktrees,
|
|
28913
|
+
scheduled
|
|
27597
28914
|
} = options;
|
|
27598
28915
|
const pasteStore = options.pasteStore ?? new PasteStore();
|
|
27599
28916
|
const writeQueue = new TerminalWriteQueue();
|
|
@@ -27658,12 +28975,12 @@ function attachTerminalSocket(options) {
|
|
|
27658
28975
|
agents: agents.list(),
|
|
27659
28976
|
defaultAgent: agents.defaultAgent()
|
|
27660
28977
|
});
|
|
27661
|
-
const writeToTerminal = (socket, terminalId, data) => {
|
|
28978
|
+
const writeToTerminal = (socket, terminalId, data, refusedAs) => {
|
|
27662
28979
|
if (registry.write(terminalId, data)) return true;
|
|
27663
28980
|
if (registry.get(terminalId) === null) {
|
|
27664
|
-
sendError(socket, "unknown-terminal", serverText("terminalGone"));
|
|
28981
|
+
sendError(socket, refusedAs ?? "unknown-terminal", serverText("terminalGone"));
|
|
27665
28982
|
} else {
|
|
27666
|
-
sendError(socket, "terminal-asleep", serverText("terminalAsleep"));
|
|
28983
|
+
sendError(socket, refusedAs ?? "terminal-asleep", serverText("terminalAsleep"));
|
|
27667
28984
|
}
|
|
27668
28985
|
return false;
|
|
27669
28986
|
};
|
|
@@ -27676,6 +28993,13 @@ function attachTerminalSocket(options) {
|
|
|
27676
28993
|
for (const { from, to } of moved) drafts.move(from, to);
|
|
27677
28994
|
const entries = draftEntriesOf(appeared, drafts).filter((entry) => !isEmptyComposerDraft(entry.draft));
|
|
27678
28995
|
if (entries.length > 0) broadcast({ type: "composer.drafts", drafts: entries });
|
|
28996
|
+
for (const { from, to } of moved) {
|
|
28997
|
+
void scheduler.moveOwner(from, to).catch((error) => {
|
|
28998
|
+
console.warn("[scheduled] couldn't move the messages of a tab:", error);
|
|
28999
|
+
});
|
|
29000
|
+
}
|
|
29001
|
+
scheduler.publish();
|
|
29002
|
+
scheduler.nudge();
|
|
27679
29003
|
};
|
|
27680
29004
|
const onRegistryExit = (terminalId, exitCode, signal) => broadcast({ type: "terminal.exit", terminalId, exitCode, signal });
|
|
27681
29005
|
const onRegistryActivity = (terminalId, activity, offlineReason) => {
|
|
@@ -27684,6 +29008,7 @@ function attachTerminalSocket(options) {
|
|
|
27684
29008
|
offlineReason === null ? { type: "terminal.activity", terminalId, activity } : { type: "terminal.activity", terminalId, activity, offlineReason }
|
|
27685
29009
|
);
|
|
27686
29010
|
if (registry.get(terminalId)?.review === true) worktrees.reviewActivity();
|
|
29011
|
+
scheduler.nudge();
|
|
27687
29012
|
};
|
|
27688
29013
|
const onIndexStatus = (status) => broadcast({ type: "index.status", status });
|
|
27689
29014
|
const onIndexProjects = (projects, replace) => broadcast({ type: "index.projects", projects, replace });
|
|
@@ -27719,7 +29044,7 @@ function attachTerminalSocket(options) {
|
|
|
27719
29044
|
const onConversationState = (terminalId, state) => broadcast({ type: "conversation.state", terminalId, state });
|
|
27720
29045
|
const onConversationMode = (terminalId, mode) => broadcast({ type: "conversation.mode", terminalId, mode });
|
|
27721
29046
|
const onConversationWaiting = (terminalId, waitingFor) => broadcast({ type: "conversation.waiting", terminalId, waitingFor });
|
|
27722
|
-
const onConversationToolCall = (terminalId,
|
|
29047
|
+
const onConversationToolCall = (terminalId, open10) => broadcast({ type: "conversation.toolCall", terminalId, open: open10 });
|
|
27723
29048
|
const onConversationSubagents = (terminalId, subagents) => broadcast({ type: "conversation.subagents", terminalId, subagents });
|
|
27724
29049
|
const onConversationLiveQuestion = (terminalId, question) => broadcast({ type: "conversation.liveQuestion", terminalId, question });
|
|
27725
29050
|
const onConversationQueue = (terminalId, queue) => broadcast({ type: "conversation.queue", terminalId, queue });
|
|
@@ -27733,6 +29058,7 @@ function attachTerminalSocket(options) {
|
|
|
27733
29058
|
return await adapter.defaults(descriptor.cwd, sessionModel) ?? NO_DEFAULTS;
|
|
27734
29059
|
};
|
|
27735
29060
|
const pieceWriter = (socket, terminalId) => (piece) => writeToTerminal(socket, terminalId, piece);
|
|
29061
|
+
const answerWriter = (socket, terminalId) => (piece) => writeToTerminal(socket, terminalId, piece, "answer-failed");
|
|
27736
29062
|
const interruptAgent = (socket, terminalId, options2) => {
|
|
27737
29063
|
writeQueue.interrupt(terminalId);
|
|
27738
29064
|
const [first, ...rest] = buildInterruptKeys(inputOf(terminalId).interruptPresses);
|
|
@@ -27807,6 +29133,12 @@ function attachTerminalSocket(options) {
|
|
|
27807
29133
|
const draftOwner = draftOwnerOf(registry.get(terminalId));
|
|
27808
29134
|
if (!registry.close(terminalId)) return false;
|
|
27809
29135
|
if (draftOwner !== null) drafts.delete(draftOwner);
|
|
29136
|
+
turnProbes.delete(terminalId);
|
|
29137
|
+
if (draftOwner !== null) {
|
|
29138
|
+
void scheduler.dropOwner(draftOwner).catch((error) => {
|
|
29139
|
+
console.warn("[scheduled] couldn't cancel the messages of a closed tab:", error);
|
|
29140
|
+
});
|
|
29141
|
+
}
|
|
27810
29142
|
conversations.drop(terminalId);
|
|
27811
29143
|
repos.drop(terminalId);
|
|
27812
29144
|
memory.drop(terminalId);
|
|
@@ -27828,6 +29160,111 @@ function attachTerminalSocket(options) {
|
|
|
27828
29160
|
noteSubmitted: (terminalId, text) => registry.noteSubmitted(terminalId, text),
|
|
27829
29161
|
enqueue: (terminalId, job, enqueueOptions) => writeQueue.enqueue(terminalId, job, enqueueOptions)
|
|
27830
29162
|
};
|
|
29163
|
+
const submitDeps = {
|
|
29164
|
+
adapterOf,
|
|
29165
|
+
inputOf,
|
|
29166
|
+
launchedAtOf: (terminalId) => registry.launchedAtOf(terminalId),
|
|
29167
|
+
activityOf: (terminalId) => registry.activityOf(terminalId),
|
|
29168
|
+
sessionIdOf: (terminalId) => registry.get(terminalId)?.sessionId ?? null,
|
|
29169
|
+
watchScreen: (terminalId) => registry.watchScreen(terminalId),
|
|
29170
|
+
noteSubmitted: (terminalId, text) => registry.noteSubmitted(terminalId, text),
|
|
29171
|
+
conversations,
|
|
29172
|
+
queuedSubmits,
|
|
29173
|
+
enqueue: (terminalId, job, enqueueOptions) => writeQueue.enqueue(terminalId, job, enqueueOptions),
|
|
29174
|
+
now: Date.now
|
|
29175
|
+
};
|
|
29176
|
+
const turnProbes = /* @__PURE__ */ new Map();
|
|
29177
|
+
const probeFor = (terminalId, follow, sessionId) => {
|
|
29178
|
+
const known = turnProbes.get(terminalId);
|
|
29179
|
+
if (known !== void 0 && known.sessionId === sessionId) return known;
|
|
29180
|
+
const follower = follow();
|
|
29181
|
+
const probe = { sessionId, follower, started: follower.start() };
|
|
29182
|
+
turnProbes.set(terminalId, probe);
|
|
29183
|
+
return probe;
|
|
29184
|
+
};
|
|
29185
|
+
const scheduledDeliveryDeps = (follow) => ({
|
|
29186
|
+
tab: (terminalId) => {
|
|
29187
|
+
const tab = registry.get(terminalId);
|
|
29188
|
+
if (tab === null) return null;
|
|
29189
|
+
return { alive: tab.alive, sessionId: tab.sessionId, cwd: tab.cwd, launchedAt: registry.launchedAtOf(terminalId) };
|
|
29190
|
+
},
|
|
29191
|
+
activityOf: (terminalId) => registry.activityOf(terminalId),
|
|
29192
|
+
screen: (terminalId) => registry.watchScreen(terminalId),
|
|
29193
|
+
// El camino del cuadro, con sus guardas, solo texto.
|
|
29194
|
+
submit: async (terminalId, text) => {
|
|
29195
|
+
const plan = prepareSubmit(submitDeps, terminalId);
|
|
29196
|
+
if ("refusal" in plan) return { kind: "refusal" };
|
|
29197
|
+
return runSubmit(submitDeps, plan, {
|
|
29198
|
+
text,
|
|
29199
|
+
imageCount: 0,
|
|
29200
|
+
send: true,
|
|
29201
|
+
prepare: () => Promise.resolve({ fullText: text, imagePaths: [] }),
|
|
29202
|
+
write: (piece) => registry.write(terminalId, piece)
|
|
29203
|
+
});
|
|
29204
|
+
},
|
|
29205
|
+
follow,
|
|
29206
|
+
now: Date.now,
|
|
29207
|
+
wait: (ms) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
29208
|
+
});
|
|
29209
|
+
const scheduler = new MessageScheduler({
|
|
29210
|
+
store: scheduled,
|
|
29211
|
+
now: Date.now,
|
|
29212
|
+
enabled: () => appSettings.scheduledMessagesEnabled(),
|
|
29213
|
+
refreshEnabled: () => appSettings.refreshScheduledMessages(),
|
|
29214
|
+
tabs: () => registry.list().flatMap(
|
|
29215
|
+
(tab) => tab.kind === "agent" && tab.agent !== null && tab.sessionId.length > 0 ? [{ terminalId: tab.terminalId, agent: tab.agent, sessionId: tab.sessionId, alive: tab.alive }] : []
|
|
29216
|
+
),
|
|
29217
|
+
activityOf: (terminalId) => registry.activityOf(terminalId),
|
|
29218
|
+
freeNow: async (tab) => {
|
|
29219
|
+
const adapter = agents.get(tab.agent)?.adapter ?? null;
|
|
29220
|
+
if (adapter === null) return "unknown";
|
|
29221
|
+
if (adapter.status !== null) {
|
|
29222
|
+
const activity = registry.activityOf(tab.terminalId);
|
|
29223
|
+
return activity === "idle" || activity === "busy" || activity === "waiting" ? activity : "unknown";
|
|
29224
|
+
}
|
|
29225
|
+
const tabNow = registry.get(tab.terminalId);
|
|
29226
|
+
if (tabNow === null) return "unknown";
|
|
29227
|
+
const probe = probeFor(tab.terminalId, () => adapter.history.follow({ cwd: tabNow.cwd, sessionId: tab.sessionId }), tab.sessionId);
|
|
29228
|
+
await probe.started;
|
|
29229
|
+
return freenessOf(false, null, probe.follower, registry.launchedAtOf(tab.terminalId));
|
|
29230
|
+
},
|
|
29231
|
+
othersRunning: () => options.instances?.othersRunning() ?? Promise.resolve(false),
|
|
29232
|
+
tellsOpenElsewhere: (agent) => agents.get(agent)?.adapter.status?.isOpen !== void 0,
|
|
29233
|
+
agentOf: (agent) => {
|
|
29234
|
+
const registered = agents.get(agent);
|
|
29235
|
+
if (registered === null) return null;
|
|
29236
|
+
return {
|
|
29237
|
+
label: registered.adapter.label,
|
|
29238
|
+
scheduledSend: registered.adapter.capabilities.scheduledSend && registered.location !== null
|
|
29239
|
+
};
|
|
29240
|
+
},
|
|
29241
|
+
isOpenElsewhere: async (agent, sessionId) => await agents.get(agent)?.adapter.status?.isOpen?.(sessionId) === true,
|
|
29242
|
+
wake: async (terminalId) => await registry.wake(terminalId) !== null,
|
|
29243
|
+
deliver: (terminalId, text) => {
|
|
29244
|
+
const adapter = adapterOf(terminalId);
|
|
29245
|
+
if (adapter?.scheduled !== void 0) {
|
|
29246
|
+
const follow = (target) => adapter.history.follow(target);
|
|
29247
|
+
return deliverScheduled(scheduledDeliveryDeps(follow), {
|
|
29248
|
+
terminalId,
|
|
29249
|
+
text,
|
|
29250
|
+
support: adapter.scheduled,
|
|
29251
|
+
hasStatus: adapter.status !== null,
|
|
29252
|
+
readyTimeoutMs: SCHEDULED_DELIVERY_TIMEOUT_MS
|
|
29253
|
+
});
|
|
29254
|
+
}
|
|
29255
|
+
return deliverWhenReady(deliverDeps, {
|
|
29256
|
+
terminalId,
|
|
29257
|
+
text,
|
|
29258
|
+
images: [],
|
|
29259
|
+
timeoutMs: SCHEDULED_DELIVERY_TIMEOUT_MS,
|
|
29260
|
+
write: (piece) => registry.write(terminalId, piece)
|
|
29261
|
+
});
|
|
29262
|
+
},
|
|
29263
|
+
onChange: (messages) => broadcast({ type: "schedule.list", messages }),
|
|
29264
|
+
log: (line) => console.warn(line)
|
|
29265
|
+
});
|
|
29266
|
+
const startScheduler = () => scheduler.start();
|
|
29267
|
+
void options.restored.then(startScheduler, startScheduler);
|
|
27831
29268
|
const continueDeps = {
|
|
27832
29269
|
registry,
|
|
27833
29270
|
closeTab: (terminalId) => {
|
|
@@ -27896,7 +29333,11 @@ function attachTerminalSocket(options) {
|
|
|
27896
29333
|
const stopRemoteStatus = remote.onChange(
|
|
27897
29334
|
() => broadcastToHost({ type: "remote.status", status: remote.status() })
|
|
27898
29335
|
);
|
|
27899
|
-
const onAppSettingsStatus = (settings2) =>
|
|
29336
|
+
const onAppSettingsStatus = (settings2) => {
|
|
29337
|
+
broadcast({ type: "settings.status", settings: settings2 });
|
|
29338
|
+
scheduler.publish();
|
|
29339
|
+
scheduler.nudge();
|
|
29340
|
+
};
|
|
27900
29341
|
appSettings.on("status", onAppSettingsStatus);
|
|
27901
29342
|
const stopRemoteRevoked = remote.onRevoked((deviceId) => {
|
|
27902
29343
|
for (const [socket, id] of remoteSockets) {
|
|
@@ -28024,6 +29465,7 @@ function attachTerminalSocket(options) {
|
|
|
28024
29465
|
send(socket, { type: "index.projects", projects: index.getProjects(), replace: true });
|
|
28025
29466
|
send(socket, { type: "notes.list", notes: notes.list() });
|
|
28026
29467
|
send(socket, { type: "composer.drafts", drafts: draftEntriesOf(registry.list(), drafts) });
|
|
29468
|
+
send(socket, { type: "schedule.list", messages: scheduler.snapshot() });
|
|
28027
29469
|
send(socket, { type: "vault.status", status: vault.status() });
|
|
28028
29470
|
if (deviceId === null) send(socket, { type: "remote.status", status: remote.status() });
|
|
28029
29471
|
send(socket, { type: "settings.status", settings: appSettings.status() });
|
|
@@ -28105,58 +29547,19 @@ function attachTerminalSocket(options) {
|
|
|
28105
29547
|
break;
|
|
28106
29548
|
}
|
|
28107
29549
|
const submitting = adapterOf(terminalId);
|
|
28108
|
-
const
|
|
28109
|
-
|
|
28110
|
-
|
|
28111
|
-
|
|
28112
|
-
|
|
28113
|
-
const blind = isBlind();
|
|
28114
|
-
const refusal2 = submitRefusal({
|
|
28115
|
-
label: submitting.label,
|
|
28116
|
-
approvesPendingOnCycle: guardsPending,
|
|
28117
|
-
waitingFor: conversations.getWaitingFor(terminalId),
|
|
28118
|
-
waitingBlocksSubmit: blocksWhileWaiting,
|
|
28119
|
-
activity: registry.activityOf(terminalId),
|
|
28120
|
-
blind,
|
|
28121
|
-
openToolCall: blind && launchedAt !== null && conversations.hasOpenToolCall(terminalId, launchedAt)
|
|
28122
|
-
});
|
|
28123
|
-
if (refusal2 !== null) {
|
|
28124
|
-
sendError(socket, "submit-failed", refusal2);
|
|
28125
|
-
settle(false);
|
|
28126
|
-
break;
|
|
28127
|
-
}
|
|
29550
|
+
const plan = prepareSubmit(submitDeps, terminalId);
|
|
29551
|
+
if ("refusal" in plan) {
|
|
29552
|
+
sendError(socket, "submit-failed", plan.refusal);
|
|
29553
|
+
settle(false);
|
|
29554
|
+
break;
|
|
28128
29555
|
}
|
|
28129
|
-
|
|
28130
|
-
|
|
28131
|
-
|
|
28132
|
-
|
|
28133
|
-
|
|
28134
|
-
|
|
28135
|
-
|
|
28136
|
-
const input = inputOf(terminalId);
|
|
28137
|
-
const startupDialog = input.startupDialog;
|
|
28138
|
-
const firstMessage = startupDialog !== void 0 && (registry.get(terminalId)?.sessionId ?? "") === "";
|
|
28139
|
-
let startupChecked = false;
|
|
28140
|
-
let stoppedByDialog = false;
|
|
28141
|
-
const pieceGuard = firstMessage || approvalGuard !== void 0 ? async () => {
|
|
28142
|
-
if (firstMessage && startupDialog !== void 0 && !startupChecked) {
|
|
28143
|
-
startupChecked = true;
|
|
28144
|
-
const screen = registry.watchScreen(terminalId);
|
|
28145
|
-
if (screen !== null) {
|
|
28146
|
-
try {
|
|
28147
|
-
stoppedByDialog = screenShowsDialog(await screen.lines(), startupDialog);
|
|
28148
|
-
} finally {
|
|
28149
|
-
screen.dispose();
|
|
28150
|
-
}
|
|
28151
|
-
if (stoppedByDialog) return false;
|
|
28152
|
-
}
|
|
28153
|
-
}
|
|
28154
|
-
return approvalGuard === void 0 ? true : approvalGuard();
|
|
28155
|
-
} : void 0;
|
|
28156
|
-
const interrupted = () => sendError(socket, "submit-failed", serverText("submitInterrupted"));
|
|
28157
|
-
void writeQueue.enqueue(
|
|
28158
|
-
terminalId,
|
|
28159
|
-
async (lane) => {
|
|
29556
|
+
void runSubmit(submitDeps, plan, {
|
|
29557
|
+
text: text2,
|
|
29558
|
+
imageCount: images.length,
|
|
29559
|
+
send: message.send !== false,
|
|
29560
|
+
write: pieceWriter(socket, terminalId),
|
|
29561
|
+
onWritten: (outcome) => settle(outcome === "written"),
|
|
29562
|
+
prepare: async (input) => {
|
|
28160
29563
|
const imagePaths = [];
|
|
28161
29564
|
try {
|
|
28162
29565
|
for (const image of images) {
|
|
@@ -28171,7 +29574,7 @@ function attachTerminalSocket(options) {
|
|
|
28171
29574
|
error instanceof PasteImageError ? error.text : serverText("pasteImageFailed"),
|
|
28172
29575
|
detail
|
|
28173
29576
|
);
|
|
28174
|
-
return;
|
|
29577
|
+
return null;
|
|
28175
29578
|
}
|
|
28176
29579
|
const attachmentLines = [];
|
|
28177
29580
|
try {
|
|
@@ -28189,55 +29592,25 @@ function attachTerminalSocket(options) {
|
|
|
28189
29592
|
error instanceof PasteFileError ? error.text : serverText("attachmentSaveFailed"),
|
|
28190
29593
|
detail
|
|
28191
29594
|
);
|
|
28192
|
-
return;
|
|
29595
|
+
return null;
|
|
28193
29596
|
}
|
|
28194
|
-
|
|
28195
|
-
|
|
28196
|
-
|
|
28197
|
-
|
|
28198
|
-
|
|
28199
|
-
|
|
28200
|
-
|
|
28201
|
-
|
|
28202
|
-
|
|
28203
|
-
|
|
28204
|
-
|
|
28205
|
-
|
|
28206
|
-
|
|
28207
|
-
|
|
28208
|
-
pieceWriter(socket, terminalId),
|
|
28209
|
-
pieceGuard,
|
|
28210
|
-
{ startAfterMs }
|
|
29597
|
+
return { fullText: textWithAttachments(text2, attachmentLines), imagePaths };
|
|
29598
|
+
}
|
|
29599
|
+
}).then((result) => {
|
|
29600
|
+
if (result.kind === "dropped" || result.kind === "interrupted") {
|
|
29601
|
+
sendError(socket, "submit-failed", serverText("submitInterrupted"));
|
|
29602
|
+
}
|
|
29603
|
+
if (result.kind === "blocked" && result.byDialog) {
|
|
29604
|
+
sendError(socket, "submit-failed", serverText("submitStartupDialog", { label: submitting?.label ?? "" }));
|
|
29605
|
+
if (clearsDraft) send(socket, { type: "composer.prefill", terminalId, text: text2, reason: "not-ready" });
|
|
29606
|
+
} else if (result.kind === "blocked") {
|
|
29607
|
+
sendError(
|
|
29608
|
+
socket,
|
|
29609
|
+
"submit-failed",
|
|
29610
|
+
result.waitingNow ? submitting === null ? serverText("submitStoppedWaitingNoLabel") : serverText("submitStoppedWaiting", { label: submitting.label }) : submitting === null ? serverText("submitStoppedToolNoLabel") : serverText("submitStoppedTool", { label: submitting.label })
|
|
28211
29611
|
);
|
|
28212
|
-
|
|
28213
|
-
|
|
28214
|
-
conversations.noteQueued(terminalId, { text: text2, images: images.length, at: Date.now() });
|
|
28215
|
-
}
|
|
28216
|
-
if (outcome === "written" && message.send !== false && submitting?.queuedSubmits !== void 0) {
|
|
28217
|
-
queuedSubmits.note(
|
|
28218
|
-
terminalId,
|
|
28219
|
-
submissionPayload(fullText, imagePaths, input.imageReference) ?? fullText,
|
|
28220
|
-
Date.now(),
|
|
28221
|
-
fullText
|
|
28222
|
-
);
|
|
28223
|
-
}
|
|
28224
|
-
if (outcome === "interrupted") interrupted();
|
|
28225
|
-
if (outcome === "blocked" && stoppedByDialog) {
|
|
28226
|
-
sendError(socket, "submit-failed", serverText("submitStartupDialog", { label: submitting?.label ?? "" }));
|
|
28227
|
-
if (clearsDraft) send(socket, { type: "composer.prefill", terminalId, text: text2, reason: "not-ready" });
|
|
28228
|
-
return;
|
|
28229
|
-
}
|
|
28230
|
-
if (outcome === "blocked") {
|
|
28231
|
-
const waitingNow = blocksWhileWaiting && registry.activityOf(terminalId) === "waiting";
|
|
28232
|
-
sendError(
|
|
28233
|
-
socket,
|
|
28234
|
-
"submit-failed",
|
|
28235
|
-
waitingNow ? submitting === null ? serverText("submitStoppedWaitingNoLabel") : serverText("submitStoppedWaiting", { label: submitting.label }) : submitting === null ? serverText("submitStoppedToolNoLabel") : serverText("submitStoppedTool", { label: submitting.label })
|
|
28236
|
-
);
|
|
28237
|
-
}
|
|
28238
|
-
},
|
|
28239
|
-
{ onDropped: interrupted }
|
|
28240
|
-
).then(() => settle(false));
|
|
29612
|
+
}
|
|
29613
|
+
}).finally(() => settle(false));
|
|
28241
29614
|
break;
|
|
28242
29615
|
}
|
|
28243
29616
|
/*
|
|
@@ -28311,7 +29684,7 @@ function attachTerminalSocket(options) {
|
|
|
28311
29684
|
sendError(socket, "answer-failed", check === "gone" ? ANSWER_NOT_PENDING_TEXT : ANSWER_MENU_CHANGED_TEXT);
|
|
28312
29685
|
return;
|
|
28313
29686
|
}
|
|
28314
|
-
const outcome = await lane.writePieces(keys, ANSWER_KEY_INTERVAL_MS,
|
|
29687
|
+
const outcome = await lane.writePieces(keys, ANSWER_KEY_INTERVAL_MS, answerWriter(socket, message.terminalId));
|
|
28315
29688
|
if (outcome === "interrupted") interruptedAnswer();
|
|
28316
29689
|
},
|
|
28317
29690
|
{ onDropped: interruptedAnswer }
|
|
@@ -28343,7 +29716,7 @@ function attachTerminalSocket(options) {
|
|
|
28343
29716
|
const outcome = await lane.writePieces(
|
|
28344
29717
|
keys,
|
|
28345
29718
|
ANSWER_KEY_INTERVAL_MS,
|
|
28346
|
-
|
|
29719
|
+
answerWriter(socket, message.terminalId)
|
|
28347
29720
|
);
|
|
28348
29721
|
if (outcome === "interrupted") interruptedAnswer();
|
|
28349
29722
|
},
|
|
@@ -28409,10 +29782,10 @@ function attachTerminalSocket(options) {
|
|
|
28409
29782
|
nunca esconde nada.
|
|
28410
29783
|
*/
|
|
28411
29784
|
case "session.archive": {
|
|
28412
|
-
const
|
|
29785
|
+
const open10 = new Set(
|
|
28413
29786
|
registry.list().map((terminal) => terminal.sessionId).filter((id) => id.length > 0)
|
|
28414
29787
|
);
|
|
28415
|
-
const targets = message.archived ? message.sessionIds.filter((id) => !
|
|
29788
|
+
const targets = message.archived ? message.sessionIds.filter((id) => !open10.has(id)) : message.sessionIds;
|
|
28416
29789
|
if (archived.set(targets, message.archived)) index.refresh();
|
|
28417
29790
|
if (message.archived && targets.length < message.sessionIds.length) {
|
|
28418
29791
|
sendError(
|
|
@@ -29667,6 +31040,88 @@ ${seen.join("\n-----\n")}`);
|
|
|
29667
31040
|
);
|
|
29668
31041
|
break;
|
|
29669
31042
|
}
|
|
31043
|
+
/*
|
|
31044
|
+
Programar lo escrito en el cuadro (hito 51, §6.39). La pestana dice de
|
|
31045
|
+
que conversacion es; el servidor revisa todo antes de guardar. Si no lo
|
|
31046
|
+
acepta, el texto vuelve al cuadro, que ya se habia vaciado al mandarlo.
|
|
31047
|
+
Guardado, el borrador se vacia como con Enviar (§6.29).
|
|
31048
|
+
*/
|
|
31049
|
+
case "schedule.create": {
|
|
31050
|
+
const { requestId, terminalId, text: text2, sendAt } = message;
|
|
31051
|
+
const giveBack = () => {
|
|
31052
|
+
if (text2.trim().length > 0) send(socket, { type: "composer.prefill", terminalId, text: text2, reason: "unscheduled" });
|
|
31053
|
+
};
|
|
31054
|
+
const descriptor = registry.get(terminalId);
|
|
31055
|
+
if (descriptor === null) {
|
|
31056
|
+
sendError(socket, "unknown-terminal", serverText("terminalGone"), void 0, requestId);
|
|
31057
|
+
break;
|
|
31058
|
+
}
|
|
31059
|
+
const owner = draftOwnerOf(descriptor);
|
|
31060
|
+
const scheduling = adapterOf(terminalId);
|
|
31061
|
+
const refusal2 = scheduleCreateRefusal({
|
|
31062
|
+
enabled: appSettings.scheduledMessagesEnabled(),
|
|
31063
|
+
owner,
|
|
31064
|
+
agentLabel: scheduling?.label ?? null,
|
|
31065
|
+
capable: scheduling?.capabilities.scheduledSend === true,
|
|
31066
|
+
text: text2,
|
|
31067
|
+
sendAt,
|
|
31068
|
+
now: Date.now(),
|
|
31069
|
+
count: scheduled.size
|
|
31070
|
+
});
|
|
31071
|
+
if (refusal2 !== null || owner === null || descriptor.agent === null) {
|
|
31072
|
+
sendError(socket, "schedule-failed", refusal2 ?? serverText("scheduleNoConversation"), void 0, requestId);
|
|
31073
|
+
giveBack();
|
|
31074
|
+
break;
|
|
31075
|
+
}
|
|
31076
|
+
const agent = descriptor.agent;
|
|
31077
|
+
void (async () => {
|
|
31078
|
+
try {
|
|
31079
|
+
const created = await scheduler.create(owner, agent, text2, sendAt);
|
|
31080
|
+
drafts.delete(owner);
|
|
31081
|
+
broadcast({ type: "composer.drafts", drafts: draftEntriesOf(registry.list(), drafts, owner) });
|
|
31082
|
+
send(socket, { type: "schedule.created", requestId, id: created.id });
|
|
31083
|
+
} catch (error) {
|
|
31084
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
31085
|
+
console.warn("[scheduled] couldn't save a scheduled message:", detail);
|
|
31086
|
+
sendError(socket, "schedule-failed", serverText("scheduleSaveFailed"), detail, requestId);
|
|
31087
|
+
giveBack();
|
|
31088
|
+
}
|
|
31089
|
+
})();
|
|
31090
|
+
break;
|
|
31091
|
+
}
|
|
31092
|
+
/* Sacarlo: su texto vuelve al cuadro de su pestana, en esta ventana. */
|
|
31093
|
+
case "schedule.cancel": {
|
|
31094
|
+
void (async () => {
|
|
31095
|
+
const outcome = await scheduler.cancel(message.id);
|
|
31096
|
+
if (outcome.kind === "gone") {
|
|
31097
|
+
sendError(socket, "schedule-failed", serverText("scheduleGone"));
|
|
31098
|
+
return;
|
|
31099
|
+
}
|
|
31100
|
+
if (outcome.kind === "sending") {
|
|
31101
|
+
sendError(socket, "schedule-failed", serverText("scheduleSending"));
|
|
31102
|
+
return;
|
|
31103
|
+
}
|
|
31104
|
+
const { agent, sessionId, text: text2 } = outcome.message;
|
|
31105
|
+
const tab = registry.list().find((entry) => entry.kind === "agent" && entry.agent === agent && entry.sessionId === sessionId);
|
|
31106
|
+
if (tab !== void 0) send(socket, { type: "composer.prefill", terminalId: tab.terminalId, text: text2, reason: "unscheduled" });
|
|
31107
|
+
})().catch((error) => {
|
|
31108
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
31109
|
+
sendError(socket, "schedule-failed", serverText("scheduleSaveFailed"), detail);
|
|
31110
|
+
});
|
|
31111
|
+
break;
|
|
31112
|
+
}
|
|
31113
|
+
/* Mandarlo ya, con las reglas de siempre: si el agente trabaja, espera. */
|
|
31114
|
+
case "schedule.sendNow": {
|
|
31115
|
+
void scheduler.sendNow(message.id).then((outcome) => {
|
|
31116
|
+
if (outcome === "off") sendError(socket, "schedule-failed", serverText("scheduleOff"));
|
|
31117
|
+
else if (outcome === "gone") sendError(socket, "schedule-failed", serverText("scheduleGone"));
|
|
31118
|
+
else if (outcome === "sending") sendError(socket, "schedule-failed", serverText("scheduleSending"));
|
|
31119
|
+
}).catch((error) => {
|
|
31120
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
31121
|
+
sendError(socket, "schedule-failed", serverText("scheduleSaveFailed"), detail);
|
|
31122
|
+
});
|
|
31123
|
+
break;
|
|
31124
|
+
}
|
|
29670
31125
|
/*
|
|
29671
31126
|
Ajustes (Hito 41, §6.32). Una consola que ya no esta, o un disco que no
|
|
29672
31127
|
deja escribir, se dicen; lo demas lo valido el parser. A una ventana
|
|
@@ -29799,14 +31254,15 @@ ${seen.join("\n-----\n")}`);
|
|
|
29799
31254
|
appSettings.off("status", onAppSettingsStatus);
|
|
29800
31255
|
stopRemoteStatus();
|
|
29801
31256
|
stopRemoteRevoked();
|
|
31257
|
+
void scheduler.stop();
|
|
29802
31258
|
for (const client of wss.clients) client.terminate();
|
|
29803
31259
|
wss.close();
|
|
29804
31260
|
};
|
|
29805
31261
|
}
|
|
29806
31262
|
|
|
29807
31263
|
// packages/server/src/vault/catalog.ts
|
|
29808
|
-
import { open as
|
|
29809
|
-
import
|
|
31264
|
+
import { open as open9, readdir as readdir24, stat as stat47 } from "node:fs/promises";
|
|
31265
|
+
import path75 from "node:path";
|
|
29810
31266
|
import { createInterface as createInterface4 } from "node:readline";
|
|
29811
31267
|
var HEADER_MAX_BYTES = 64 * 1024;
|
|
29812
31268
|
var LOAD_CONCURRENCY = 16;
|
|
@@ -29818,7 +31274,7 @@ async function assetBytesOf(dir, agent, sessionId) {
|
|
|
29818
31274
|
const folder = assetsDir(dir, agent, sessionId);
|
|
29819
31275
|
let names;
|
|
29820
31276
|
try {
|
|
29821
|
-
names = await
|
|
31277
|
+
names = await readdir24(folder);
|
|
29822
31278
|
} catch {
|
|
29823
31279
|
return 0;
|
|
29824
31280
|
}
|
|
@@ -29826,7 +31282,7 @@ async function assetBytesOf(dir, agent, sessionId) {
|
|
|
29826
31282
|
for (const name of names) {
|
|
29827
31283
|
if (!VAULT_ASSET_NAME_PATTERN.test(name)) continue;
|
|
29828
31284
|
try {
|
|
29829
|
-
const info = await
|
|
31285
|
+
const info = await stat47(path75.join(folder, name));
|
|
29830
31286
|
if (info.isFile()) total += info.size;
|
|
29831
31287
|
} catch {
|
|
29832
31288
|
}
|
|
@@ -29837,7 +31293,7 @@ async function readEntry2(dir, agent, sessionId) {
|
|
|
29837
31293
|
const file = sessionFile(dir, agent, sessionId);
|
|
29838
31294
|
let sizeBytes;
|
|
29839
31295
|
try {
|
|
29840
|
-
const info = await
|
|
31296
|
+
const info = await stat47(file);
|
|
29841
31297
|
if (!info.isFile()) return { kind: "absent" };
|
|
29842
31298
|
sizeBytes = info.size;
|
|
29843
31299
|
} catch {
|
|
@@ -29894,7 +31350,7 @@ var VaultCatalog = class {
|
|
|
29894
31350
|
const keys = [];
|
|
29895
31351
|
let agentFolders = [];
|
|
29896
31352
|
try {
|
|
29897
|
-
agentFolders = await
|
|
31353
|
+
agentFolders = await readdir24(sessionsRoot2(dir));
|
|
29898
31354
|
} catch {
|
|
29899
31355
|
agentFolders = [];
|
|
29900
31356
|
}
|
|
@@ -29902,7 +31358,7 @@ var VaultCatalog = class {
|
|
|
29902
31358
|
if (!isSessionAgentId2(folder)) continue;
|
|
29903
31359
|
let names;
|
|
29904
31360
|
try {
|
|
29905
|
-
names = await
|
|
31361
|
+
names = await readdir24(agentSessionsDir(dir, folder));
|
|
29906
31362
|
} catch {
|
|
29907
31363
|
continue;
|
|
29908
31364
|
}
|
|
@@ -29991,7 +31447,7 @@ var VaultCatalog = class {
|
|
|
29991
31447
|
async *bodyLines(agent, sessionId) {
|
|
29992
31448
|
const dir = this.dir;
|
|
29993
31449
|
if (dir === null || !this.entries.has(keyOf3(agent, sessionId))) return;
|
|
29994
|
-
const handle = await
|
|
31450
|
+
const handle = await open9(sessionFile(dir, agent, sessionId), "r").catch(() => null);
|
|
29995
31451
|
if (handle === null) return;
|
|
29996
31452
|
const input = handle.createReadStream({ encoding: "utf8" });
|
|
29997
31453
|
const lines = createInterface4({ input, crlfDelay: Number.POSITIVE_INFINITY });
|
|
@@ -30046,22 +31502,22 @@ var VaultCatalog = class {
|
|
|
30046
31502
|
};
|
|
30047
31503
|
|
|
30048
31504
|
// packages/server/src/worktrees/links.ts
|
|
30049
|
-
import { stat as
|
|
30050
|
-
import
|
|
31505
|
+
import { stat as stat48 } from "node:fs/promises";
|
|
31506
|
+
import path76 from "node:path";
|
|
30051
31507
|
async function exists6(candidate) {
|
|
30052
31508
|
try {
|
|
30053
|
-
await
|
|
31509
|
+
await stat48(candidate);
|
|
30054
31510
|
return true;
|
|
30055
31511
|
} catch {
|
|
30056
31512
|
return false;
|
|
30057
31513
|
}
|
|
30058
31514
|
}
|
|
30059
31515
|
function recordAbove(registry, cwd) {
|
|
30060
|
-
let current =
|
|
31516
|
+
let current = path76.resolve(cwd);
|
|
30061
31517
|
for (let depth = 0; depth < 24; depth += 1) {
|
|
30062
31518
|
const record = registry.byPath(current);
|
|
30063
|
-
if (record !== null) return { record, inner:
|
|
30064
|
-
const parent =
|
|
31519
|
+
if (record !== null) return { record, inner: path76.relative(current, path76.resolve(cwd)) };
|
|
31520
|
+
const parent = path76.dirname(current);
|
|
30065
31521
|
if (parent === current) return null;
|
|
30066
31522
|
current = parent;
|
|
30067
31523
|
}
|
|
@@ -30082,7 +31538,7 @@ var WorktreeLinks = class {
|
|
|
30082
31538
|
return {
|
|
30083
31539
|
mainCwd: mainCwdFor(cwd, checkout),
|
|
30084
31540
|
worktree: {
|
|
30085
|
-
branch: checkout.branch ?? record?.branch ??
|
|
31541
|
+
branch: checkout.branch ?? record?.branch ?? path76.basename(checkout.root),
|
|
30086
31542
|
exists: true,
|
|
30087
31543
|
reopenable: false
|
|
30088
31544
|
}
|
|
@@ -30092,13 +31548,13 @@ var WorktreeLinks = class {
|
|
|
30092
31548
|
if (found !== null) {
|
|
30093
31549
|
const { record, inner } = found;
|
|
30094
31550
|
return {
|
|
30095
|
-
mainCwd: inner.length === 0 ? record.mainRoot :
|
|
31551
|
+
mainCwd: inner.length === 0 ? record.mainRoot : path76.join(record.mainRoot, inner),
|
|
30096
31552
|
worktree: { branch: record.branch, exists: false, reopenable: await this.reopenable(record) }
|
|
30097
31553
|
};
|
|
30098
31554
|
}
|
|
30099
|
-
const mainRoot = mainRootFromShape(
|
|
31555
|
+
const mainRoot = mainRootFromShape(path76.resolve(cwd));
|
|
30100
31556
|
if (mainRoot === null) return null;
|
|
30101
|
-
return { mainCwd: mainRoot, worktree: { branch:
|
|
31557
|
+
return { mainCwd: mainRoot, worktree: { branch: path76.basename(cwd), exists: false, reopenable: false } };
|
|
30102
31558
|
}
|
|
30103
31559
|
/**
|
|
30104
31560
|
* Se puede volver a abrir una rama quitada si la creó la app, su carpeta no
|
|
@@ -30128,15 +31584,15 @@ var WorktreeLinks = class {
|
|
|
30128
31584
|
};
|
|
30129
31585
|
|
|
30130
31586
|
// packages/server/src/index.ts
|
|
30131
|
-
var serverDir =
|
|
31587
|
+
var serverDir = path77.dirname(fileURLToPath2(import.meta.url));
|
|
30132
31588
|
var isPackaged = true;
|
|
30133
|
-
var webRoot = isPackaged ? serverDir :
|
|
30134
|
-
var webDist = isPackaged ?
|
|
30135
|
-
var repoRoot =
|
|
31589
|
+
var webRoot = isPackaged ? serverDir : path77.resolve(serverDir, "../../web");
|
|
31590
|
+
var webDist = isPackaged ? path77.join(serverDir, "web") : path77.join(webRoot, "dist");
|
|
31591
|
+
var repoRoot = path77.resolve(serverDir, "../../..");
|
|
30136
31592
|
var isProduction = isPackaged || process.argv.includes("--prod");
|
|
30137
31593
|
function resolveDefaultCwd() {
|
|
30138
31594
|
const override = process.env["AGENT_WORKBENCH_CWD"];
|
|
30139
|
-
if (override !== void 0 && override.length > 0) return
|
|
31595
|
+
if (override !== void 0 && override.length > 0) return path77.resolve(override);
|
|
30140
31596
|
if (isPackaged) return process.cwd();
|
|
30141
31597
|
return repoRoot;
|
|
30142
31598
|
}
|
|
@@ -30211,7 +31667,7 @@ async function mountDevUi(app, httpServer) {
|
|
|
30211
31667
|
}
|
|
30212
31668
|
var LOOKS_LIKE_FILE = /\.[a-zA-Z0-9]{1,8}$/;
|
|
30213
31669
|
function mountBuiltUi(app) {
|
|
30214
|
-
const indexHtml =
|
|
31670
|
+
const indexHtml = path77.join(webDist, "index.html");
|
|
30215
31671
|
if (!existsSync4(indexHtml)) {
|
|
30216
31672
|
throw new Error(
|
|
30217
31673
|
`There's no compiled interface in ${webDist}.
|
|
@@ -30299,11 +31755,11 @@ async function main() {
|
|
|
30299
31755
|
findSession: (agent, sessionId) => index.find(agent, sessionId),
|
|
30300
31756
|
// Hito 49: las conversaciones de una rama (para dejarle un pedido a su autor) y la respuesta del revisor.
|
|
30301
31757
|
sessionsUnder: (folder) => {
|
|
30302
|
-
const root =
|
|
31758
|
+
const root = path77.resolve(folder);
|
|
30303
31759
|
return index.getProjects().flatMap((project) => project.sessions).filter((session) => {
|
|
30304
31760
|
if (session.archived || session.cwd.length === 0) return false;
|
|
30305
|
-
const relative =
|
|
30306
|
-
return relative.length === 0 || !relative.startsWith("..") && !
|
|
31761
|
+
const relative = path77.relative(root, path77.resolve(session.cwd));
|
|
31762
|
+
return relative.length === 0 || !relative.startsWith("..") && !path77.isAbsolute(relative);
|
|
30307
31763
|
});
|
|
30308
31764
|
},
|
|
30309
31765
|
history: (agent) => agents.get(agent)?.adapter.history ?? null
|
|
@@ -30327,6 +31783,14 @@ async function main() {
|
|
|
30327
31783
|
await notes.load();
|
|
30328
31784
|
const drafts = new DraftStore();
|
|
30329
31785
|
await drafts.load();
|
|
31786
|
+
const scheduled = new ScheduledMessageStore();
|
|
31787
|
+
await scheduled.load();
|
|
31788
|
+
const instances = new RunningInstances();
|
|
31789
|
+
await instances.register();
|
|
31790
|
+
let markRestored = () => void 0;
|
|
31791
|
+
const restored = new Promise((resolve) => {
|
|
31792
|
+
markRestored = resolve;
|
|
31793
|
+
});
|
|
30330
31794
|
const conversations = new ConversationHub(registry, agents);
|
|
30331
31795
|
const repos = new RepoHub(registry);
|
|
30332
31796
|
const memory = new MemoryHub(registry);
|
|
@@ -30378,21 +31842,30 @@ async function main() {
|
|
|
30378
31842
|
defaultCwd,
|
|
30379
31843
|
vault,
|
|
30380
31844
|
remote,
|
|
30381
|
-
worktrees
|
|
31845
|
+
worktrees,
|
|
31846
|
+
scheduled,
|
|
31847
|
+
restored,
|
|
31848
|
+
instances
|
|
30382
31849
|
});
|
|
30383
31850
|
void pasteStore.purgeStale();
|
|
30384
31851
|
void index.start();
|
|
30385
31852
|
const stopWatching = watchSessions(index, conversations, agents);
|
|
30386
31853
|
conversations.on("historyGrew", (agent, filePath) => void index.refreshPath(agent, filePath));
|
|
30387
31854
|
if (process.env["AGENT_WORKBENCH_NO_RESTORE"] !== "1") {
|
|
30388
|
-
void store.load().then((state) => {
|
|
30389
|
-
|
|
31855
|
+
void store.load().then(async (state) => {
|
|
31856
|
+
const owners = workspaceDraftOwners(state);
|
|
31857
|
+
drafts.retainOnly(owners);
|
|
31858
|
+
await scheduled.retainOnly(owners).catch((error) => {
|
|
31859
|
+
console.warn("[scheduled] couldn't drop the messages of closed tabs:", error);
|
|
31860
|
+
});
|
|
30390
31861
|
if (state.tabs.length === 0 && state.foreignTabs.length === 0) return;
|
|
30391
31862
|
if (agents.anyAvailable()) {
|
|
30392
31863
|
console.log(`Restoring ${state.tabs.length} tab(s) from the previous run...`);
|
|
30393
31864
|
}
|
|
30394
31865
|
return registry.restore(state);
|
|
30395
|
-
});
|
|
31866
|
+
}).finally(markRestored);
|
|
31867
|
+
} else {
|
|
31868
|
+
markRestored();
|
|
30396
31869
|
}
|
|
30397
31870
|
const url = `http://${LOOPBACK_HOST}:${port}/?${TOKEN_QUERY_PARAM}=${token}`;
|
|
30398
31871
|
const remoteStatus = remote.status();
|
|
@@ -30423,6 +31896,9 @@ async function main() {
|
|
|
30423
31896
|
archived.flush(),
|
|
30424
31897
|
notes.flush(),
|
|
30425
31898
|
drafts.flush(),
|
|
31899
|
+
// Lo que esta instancia tenia reservado sin haber escrito nada lo puede tomar otra (hito 51).
|
|
31900
|
+
scheduled.flush().then(() => scheduled.releaseReserved()),
|
|
31901
|
+
instances.unregister(),
|
|
30426
31902
|
remote.flush(),
|
|
30427
31903
|
worktreeStore.flush(),
|
|
30428
31904
|
agentsDisposed
|