agent-workbench 0.4.0 → 0.5.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 +21 -18
- package/dist/server.js +446 -51
- package/dist/web/assets/de-D4f5KK7F.js +1 -0
- package/dist/web/assets/es-BPdfRlPL.js +1 -0
- package/dist/web/assets/fr-Cv2VZ3uf.js +1 -0
- package/dist/web/assets/{highlight-2xGvJy1W.js → highlight-DqX1MSlE.js} +1 -1
- package/dist/web/assets/index-B09L7LWU.css +32 -0
- package/dist/web/assets/index-CRZuxaEg.js +139 -0
- package/dist/web/assets/ja-X1nVuf-y.js +1 -0
- package/dist/web/assets/ko-c__ksx35.js +1 -0
- package/dist/web/assets/pt-BR-Bq9Rmy51.js +1 -0
- package/dist/web/assets/ru-4tlHIIyY.js +1 -0
- package/dist/web/assets/zh-CN-B_6-wR0g.js +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/de-fq_F8zRo.js +0 -1
- package/dist/web/assets/es-BCB5Npb3.js +0 -1
- package/dist/web/assets/fr-ClL0BO43.js +0 -1
- package/dist/web/assets/index-BoXw3kE_.css +0 -32
- package/dist/web/assets/index-Bu0LGhCK.js +0 -139
- package/dist/web/assets/ja-C7KI4Gtm.js +0 -1
- package/dist/web/assets/ko-C8der8AK.js +0 -1
- package/dist/web/assets/pt-BR-rXhfKNdD.js +0 -1
- package/dist/web/assets/ru-BjrRxMeM.js +0 -1
- package/dist/web/assets/zh-CN-DDY7nBK5.js +0 -1
package/dist/server.js
CHANGED
|
@@ -170,6 +170,7 @@ function noticeText(part) {
|
|
|
170
170
|
return part.detail.length > 0 ? `La CLI devolvi\xF3 un error: ${part.detail}` : "La CLI devolvi\xF3 un error";
|
|
171
171
|
}
|
|
172
172
|
}
|
|
173
|
+
var MAX_SUBAGENT_DESCRIPTION = 120;
|
|
173
174
|
var EMPTY_CONTEXT_USAGE = {
|
|
174
175
|
lastRequestTokens: 0,
|
|
175
176
|
lastOutputTokens: 0,
|
|
@@ -679,6 +680,38 @@ function cleanDeviceLabel(label) {
|
|
|
679
680
|
return label.replace(/\s+/g, " ").trim().slice(0, REMOTE_DEVICE_LABEL_MAX_CHARS);
|
|
680
681
|
}
|
|
681
682
|
|
|
683
|
+
// packages/shared/src/settings.ts
|
|
684
|
+
var CONSOLE_SHELL_IDS = [
|
|
685
|
+
"auto",
|
|
686
|
+
"pwsh",
|
|
687
|
+
"powershell",
|
|
688
|
+
"cmd",
|
|
689
|
+
"git-bash",
|
|
690
|
+
"login",
|
|
691
|
+
"bash",
|
|
692
|
+
"sh",
|
|
693
|
+
"zsh"
|
|
694
|
+
];
|
|
695
|
+
function asConsoleShellId(value) {
|
|
696
|
+
return asLiteral(value, CONSOLE_SHELL_IDS);
|
|
697
|
+
}
|
|
698
|
+
function asAgentIdList(value) {
|
|
699
|
+
if (!Array.isArray(value)) return null;
|
|
700
|
+
return [...new Set(value.filter((item) => typeof item === "string" && isAgentId(item)))];
|
|
701
|
+
}
|
|
702
|
+
function parseAppSettingsChange(record) {
|
|
703
|
+
const rawShell = record["consoleShell"];
|
|
704
|
+
const rawAgents = record["disabledAgents"];
|
|
705
|
+
const consoleShell = rawShell === void 0 ? void 0 : asConsoleShellId(rawShell);
|
|
706
|
+
const disabledAgents = rawAgents === void 0 ? void 0 : asAgentIdList(rawAgents);
|
|
707
|
+
if (consoleShell === null || disabledAgents === null) return null;
|
|
708
|
+
if (consoleShell === void 0 && disabledAgents === void 0) return null;
|
|
709
|
+
return {
|
|
710
|
+
...consoleShell !== void 0 ? { consoleShell } : {},
|
|
711
|
+
...disabledAgents !== void 0 ? { disabledAgents } : {}
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
|
|
682
715
|
// packages/shared/src/protocol.ts
|
|
683
716
|
var PROTOCOL_VERSION = 8;
|
|
684
717
|
var WS_PATH = "/ws";
|
|
@@ -738,6 +771,8 @@ function parseClientMessage(raw) {
|
|
|
738
771
|
if (files.length > 0) message.files = files;
|
|
739
772
|
}
|
|
740
773
|
if (record["send"] === false) message.send = false;
|
|
774
|
+
const requestId = asNonEmptyString(record["requestId"]);
|
|
775
|
+
if (requestId !== null) message.requestId = requestId;
|
|
741
776
|
return message;
|
|
742
777
|
}
|
|
743
778
|
case "agent.mode": {
|
|
@@ -1067,6 +1102,10 @@ function parseClientMessage(raw) {
|
|
|
1067
1102
|
const draft = parseComposerDraft(record["draft"]);
|
|
1068
1103
|
return terminalId === null || draft === null ? null : { type: "composer.draft", terminalId, draft };
|
|
1069
1104
|
}
|
|
1105
|
+
case "settings.update": {
|
|
1106
|
+
const change = parseAppSettingsChange(record);
|
|
1107
|
+
return change === null ? null : { type: "settings.update", ...change };
|
|
1108
|
+
}
|
|
1070
1109
|
default:
|
|
1071
1110
|
return null;
|
|
1072
1111
|
}
|
|
@@ -5069,6 +5108,135 @@ function toTurnDuration(record) {
|
|
|
5069
5108
|
return { eventId, durationMs };
|
|
5070
5109
|
}
|
|
5071
5110
|
|
|
5111
|
+
// packages/server/src/agents/claude-code/subagent-tracker.ts
|
|
5112
|
+
var SUBAGENT_TOOLS = /* @__PURE__ */ new Set(["Agent", "Task"]);
|
|
5113
|
+
var MAX_TRACKED = 50;
|
|
5114
|
+
var NOTIFICATION = /<task-notification>([\s\S]*?)<\/task-notification>/g;
|
|
5115
|
+
var TOOL_USE_ID = /<tool-use-id>\s*([^<\s]+)\s*<\/tool-use-id>/;
|
|
5116
|
+
function labelOf(value) {
|
|
5117
|
+
if (typeof value !== "string") return "";
|
|
5118
|
+
return value.replace(/\s+/g, " ").trim().slice(0, MAX_SUBAGENT_DESCRIPTION);
|
|
5119
|
+
}
|
|
5120
|
+
function notifiedToolUseIds(text) {
|
|
5121
|
+
if (!text.includes("<task-notification>")) return [];
|
|
5122
|
+
const ids = [];
|
|
5123
|
+
for (const match of text.matchAll(NOTIFICATION)) {
|
|
5124
|
+
const id = TOOL_USE_ID.exec(match[1] ?? "")?.[1];
|
|
5125
|
+
if (id !== void 0) ids.push(id);
|
|
5126
|
+
}
|
|
5127
|
+
return ids;
|
|
5128
|
+
}
|
|
5129
|
+
function contentText(content) {
|
|
5130
|
+
if (typeof content === "string") return content;
|
|
5131
|
+
if (!Array.isArray(content)) return "";
|
|
5132
|
+
let text = "";
|
|
5133
|
+
for (const block of content) {
|
|
5134
|
+
const record = recordOf(block);
|
|
5135
|
+
if (record !== null && record["type"] === "text" && typeof record["text"] === "string") {
|
|
5136
|
+
text += record["text"];
|
|
5137
|
+
}
|
|
5138
|
+
}
|
|
5139
|
+
return text;
|
|
5140
|
+
}
|
|
5141
|
+
function launchedInBackground(toolUseResult) {
|
|
5142
|
+
const result = recordOf(toolUseResult);
|
|
5143
|
+
return result !== null && (result["status"] === "async_launched" || result["isAsync"] === true);
|
|
5144
|
+
}
|
|
5145
|
+
var SubagentTracker = class {
|
|
5146
|
+
/** Los que no terminaron, en orden de lanzamiento (`Map` conserva el de insercion). */
|
|
5147
|
+
pending = /* @__PURE__ */ new Map();
|
|
5148
|
+
/** Una linea del JSONL, ya parseada. Nunca lanza: el esquema cambia. */
|
|
5149
|
+
observe(record) {
|
|
5150
|
+
if (record["isSidechain"] === true) return;
|
|
5151
|
+
switch (record["type"]) {
|
|
5152
|
+
case "assistant":
|
|
5153
|
+
this.observeLaunches(record);
|
|
5154
|
+
return;
|
|
5155
|
+
case "user":
|
|
5156
|
+
this.observeResults(record);
|
|
5157
|
+
this.finish(notifiedToolUseIds(contentText(recordOf(record["message"])?.["content"])));
|
|
5158
|
+
return;
|
|
5159
|
+
case "queue-operation":
|
|
5160
|
+
if (record["operation"] === "enqueue" && typeof record["content"] === "string") {
|
|
5161
|
+
this.finish(notifiedToolUseIds(record["content"]));
|
|
5162
|
+
}
|
|
5163
|
+
return;
|
|
5164
|
+
case "attachment": {
|
|
5165
|
+
const attachment = recordOf(record["attachment"]);
|
|
5166
|
+
if (attachment?.["type"] === "queued_command" && typeof attachment["prompt"] === "string") {
|
|
5167
|
+
this.finish(notifiedToolUseIds(attachment["prompt"]));
|
|
5168
|
+
}
|
|
5169
|
+
return;
|
|
5170
|
+
}
|
|
5171
|
+
default:
|
|
5172
|
+
return;
|
|
5173
|
+
}
|
|
5174
|
+
}
|
|
5175
|
+
/**
|
|
5176
|
+
* Los que siguen trabajando y los lanzo el proceso de `launchedAt` o uno
|
|
5177
|
+
* posterior. Uno sin hora no se puede atribuir a ningun proceso y no cuenta.
|
|
5178
|
+
*/
|
|
5179
|
+
running(launchedAt) {
|
|
5180
|
+
const result = [];
|
|
5181
|
+
for (const subagent of this.pending.values()) {
|
|
5182
|
+
if (subagent.startedAt > 0 && subagent.startedAt >= launchedAt) result.push({ ...subagent });
|
|
5183
|
+
}
|
|
5184
|
+
return result;
|
|
5185
|
+
}
|
|
5186
|
+
/** El archivo se reemplazo: lo anotado era de otra lectura. */
|
|
5187
|
+
reset() {
|
|
5188
|
+
this.pending.clear();
|
|
5189
|
+
}
|
|
5190
|
+
observeLaunches(record) {
|
|
5191
|
+
const content = recordOf(record["message"])?.["content"];
|
|
5192
|
+
if (!Array.isArray(content)) return;
|
|
5193
|
+
const startedAt = readTimestamp(record["timestamp"]);
|
|
5194
|
+
for (const block of content) {
|
|
5195
|
+
const call = recordOf(block);
|
|
5196
|
+
if (call === null || call["type"] !== "tool_use") continue;
|
|
5197
|
+
const toolUseId = call["id"];
|
|
5198
|
+
if (typeof toolUseId !== "string" || toolUseId.length === 0) continue;
|
|
5199
|
+
if (typeof call["name"] !== "string" || !SUBAGENT_TOOLS.has(call["name"])) continue;
|
|
5200
|
+
if (this.pending.has(toolUseId)) continue;
|
|
5201
|
+
const input = recordOf(call["input"]);
|
|
5202
|
+
this.pending.set(toolUseId, {
|
|
5203
|
+
toolUseId,
|
|
5204
|
+
description: labelOf(input?.["description"]),
|
|
5205
|
+
background: input?.["run_in_background"] === true,
|
|
5206
|
+
startedAt
|
|
5207
|
+
});
|
|
5208
|
+
if (this.pending.size > MAX_TRACKED) {
|
|
5209
|
+
const oldest = this.pending.keys().next();
|
|
5210
|
+
if (!oldest.done) this.pending.delete(oldest.value);
|
|
5211
|
+
}
|
|
5212
|
+
}
|
|
5213
|
+
}
|
|
5214
|
+
/**
|
|
5215
|
+
* El resultado de la llamada. En segundo plano dice que quedo andando, y
|
|
5216
|
+
* sigue; si no, termino —o no llego a lanzarse, que para el hilo es lo mismo—.
|
|
5217
|
+
*/
|
|
5218
|
+
observeResults(record) {
|
|
5219
|
+
const content = recordOf(record["message"])?.["content"];
|
|
5220
|
+
if (!Array.isArray(content)) return;
|
|
5221
|
+
for (const block of content) {
|
|
5222
|
+
const result = recordOf(block);
|
|
5223
|
+
if (result === null || result["type"] !== "tool_result") continue;
|
|
5224
|
+
const toolUseId = result["tool_use_id"];
|
|
5225
|
+
if (typeof toolUseId !== "string") continue;
|
|
5226
|
+
const subagent = this.pending.get(toolUseId);
|
|
5227
|
+
if (subagent === void 0) continue;
|
|
5228
|
+
if (result["is_error"] !== true && launchedInBackground(record["toolUseResult"])) {
|
|
5229
|
+
subagent.background = true;
|
|
5230
|
+
} else {
|
|
5231
|
+
this.pending.delete(toolUseId);
|
|
5232
|
+
}
|
|
5233
|
+
}
|
|
5234
|
+
}
|
|
5235
|
+
finish(toolUseIds) {
|
|
5236
|
+
for (const toolUseId of toolUseIds) this.pending.delete(toolUseId);
|
|
5237
|
+
}
|
|
5238
|
+
};
|
|
5239
|
+
|
|
5072
5240
|
// packages/server/src/agents/claude-code/conversation-follower.ts
|
|
5073
5241
|
var MAX_ATTACHMENT_LINKS = 100;
|
|
5074
5242
|
var MAX_PLAN_REFS = 50;
|
|
@@ -5155,6 +5323,11 @@ var ConversationFollower = class {
|
|
|
5155
5323
|
* ninguna—, y ahi vale con que se lanzo la pestana (`LAUNCH_PERMISSION_MODE`).
|
|
5156
5324
|
*/
|
|
5157
5325
|
permissionMode = null;
|
|
5326
|
+
/**
|
|
5327
|
+
* Los subagentes que no terminaron (§4.15). Mira lineas que no son tarjetas
|
|
5328
|
+
* —el aviso de fin es una `queue-operation`—, asi que va antes de todo filtro.
|
|
5329
|
+
*/
|
|
5330
|
+
subagents = new SubagentTracker();
|
|
5158
5331
|
/** Los topes de cada parte. Los de transporte salvo que se lea para la copia propia. */
|
|
5159
5332
|
limits;
|
|
5160
5333
|
/** La pestana cuyos documentos se reconocen. Ver el constructor. */
|
|
@@ -5207,6 +5380,10 @@ var ConversationFollower = class {
|
|
|
5207
5380
|
getUsage() {
|
|
5208
5381
|
return this.usage;
|
|
5209
5382
|
}
|
|
5383
|
+
/** Los subagentes del proceso de `launchedAt` que siguen trabajando (§4.15). */
|
|
5384
|
+
runningSubagents(launchedAt) {
|
|
5385
|
+
return this.subagents.running(launchedAt);
|
|
5386
|
+
}
|
|
5210
5387
|
/** Ultimos `limit` eventos, que es lo que se quiere ver al abrir el panel. */
|
|
5211
5388
|
getTail(limit) {
|
|
5212
5389
|
return this.jsonl.getTail(limit);
|
|
@@ -5252,6 +5429,7 @@ var ConversationFollower = class {
|
|
|
5252
5429
|
this.permissionMode = null;
|
|
5253
5430
|
this.attachmentRoots.clear();
|
|
5254
5431
|
this.planFiles = [];
|
|
5432
|
+
this.subagents.reset();
|
|
5255
5433
|
}
|
|
5256
5434
|
/** true si la linea es la invocacion de `/model` que la CLI guarda. */
|
|
5257
5435
|
mentionsModelCommand(record) {
|
|
@@ -5268,6 +5446,7 @@ var ConversationFollower = class {
|
|
|
5268
5446
|
* los que ya guardo, para pegarle a uno anterior la duracion o una imagen.
|
|
5269
5447
|
*/
|
|
5270
5448
|
consume(record, lineNumber, events) {
|
|
5449
|
+
this.subagents.observe(record);
|
|
5271
5450
|
if (record["type"] === "cost-state") {
|
|
5272
5451
|
this.sessionVariants.observeCostState(record, Date.now());
|
|
5273
5452
|
this.recomputeModel();
|
|
@@ -5647,6 +5826,10 @@ var ClaudeCodeSessionFollower = class {
|
|
|
5647
5826
|
hasOpenToolCall() {
|
|
5648
5827
|
return false;
|
|
5649
5828
|
}
|
|
5829
|
+
/** Los subagentes del proceso de `launchedAt` que siguen trabajando (§4.15). */
|
|
5830
|
+
runningSubagents(launchedAt) {
|
|
5831
|
+
return this.inner.runningSubagents(launchedAt);
|
|
5832
|
+
}
|
|
5650
5833
|
};
|
|
5651
5834
|
|
|
5652
5835
|
// packages/server/src/agents/claude-code/session-scan.ts
|
|
@@ -9967,28 +10150,48 @@ var AgentRegistry = class {
|
|
|
9967
10150
|
agents = /* @__PURE__ */ new Map();
|
|
9968
10151
|
/** Las suscripciones a cambios de configuracion que siguen abiertas, para soltarlas al apagar. */
|
|
9969
10152
|
changeSubscriptions = /* @__PURE__ */ new Set();
|
|
10153
|
+
/**
|
|
10154
|
+
* Las que el usuario apago en Ajustes (Hito 41, §6.32), tal como estaban al
|
|
10155
|
+
* arrancar. Una apagada es, para todo el resto, una CLI que no esta: no se
|
|
10156
|
+
* busca —no se corre su `--version`—, no se lanza, y su historial no se lee
|
|
10157
|
+
* (`historySources`). Cambiarlo vale al reiniciar.
|
|
10158
|
+
*/
|
|
10159
|
+
disabled;
|
|
9970
10160
|
/** El orden del array es el orden de preferencia para `defaultAgent`. */
|
|
9971
|
-
constructor(adapters) {
|
|
10161
|
+
constructor(adapters, disabled = []) {
|
|
9972
10162
|
for (const adapter of adapters) {
|
|
9973
10163
|
if (this.agents.has(adapter.id)) {
|
|
9974
10164
|
throw new Error(`Adapter registered twice: ${adapter.id}`);
|
|
9975
10165
|
}
|
|
9976
10166
|
this.agents.set(adapter.id, { adapter, location: null });
|
|
9977
10167
|
}
|
|
10168
|
+
this.disabled = new Set([...disabled].filter((id) => this.agents.has(id)));
|
|
9978
10169
|
}
|
|
9979
10170
|
/**
|
|
9980
10171
|
* Localiza todos a la vez. Se llama una vez al arrancar.
|
|
9981
10172
|
*
|
|
9982
10173
|
* En paralelo porque cada uno puede correr `--version`, y eso son segundos
|
|
9983
|
-
* que el arranque no tiene por que sumar.
|
|
10174
|
+
* que el arranque no tiene por que sumar. Una apagada no se busca.
|
|
9984
10175
|
*/
|
|
9985
10176
|
async locateAll() {
|
|
9986
10177
|
await Promise.all(
|
|
9987
10178
|
[...this.agents.values()].map(async (entry) => {
|
|
9988
|
-
entry.location = await entry.adapter.locate();
|
|
10179
|
+
entry.location = this.disabled.has(entry.adapter.id) ? null : await entry.adapter.locate();
|
|
9989
10180
|
})
|
|
9990
10181
|
);
|
|
9991
10182
|
}
|
|
10183
|
+
/** Las que este arranque dejo apagadas. */
|
|
10184
|
+
disabledIds() {
|
|
10185
|
+
return [...this.agents.keys()].filter((id) => this.disabled.has(id));
|
|
10186
|
+
}
|
|
10187
|
+
/**
|
|
10188
|
+
* Las CLIs cuyo historial se lee: todas menos las apagadas. Instalada o no da
|
|
10189
|
+
* igual —el historial de una ausente se lee igual, apagado en la barra—; una
|
|
10190
|
+
* apagada es lo que el usuario pidio no ver.
|
|
10191
|
+
*/
|
|
10192
|
+
historySources() {
|
|
10193
|
+
return this.all().filter((entry) => !this.disabled.has(entry.adapter.id));
|
|
10194
|
+
}
|
|
9992
10195
|
/**
|
|
9993
10196
|
* Lo que cada CLI encontrada prepara en las carpetas de la app
|
|
9994
10197
|
* (`AgentAdapter.prepare`). Se llama una vez al arrancar, despues de
|
|
@@ -10140,13 +10343,13 @@ function resolveAgentForOpen(input) {
|
|
|
10140
10343
|
}
|
|
10141
10344
|
return fromTabs ?? input.defaultAgent;
|
|
10142
10345
|
}
|
|
10143
|
-
function createAgentRegistry() {
|
|
10346
|
+
function createAgentRegistry(disabled = []) {
|
|
10144
10347
|
const registry = new AgentRegistry([
|
|
10145
10348
|
createClaudeCodeAdapter(),
|
|
10146
10349
|
createCodexAdapter(),
|
|
10147
10350
|
createOpenCodeAdapter({ serveEnv: () => registry.composedEnvironment(process.env) }),
|
|
10148
10351
|
createAntigravityAdapter()
|
|
10149
|
-
]);
|
|
10352
|
+
], disabled);
|
|
10150
10353
|
return registry;
|
|
10151
10354
|
}
|
|
10152
10355
|
|
|
@@ -10168,6 +10371,9 @@ var INITIAL_EVENT_LIMIT = 300;
|
|
|
10168
10371
|
var MAX_PAGE_LIMIT = 500;
|
|
10169
10372
|
var PENDING_QUESTION_LOOKBACK = 50;
|
|
10170
10373
|
var POLL_DEBOUNCE_MS = 150;
|
|
10374
|
+
function sameSubagents(a, b) {
|
|
10375
|
+
return a.length === b.length && a.every((item, index) => item.toolUseId === b[index]?.toolUseId && item.background === b[index]?.background);
|
|
10376
|
+
}
|
|
10171
10377
|
var ConversationHub = class extends EventEmitter {
|
|
10172
10378
|
constructor(registry, agents) {
|
|
10173
10379
|
super();
|
|
@@ -10175,7 +10381,10 @@ var ConversationHub = class extends EventEmitter {
|
|
|
10175
10381
|
this.agents = agents;
|
|
10176
10382
|
registry.on("session", (terminalId) => void this.rebind(terminalId));
|
|
10177
10383
|
registry.on("changed", () => {
|
|
10178
|
-
for (const [terminalId, entry] of this.entries)
|
|
10384
|
+
for (const [terminalId, entry] of this.entries) {
|
|
10385
|
+
this.refreshOpenToolCall(terminalId, entry);
|
|
10386
|
+
this.refreshSubagents(terminalId, entry);
|
|
10387
|
+
}
|
|
10179
10388
|
});
|
|
10180
10389
|
}
|
|
10181
10390
|
registry;
|
|
@@ -10199,6 +10408,7 @@ var ConversationHub = class extends EventEmitter {
|
|
|
10199
10408
|
usage: { ...EMPTY_CONTEXT_USAGE },
|
|
10200
10409
|
waitingFor: null,
|
|
10201
10410
|
openToolCall: false,
|
|
10411
|
+
subagents: [],
|
|
10202
10412
|
plans: []
|
|
10203
10413
|
};
|
|
10204
10414
|
}
|
|
@@ -10235,6 +10445,7 @@ var ConversationHub = class extends EventEmitter {
|
|
|
10235
10445
|
permissionMode: entry.observedMode ?? entry.assumedMode,
|
|
10236
10446
|
waitingFor: entry.waitingFor,
|
|
10237
10447
|
openToolCall: entry.openToolCall,
|
|
10448
|
+
subagents: entry.subagents,
|
|
10238
10449
|
plans: plans === null ? [] : await plans.describe(
|
|
10239
10450
|
{ cwd: entry.cwd, sessionId: entry.sessionId },
|
|
10240
10451
|
entry.follower.getPlanFiles()
|
|
@@ -10284,6 +10495,7 @@ var ConversationHub = class extends EventEmitter {
|
|
|
10284
10495
|
waitingFor: null,
|
|
10285
10496
|
statusActivity: null,
|
|
10286
10497
|
openToolCall: false,
|
|
10498
|
+
subagents: [],
|
|
10287
10499
|
primed: false,
|
|
10288
10500
|
stopWatchingStatus: null,
|
|
10289
10501
|
followTicker: null
|
|
@@ -10326,6 +10538,23 @@ var ConversationHub = class extends EventEmitter {
|
|
|
10326
10538
|
entry.openToolCall = open8;
|
|
10327
10539
|
this.emit("toolCall", terminalId, open8);
|
|
10328
10540
|
}
|
|
10541
|
+
/**
|
|
10542
|
+
* Recalcula los subagentes que siguen trabajando y avisa si cambiaron
|
|
10543
|
+
* (§4.15). Solo los del proceso vivo: sin pty no queda ninguno, y los de un
|
|
10544
|
+
* proceso anterior murieron con el sin escribir el aviso de fin. Una CLI cuyo
|
|
10545
|
+
* seguidor no los declara no tiene nunca.
|
|
10546
|
+
*/
|
|
10547
|
+
refreshSubagents(terminalId, entry) {
|
|
10548
|
+
const launchedAt = this.registry.launchedAtOf(terminalId);
|
|
10549
|
+
const next = launchedAt === null || entry.follower.runningSubagents === void 0 ? [] : [...entry.follower.runningSubagents(launchedAt)];
|
|
10550
|
+
if (sameSubagents(next, entry.subagents)) return;
|
|
10551
|
+
entry.subagents = next;
|
|
10552
|
+
this.emit("subagents", terminalId, next);
|
|
10553
|
+
}
|
|
10554
|
+
/** Los subagentes que siguen trabajando, lo ultimo avisado. [] si nadie sigue la pestana. */
|
|
10555
|
+
getSubagents(terminalId) {
|
|
10556
|
+
return this.entries.get(terminalId)?.subagents ?? [];
|
|
10557
|
+
}
|
|
10329
10558
|
/**
|
|
10330
10559
|
* La pestana cambio de sesion: se suelta el seguidor viejo y se sigue la
|
|
10331
10560
|
* nueva, sin perder a quien estaba mirando.
|
|
@@ -10506,6 +10735,7 @@ var ConversationHub = class extends EventEmitter {
|
|
|
10506
10735
|
permissionMode: this.resetMode(entry),
|
|
10507
10736
|
waitingFor: entry.waitingFor,
|
|
10508
10737
|
openToolCall: entry.openToolCall,
|
|
10738
|
+
subagents: entry.subagents,
|
|
10509
10739
|
// Este snapshot es sincronico y describir un plan pide el disco. Quien
|
|
10510
10740
|
// necesita los planes usa `subscribe`, que si puede esperar.
|
|
10511
10741
|
plans: []
|
|
@@ -10662,6 +10892,7 @@ var ConversationHub = class extends EventEmitter {
|
|
|
10662
10892
|
return;
|
|
10663
10893
|
}
|
|
10664
10894
|
this.refreshOpenToolCall(terminalId, entry);
|
|
10895
|
+
this.refreshSubagents(terminalId, entry);
|
|
10665
10896
|
if (options.silent) return;
|
|
10666
10897
|
const observed = entry.follower.getPermissionMode();
|
|
10667
10898
|
if (observed !== null) {
|
|
@@ -11030,6 +11261,7 @@ var OPENS_ON_HOST = /* @__PURE__ */ new Set([
|
|
|
11030
11261
|
]);
|
|
11031
11262
|
function remoteRefusal(type, remoteClient) {
|
|
11032
11263
|
if (!remoteClient) return null;
|
|
11264
|
+
if (type === "settings.update") return serverText("settingsHostOnly");
|
|
11033
11265
|
if (HOST_ONLY.has(type)) return serverText("remoteHostOnly");
|
|
11034
11266
|
if (OPENS_ON_HOST.has(type)) return serverText("remoteOpensOnHost");
|
|
11035
11267
|
return null;
|
|
@@ -14288,7 +14520,10 @@ function vaultOnlySessions(natives, vault, listState, ready) {
|
|
|
14288
14520
|
for (const copy of vault) {
|
|
14289
14521
|
const pair = vaultPairKey(copy.agent, copy.sessionId);
|
|
14290
14522
|
if (native.has(pair) || seen.has(pair)) continue;
|
|
14291
|
-
if (!isImportedAgentId(copy.agent)
|
|
14523
|
+
if (!isImportedAgentId(copy.agent)) {
|
|
14524
|
+
const state = listState.get(copy.agent) ?? "missing";
|
|
14525
|
+
if (state === "unreadable" || state === "off") continue;
|
|
14526
|
+
}
|
|
14292
14527
|
seen.add(pair);
|
|
14293
14528
|
const hasCwd = copy.cwd.length > 0;
|
|
14294
14529
|
result.push({
|
|
@@ -14670,7 +14905,7 @@ var SessionIndex = class extends EventEmitter4 {
|
|
|
14670
14905
|
}
|
|
14671
14906
|
this.scanning = true;
|
|
14672
14907
|
try {
|
|
14673
|
-
const sources = this.agents.all().map(({ adapter }) => ({
|
|
14908
|
+
const sources = (this.agents.historySources?.() ?? this.agents.all()).map(({ adapter }) => ({
|
|
14674
14909
|
agent: adapter.id,
|
|
14675
14910
|
history: adapter.history
|
|
14676
14911
|
}));
|
|
@@ -14690,6 +14925,9 @@ var SessionIndex = class extends EventEmitter4 {
|
|
|
14690
14925
|
const outcome = outcomes[position] ?? { items: null, threw: true };
|
|
14691
14926
|
states.set(source.agent, await nativeListState(source.history, outcome));
|
|
14692
14927
|
}
|
|
14928
|
+
for (const { adapter } of this.agents.all()) {
|
|
14929
|
+
if (!states.has(adapter.id)) states.set(adapter.id, "off");
|
|
14930
|
+
}
|
|
14693
14931
|
this.listState = states;
|
|
14694
14932
|
if (lists.every((items) => items === null)) {
|
|
14695
14933
|
this.sessions.clear();
|
|
@@ -14875,7 +15113,9 @@ function defaultAppSettings() {
|
|
|
14875
15113
|
return {
|
|
14876
15114
|
version: SETTINGS_VERSION,
|
|
14877
15115
|
vault: { enabled: false, dir: null, toolResultMaxChars: VAULT_TOOL_RESULT_DEFAULT_CHARS },
|
|
14878
|
-
remote: { enabled: false, port: REMOTE_DEFAULT_PORT }
|
|
15116
|
+
remote: { enabled: false, port: REMOTE_DEFAULT_PORT },
|
|
15117
|
+
console: { shell: "auto" },
|
|
15118
|
+
agents: { disabled: [] }
|
|
14879
15119
|
};
|
|
14880
15120
|
}
|
|
14881
15121
|
function isAbsoluteDir(dir, platform) {
|
|
@@ -14891,6 +15131,12 @@ function parseAppSettings(value, platform, warn = () => void 0) {
|
|
|
14891
15131
|
const record = asRecord(value);
|
|
14892
15132
|
if (record === null || record["version"] !== SETTINGS_VERSION) return null;
|
|
14893
15133
|
const settings2 = defaultAppSettings();
|
|
15134
|
+
const consoleRecord = asRecord(record["console"]);
|
|
15135
|
+
const shell = consoleRecord === null ? null : asLiteral(consoleRecord["shell"], CONSOLE_SHELL_IDS);
|
|
15136
|
+
if (shell !== null) settings2.console.shell = shell;
|
|
15137
|
+
const agentsRecord = asRecord(record["agents"]);
|
|
15138
|
+
const disabled = agentsRecord === null ? null : asAgentIdList(agentsRecord["disabled"]);
|
|
15139
|
+
if (disabled !== null) settings2.agents.disabled = disabled;
|
|
14894
15140
|
const remote = asRecord(record["remote"]);
|
|
14895
15141
|
if (remote !== null) {
|
|
14896
15142
|
settings2.remote.enabled = asBoolean(remote["enabled"]) ?? false;
|
|
@@ -14903,7 +15149,7 @@ function parseAppSettings(value, platform, warn = () => void 0) {
|
|
|
14903
15149
|
if (typeof dir === "string" && isAbsoluteDir(dir, platform)) {
|
|
14904
15150
|
settings2.vault.dir = dir;
|
|
14905
15151
|
} else if (dir !== null && dir !== void 0) {
|
|
14906
|
-
warn(`[settings] the
|
|
15152
|
+
warn(`[settings] the backup folder ${JSON.stringify(dir)} isn't an absolute path: using the default one`);
|
|
14907
15153
|
}
|
|
14908
15154
|
settings2.vault.toolResultMaxChars = clampToolResultMaxChars(vault["toolResultMaxChars"]);
|
|
14909
15155
|
return settings2;
|
|
@@ -14912,7 +15158,9 @@ function frozen(settings2) {
|
|
|
14912
15158
|
return Object.freeze({
|
|
14913
15159
|
...settings2,
|
|
14914
15160
|
vault: Object.freeze({ ...settings2.vault }),
|
|
14915
|
-
remote: Object.freeze({ ...settings2.remote })
|
|
15161
|
+
remote: Object.freeze({ ...settings2.remote }),
|
|
15162
|
+
console: Object.freeze({ ...settings2.console }),
|
|
15163
|
+
agents: Object.freeze({ disabled: Object.freeze([...settings2.agents.disabled]) })
|
|
14916
15164
|
});
|
|
14917
15165
|
}
|
|
14918
15166
|
var SettingsStore = class {
|
|
@@ -14992,19 +15240,23 @@ var SettingsStore = class {
|
|
|
14992
15240
|
const next = {
|
|
14993
15241
|
version: SETTINGS_VERSION,
|
|
14994
15242
|
vault: { ...this.current.vault },
|
|
14995
|
-
remote: { ...this.current.remote }
|
|
15243
|
+
remote: { ...this.current.remote },
|
|
15244
|
+
console: { ...this.current.console },
|
|
15245
|
+
agents: { disabled: [...this.current.agents.disabled] }
|
|
14996
15246
|
};
|
|
14997
15247
|
const vault = patch.vault ?? {};
|
|
14998
15248
|
if (vault.enabled !== void 0) next.vault.enabled = vault.enabled;
|
|
14999
15249
|
if (vault.dir !== void 0) {
|
|
15000
15250
|
if (vault.dir !== null && !isAbsoluteDir(vault.dir, this.platform)) {
|
|
15001
|
-
throw new Error(`The
|
|
15251
|
+
throw new Error(`The backup folder has to be an absolute path: ${JSON.stringify(vault.dir)}`);
|
|
15002
15252
|
}
|
|
15003
15253
|
next.vault.dir = vault.dir;
|
|
15004
15254
|
}
|
|
15005
15255
|
if (vault.toolResultMaxChars !== void 0) {
|
|
15006
15256
|
next.vault.toolResultMaxChars = clampToolResultMaxChars(vault.toolResultMaxChars);
|
|
15007
15257
|
}
|
|
15258
|
+
if (patch.console?.shell !== void 0) next.console.shell = patch.console.shell;
|
|
15259
|
+
if (patch.agents?.disabled !== void 0) next.agents.disabled = [...new Set(patch.agents.disabled)];
|
|
15008
15260
|
const remote = patch.remote ?? {};
|
|
15009
15261
|
if (remote.enabled !== void 0) next.remote.enabled = remote.enabled;
|
|
15010
15262
|
if (remote.port !== void 0) {
|
|
@@ -15564,39 +15816,131 @@ async function isExecutable(candidate) {
|
|
|
15564
15816
|
return false;
|
|
15565
15817
|
}
|
|
15566
15818
|
}
|
|
15567
|
-
|
|
15568
|
-
|
|
15819
|
+
var SYSTEM_PROBE = {
|
|
15820
|
+
platform: process.platform,
|
|
15821
|
+
env: process.env,
|
|
15822
|
+
findInPath,
|
|
15823
|
+
isExecutable
|
|
15824
|
+
};
|
|
15825
|
+
function windowsPowerShellPath(env) {
|
|
15826
|
+
const systemRoot = env["SystemRoot"] ?? env["windir"];
|
|
15569
15827
|
if (systemRoot === void 0 || systemRoot.length === 0) return null;
|
|
15570
|
-
return path40.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
15828
|
+
return path40.win32.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
15571
15829
|
}
|
|
15572
|
-
|
|
15573
|
-
const
|
|
15574
|
-
if (
|
|
15575
|
-
|
|
15576
|
-
|
|
15577
|
-
|
|
15578
|
-
|
|
15579
|
-
|
|
15830
|
+
function gitBashCandidates(gitPath, env) {
|
|
15831
|
+
const candidates = [];
|
|
15832
|
+
if (gitPath !== null) {
|
|
15833
|
+
let dir = path40.win32.dirname(gitPath);
|
|
15834
|
+
for (let level = 0; level < 3; level++) {
|
|
15835
|
+
candidates.push(path40.win32.join(dir, "bin", "bash.exe"));
|
|
15836
|
+
const parent = path40.win32.dirname(dir);
|
|
15837
|
+
if (parent === dir) break;
|
|
15838
|
+
dir = parent;
|
|
15839
|
+
}
|
|
15580
15840
|
}
|
|
15581
|
-
const
|
|
15582
|
-
|
|
15583
|
-
return { file: comSpec, args: [], name: "cmd" };
|
|
15841
|
+
for (const base of [env["ProgramFiles"], env["LOCALAPPDATA"] === void 0 ? void 0 : path40.win32.join(env["LOCALAPPDATA"], "Programs")]) {
|
|
15842
|
+
if (base !== void 0 && base.length > 0) candidates.push(path40.win32.join(base, "Git", "bin", "bash.exe"));
|
|
15584
15843
|
}
|
|
15585
|
-
|
|
15844
|
+
const systemRoot = (env["SystemRoot"] ?? env["windir"] ?? "").toLowerCase();
|
|
15845
|
+
return [...new Set(candidates)].filter((candidate) => systemRoot.length === 0 || !candidate.toLowerCase().startsWith(systemRoot));
|
|
15586
15846
|
}
|
|
15587
|
-
async function
|
|
15588
|
-
const
|
|
15589
|
-
|
|
15590
|
-
|
|
15847
|
+
async function locateWindowsShells(probe) {
|
|
15848
|
+
const found = [];
|
|
15849
|
+
const pwsh = await probe.findInPath("pwsh");
|
|
15850
|
+
if (pwsh !== null) found.push({ id: "pwsh", label: "PowerShell 7", location: { file: pwsh, args: ["-NoLogo"], name: "PowerShell" } });
|
|
15851
|
+
const fromPath = await probe.findInPath("powershell");
|
|
15852
|
+
const systemCopy = windowsPowerShellPath(probe.env);
|
|
15853
|
+
const windowsPowerShell = fromPath ?? (systemCopy !== null && await probe.isExecutable(systemCopy) ? systemCopy : null);
|
|
15854
|
+
if (windowsPowerShell !== null) {
|
|
15855
|
+
found.push({ id: "powershell", label: "Windows PowerShell", location: { file: windowsPowerShell, args: ["-NoLogo"], name: "PowerShell" } });
|
|
15856
|
+
}
|
|
15857
|
+
const comSpec = probe.env["ComSpec"];
|
|
15858
|
+
if (comSpec !== void 0 && comSpec.length > 0 && await probe.isExecutable(comSpec)) {
|
|
15859
|
+
found.push({ id: "cmd", label: "cmd", location: { file: comSpec, args: [], name: "cmd" } });
|
|
15860
|
+
}
|
|
15861
|
+
for (const candidate of gitBashCandidates(await probe.findInPath("git"), probe.env)) {
|
|
15862
|
+
if (await probe.isExecutable(candidate)) {
|
|
15863
|
+
found.push({ id: "git-bash", label: "Git Bash", location: { file: candidate, args: ["--login", "-i"], name: "Git Bash" } });
|
|
15864
|
+
break;
|
|
15865
|
+
}
|
|
15591
15866
|
}
|
|
15592
|
-
|
|
15593
|
-
|
|
15594
|
-
|
|
15867
|
+
return found;
|
|
15868
|
+
}
|
|
15869
|
+
async function locatePosixShells(probe) {
|
|
15870
|
+
const found = [];
|
|
15871
|
+
const preferred = probe.env["SHELL"];
|
|
15872
|
+
if (preferred !== void 0 && preferred.length > 0 && await probe.isExecutable(preferred)) {
|
|
15873
|
+
const name = path40.posix.basename(preferred);
|
|
15874
|
+
found.push({ id: "login", label: name, location: { file: preferred, args: [], name } });
|
|
15875
|
+
}
|
|
15876
|
+
for (const [id, file] of [["bash", "/bin/bash"], ["sh", "/bin/sh"], ["zsh", "/bin/zsh"]]) {
|
|
15877
|
+
if (found.some((shell) => shell.location.file === file)) continue;
|
|
15878
|
+
if (await probe.isExecutable(file)) found.push({ id, label: id, location: { file, args: [], name: id } });
|
|
15879
|
+
}
|
|
15880
|
+
return found;
|
|
15595
15881
|
}
|
|
15596
|
-
async function
|
|
15597
|
-
return
|
|
15882
|
+
async function locateShells(probe = SYSTEM_PROBE) {
|
|
15883
|
+
return probe.platform === "win32" ? locateWindowsShells(probe) : locatePosixShells(probe);
|
|
15884
|
+
}
|
|
15885
|
+
var NEVER_AUTO = /* @__PURE__ */ new Set(["git-bash", "zsh"]);
|
|
15886
|
+
function pickShell(candidates, choice) {
|
|
15887
|
+
if (choice !== "auto") {
|
|
15888
|
+
const chosen = candidates.find((candidate) => candidate.id === choice);
|
|
15889
|
+
if (chosen !== void 0) return chosen.location;
|
|
15890
|
+
}
|
|
15891
|
+
return candidates.find((candidate) => !NEVER_AUTO.has(candidate.id))?.location ?? null;
|
|
15598
15892
|
}
|
|
15599
15893
|
|
|
15894
|
+
// packages/server/src/app-settings-service.ts
|
|
15895
|
+
import { EventEmitter as EventEmitter5 } from "node:events";
|
|
15896
|
+
var AppSettingsChangeError = class extends Error {
|
|
15897
|
+
constructor(reason2) {
|
|
15898
|
+
super(reason2);
|
|
15899
|
+
this.reason = reason2;
|
|
15900
|
+
}
|
|
15901
|
+
reason;
|
|
15902
|
+
};
|
|
15903
|
+
var AppSettingsService = class extends EventEmitter5 {
|
|
15904
|
+
constructor(options) {
|
|
15905
|
+
super();
|
|
15906
|
+
this.options = options;
|
|
15907
|
+
this.disabledAtStart = options.agents.disabledIds();
|
|
15908
|
+
}
|
|
15909
|
+
options;
|
|
15910
|
+
disabledAtStart;
|
|
15911
|
+
/** La consola que abre una nueva con lo que dice `settings.json`. */
|
|
15912
|
+
currentShell() {
|
|
15913
|
+
return pickShell(this.options.shells, this.options.settings.get().console.shell);
|
|
15914
|
+
}
|
|
15915
|
+
status() {
|
|
15916
|
+
const settings2 = this.options.settings.get();
|
|
15917
|
+
return {
|
|
15918
|
+
consoleShell: settings2.console.shell,
|
|
15919
|
+
consoleShells: this.options.shells.map(({ id, label }) => ({ id, label })),
|
|
15920
|
+
shellName: this.currentShell()?.name ?? null,
|
|
15921
|
+
disabledAgents: [...settings2.agents.disabled],
|
|
15922
|
+
disabledAtStart: [...this.disabledAtStart]
|
|
15923
|
+
};
|
|
15924
|
+
}
|
|
15925
|
+
/** Guarda el cambio, avisa al registro si cambio la consola, y emite `status`. */
|
|
15926
|
+
async update(change) {
|
|
15927
|
+
if (change.consoleShell !== void 0 && !this.offered(change.consoleShell)) {
|
|
15928
|
+
throw new AppSettingsChangeError("shell-not-installed");
|
|
15929
|
+
}
|
|
15930
|
+
const before = this.currentShell();
|
|
15931
|
+
await this.options.settings.update({
|
|
15932
|
+
...change.consoleShell !== void 0 ? { console: { shell: change.consoleShell } } : {},
|
|
15933
|
+
...change.disabledAgents !== void 0 ? { agents: { disabled: change.disabledAgents } } : {}
|
|
15934
|
+
});
|
|
15935
|
+
const after = this.currentShell();
|
|
15936
|
+
if (after !== before) this.options.onShellChange(after);
|
|
15937
|
+
this.emit("status", this.status());
|
|
15938
|
+
}
|
|
15939
|
+
offered(choice) {
|
|
15940
|
+
return choice === "auto" || this.options.shells.some((shell) => shell.id === choice);
|
|
15941
|
+
}
|
|
15942
|
+
};
|
|
15943
|
+
|
|
15600
15944
|
// packages/server/src/session-watcher.ts
|
|
15601
15945
|
import { stat as stat21 } from "node:fs/promises";
|
|
15602
15946
|
import chokidar3 from "chokidar";
|
|
@@ -15674,7 +16018,7 @@ function watchSessions(index, hub, agents, options = {}) {
|
|
|
15674
16018
|
probe.unref();
|
|
15675
16019
|
probes.add(probe);
|
|
15676
16020
|
};
|
|
15677
|
-
for (const { adapter } of agents.all()) {
|
|
16021
|
+
for (const { adapter } of agents.historySources?.() ?? agents.all()) {
|
|
15678
16022
|
for (const root of adapter.history.roots()) {
|
|
15679
16023
|
if (root.watch !== void 0) {
|
|
15680
16024
|
subscribeRoot(adapter.id, root, root.watch);
|
|
@@ -15706,7 +16050,7 @@ function watchSessions(index, hub, agents, options = {}) {
|
|
|
15706
16050
|
|
|
15707
16051
|
// packages/server/src/terminal-registry.ts
|
|
15708
16052
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
15709
|
-
import { EventEmitter as
|
|
16053
|
+
import { EventEmitter as EventEmitter6 } from "node:events";
|
|
15710
16054
|
import { stat as stat22 } from "node:fs/promises";
|
|
15711
16055
|
|
|
15712
16056
|
// packages/server/src/launch-hook-slot.ts
|
|
@@ -16139,7 +16483,7 @@ var MAX_TERMINALS = 24;
|
|
|
16139
16483
|
var RESTART_EXIT_TIMEOUT_MS = 1e4;
|
|
16140
16484
|
var DEFAULT_COLS = 80;
|
|
16141
16485
|
var DEFAULT_ROWS = 24;
|
|
16142
|
-
var TerminalRegistry = class extends
|
|
16486
|
+
var TerminalRegistry = class extends EventEmitter6 {
|
|
16143
16487
|
constructor(agents, shell, store, sessionAgentOf = () => null) {
|
|
16144
16488
|
super();
|
|
16145
16489
|
this.agents = agents;
|
|
@@ -16163,6 +16507,10 @@ var TerminalRegistry = class extends EventEmitter5 {
|
|
|
16163
16507
|
unavailableTabs = [];
|
|
16164
16508
|
/** Pestanas guardadas de una CLI que esta build no conoce. Idem, crudas. */
|
|
16165
16509
|
foreignTabs = [];
|
|
16510
|
+
/** Otra consola para las que se abran desde ahora (Ajustes, Hito 41). Las abiertas no cambian. */
|
|
16511
|
+
setShell(shell) {
|
|
16512
|
+
this.shell = shell;
|
|
16513
|
+
}
|
|
16166
16514
|
list() {
|
|
16167
16515
|
return this.order.map((id) => this.terminals.get(id)?.descriptor).filter((descriptor) => descriptor !== void 0);
|
|
16168
16516
|
}
|
|
@@ -16252,7 +16600,9 @@ var TerminalRegistry = class extends EventEmitter5 {
|
|
|
16252
16600
|
cwd: options.cwd,
|
|
16253
16601
|
// Provisional: `spawn` lo confirma con lo que diga el adaptador.
|
|
16254
16602
|
sessionId: kind === "agent" ? options.resumeSessionId ?? randomUUID5() : "",
|
|
16255
|
-
|
|
16603
|
+
// Una consola lleva el nombre de la suya: desde el Hito 41 pueden convivir
|
|
16604
|
+
// PowerShell y Git Bash, y el nombre de la consola nueva ya no es el de todas.
|
|
16605
|
+
label: options.label ?? (kind === "shell" ? this.shell?.name ?? "" : ""),
|
|
16256
16606
|
resumed,
|
|
16257
16607
|
createdAt: Date.now(),
|
|
16258
16608
|
// Lo pone `spawn`. Una entrada que no llega a tener proceso no queda en
|
|
@@ -19211,7 +19561,7 @@ function attachTerminalSocket(options) {
|
|
|
19211
19561
|
httpServer,
|
|
19212
19562
|
port,
|
|
19213
19563
|
token,
|
|
19214
|
-
|
|
19564
|
+
appSettings,
|
|
19215
19565
|
registry,
|
|
19216
19566
|
index,
|
|
19217
19567
|
archived,
|
|
@@ -19331,6 +19681,7 @@ function attachTerminalSocket(options) {
|
|
|
19331
19681
|
const onConversationMode = (terminalId, mode) => broadcast({ type: "conversation.mode", terminalId, mode });
|
|
19332
19682
|
const onConversationWaiting = (terminalId, waitingFor) => broadcast({ type: "conversation.waiting", terminalId, waitingFor });
|
|
19333
19683
|
const onConversationToolCall = (terminalId, open8) => broadcast({ type: "conversation.toolCall", terminalId, open: open8 });
|
|
19684
|
+
const onConversationSubagents = (terminalId, subagents) => broadcast({ type: "conversation.subagents", terminalId, subagents });
|
|
19334
19685
|
const onConversationPlans = (terminalId, plans) => broadcast({ type: "conversation.plans", terminalId, plans });
|
|
19335
19686
|
const onConversationTurns = (terminalId, turns) => broadcast({ type: "conversation.turns", terminalId, turns });
|
|
19336
19687
|
const onConversationParts = (terminalId, updates) => broadcast({ type: "conversation.parts", terminalId, updates });
|
|
@@ -19394,7 +19745,8 @@ function attachTerminalSocket(options) {
|
|
|
19394
19745
|
waitingFor: snapshot.waitingFor,
|
|
19395
19746
|
// El de ahora y no el del snapshot: leer la configuracion es asincrono,
|
|
19396
19747
|
// y un aviso que salio mientras tanto no puede quedar pisado por uno viejo.
|
|
19397
|
-
openToolCall: conversations.isToolCallOpen(terminalId)
|
|
19748
|
+
openToolCall: conversations.isToolCallOpen(terminalId),
|
|
19749
|
+
subagents: conversations.getSubagents(terminalId)
|
|
19398
19750
|
});
|
|
19399
19751
|
});
|
|
19400
19752
|
};
|
|
@@ -19409,6 +19761,7 @@ function attachTerminalSocket(options) {
|
|
|
19409
19761
|
conversations.on("mode", onConversationMode);
|
|
19410
19762
|
conversations.on("waiting", onConversationWaiting);
|
|
19411
19763
|
conversations.on("toolCall", onConversationToolCall);
|
|
19764
|
+
conversations.on("subagents", onConversationSubagents);
|
|
19412
19765
|
conversations.on("turns", onConversationTurns);
|
|
19413
19766
|
conversations.on("plans", onConversationPlans);
|
|
19414
19767
|
conversations.on("parts", onConversationParts);
|
|
@@ -19419,6 +19772,8 @@ function attachTerminalSocket(options) {
|
|
|
19419
19772
|
const stopRemoteStatus = remote.onChange(
|
|
19420
19773
|
() => broadcastToHost({ type: "remote.status", status: remote.status() })
|
|
19421
19774
|
);
|
|
19775
|
+
const onAppSettingsStatus = (settings2) => broadcast({ type: "settings.status", settings: settings2 });
|
|
19776
|
+
appSettings.on("status", onAppSettingsStatus);
|
|
19422
19777
|
const stopRemoteRevoked = remote.onRevoked((deviceId) => {
|
|
19423
19778
|
for (const [socket, id] of remoteSockets) {
|
|
19424
19779
|
if (deviceId === null || id === deviceId) socket.close(REMOTE_REVOKED_CLOSE_CODE, "revoked");
|
|
@@ -19531,7 +19886,7 @@ function attachTerminalSocket(options) {
|
|
|
19531
19886
|
defaultAgent: agents.defaultAgent(),
|
|
19532
19887
|
platform: process.platform,
|
|
19533
19888
|
defaultCwd,
|
|
19534
|
-
shellName:
|
|
19889
|
+
shellName: appSettings.currentShell()?.name ?? null,
|
|
19535
19890
|
remoteClient: deviceId !== null
|
|
19536
19891
|
});
|
|
19537
19892
|
send(socket, terminalListMessage());
|
|
@@ -19544,6 +19899,7 @@ function attachTerminalSocket(options) {
|
|
|
19544
19899
|
send(socket, { type: "composer.drafts", drafts: draftEntriesOf(registry.list()) });
|
|
19545
19900
|
send(socket, { type: "vault.status", status: vault.status() });
|
|
19546
19901
|
if (deviceId === null) send(socket, { type: "remote.status", status: remote.status() });
|
|
19902
|
+
send(socket, { type: "settings.status", settings: appSettings.status() });
|
|
19547
19903
|
socket.on("message", (raw) => {
|
|
19548
19904
|
const text = Array.isArray(raw) ? Buffer.concat(raw).toString("utf8") : Buffer.from(raw).toString("utf8");
|
|
19549
19905
|
const message = parseClientMessage(text);
|
|
@@ -19578,6 +19934,12 @@ function attachTerminalSocket(options) {
|
|
|
19578
19934
|
*/
|
|
19579
19935
|
case "agent.submit": {
|
|
19580
19936
|
const { terminalId, text: text2, images } = message;
|
|
19937
|
+
let settled = false;
|
|
19938
|
+
const settle = (delivered) => {
|
|
19939
|
+
if (settled || message.requestId === void 0) return;
|
|
19940
|
+
settled = true;
|
|
19941
|
+
send(socket, { type: "agent.submitted", terminalId, requestId: message.requestId, delivered });
|
|
19942
|
+
};
|
|
19581
19943
|
const submittedOwner = draftOwnerOf(registry.get(terminalId));
|
|
19582
19944
|
if (submittedOwner !== null) drafts.delete(submittedOwner);
|
|
19583
19945
|
const files = message.files ?? [];
|
|
@@ -19587,6 +19949,7 @@ function attachTerminalSocket(options) {
|
|
|
19587
19949
|
"submit-failed",
|
|
19588
19950
|
serverText("filesPerMessage", { max: MAX_FILES_PER_SUBMIT })
|
|
19589
19951
|
);
|
|
19952
|
+
settle(false);
|
|
19590
19953
|
break;
|
|
19591
19954
|
}
|
|
19592
19955
|
if (images.length > MAX_IMAGES_PER_SUBMIT) {
|
|
@@ -19595,9 +19958,11 @@ function attachTerminalSocket(options) {
|
|
|
19595
19958
|
"submit-failed",
|
|
19596
19959
|
serverText("imagesPerMessage", { max: MAX_IMAGES_PER_SUBMIT })
|
|
19597
19960
|
);
|
|
19961
|
+
settle(false);
|
|
19598
19962
|
break;
|
|
19599
19963
|
}
|
|
19600
19964
|
if (images.length > 0 && imageStyleFor(socket, terminalId, serverText("imagesUnsupported")) === null) {
|
|
19965
|
+
settle(false);
|
|
19601
19966
|
break;
|
|
19602
19967
|
}
|
|
19603
19968
|
const submitting = adapterOf(terminalId);
|
|
@@ -19618,6 +19983,7 @@ function attachTerminalSocket(options) {
|
|
|
19618
19983
|
});
|
|
19619
19984
|
if (refusal2 !== null) {
|
|
19620
19985
|
sendError(socket, "submit-failed", refusal2);
|
|
19986
|
+
settle(false);
|
|
19621
19987
|
break;
|
|
19622
19988
|
}
|
|
19623
19989
|
}
|
|
@@ -19685,6 +20051,7 @@ function attachTerminalSocket(options) {
|
|
|
19685
20051
|
approvalGuard,
|
|
19686
20052
|
{ startAfterMs }
|
|
19687
20053
|
);
|
|
20054
|
+
settle(outcome === "written");
|
|
19688
20055
|
if (outcome === "interrupted") interrupted();
|
|
19689
20056
|
if (outcome === "blocked") {
|
|
19690
20057
|
const waitingNow = blocksWhileWaiting && registry.activityOf(terminalId) === "waiting";
|
|
@@ -19696,7 +20063,7 @@ function attachTerminalSocket(options) {
|
|
|
19696
20063
|
}
|
|
19697
20064
|
},
|
|
19698
20065
|
{ onDropped: interrupted }
|
|
19699
|
-
);
|
|
20066
|
+
).then(() => settle(false));
|
|
19700
20067
|
break;
|
|
19701
20068
|
}
|
|
19702
20069
|
/*
|
|
@@ -20014,7 +20381,8 @@ function attachTerminalSocket(options) {
|
|
|
20014
20381
|
permissionMode: snapshot.permissionMode,
|
|
20015
20382
|
defaults: await defaultsFor(message.terminalId),
|
|
20016
20383
|
waitingFor: snapshot.waitingFor,
|
|
20017
|
-
openToolCall: conversations.isToolCallOpen(message.terminalId)
|
|
20384
|
+
openToolCall: conversations.isToolCallOpen(message.terminalId),
|
|
20385
|
+
subagents: conversations.getSubagents(message.terminalId)
|
|
20018
20386
|
});
|
|
20019
20387
|
if (snapshot.plans.length > 0) {
|
|
20020
20388
|
send(socket, {
|
|
@@ -20585,6 +20953,24 @@ function attachTerminalSocket(options) {
|
|
|
20585
20953
|
case "search.cancel":
|
|
20586
20954
|
globalSearch.cancel(message.searchId);
|
|
20587
20955
|
break;
|
|
20956
|
+
/*
|
|
20957
|
+
Ajustes (Hito 41, §6.32). Una consola que ya no esta, o un disco que no
|
|
20958
|
+
deja escribir, se dicen; lo demas lo valido el parser. A una ventana
|
|
20959
|
+
remota no llega: lo frena `remoteRefusal`.
|
|
20960
|
+
*/
|
|
20961
|
+
case "settings.update": {
|
|
20962
|
+
const { type: _type, ...change } = message;
|
|
20963
|
+
void appSettings.update(change).catch((error) => {
|
|
20964
|
+
if (error instanceof AppSettingsChangeError) {
|
|
20965
|
+
sendError(socket, "settings-failed", serverText("settingsShellMissing"));
|
|
20966
|
+
return;
|
|
20967
|
+
}
|
|
20968
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
20969
|
+
console.warn("[settings] a change failed:", detail);
|
|
20970
|
+
sendError(socket, "settings-failed", serverText("settingsFailed"), detail);
|
|
20971
|
+
});
|
|
20972
|
+
break;
|
|
20973
|
+
}
|
|
20588
20974
|
/*
|
|
20589
20975
|
El acceso remoto (hito 37). Hasta aca solo llega una ventana del
|
|
20590
20976
|
anfitrion: a un equipo remoto lo freno `remoteRefusal`. El estado
|
|
@@ -20680,10 +21066,12 @@ function attachTerminalSocket(options) {
|
|
|
20680
21066
|
conversations.off("mode", onConversationMode);
|
|
20681
21067
|
conversations.off("waiting", onConversationWaiting);
|
|
20682
21068
|
conversations.off("toolCall", onConversationToolCall);
|
|
21069
|
+
conversations.off("subagents", onConversationSubagents);
|
|
20683
21070
|
repos.off("status", onGitStatus);
|
|
20684
21071
|
memory.off("status", onMemoryStatus);
|
|
20685
21072
|
stopAgentChanges();
|
|
20686
21073
|
stopVaultStatus();
|
|
21074
|
+
appSettings.off("status", onAppSettingsStatus);
|
|
20687
21075
|
stopRemoteStatus();
|
|
20688
21076
|
stopRemoteRevoked();
|
|
20689
21077
|
for (const client of wss.clients) client.terminate();
|
|
@@ -21070,17 +21458,18 @@ ${line}`);
|
|
|
21070
21458
|
async function main() {
|
|
21071
21459
|
const token = createSessionToken();
|
|
21072
21460
|
const defaultCwd = resolveDefaultCwd();
|
|
21073
|
-
const agents = createAgentRegistry();
|
|
21074
|
-
const [, shell] = await Promise.all([agents.locateAll(), locateShell()]);
|
|
21075
21461
|
const movedFrom = await migrateLegacyConfigDir();
|
|
21076
21462
|
if (movedFrom !== null) console.log(`Configuration moved from ${movedFrom} to ${appConfigDir()}`);
|
|
21463
|
+
const settings2 = new SettingsStore();
|
|
21464
|
+
await settings2.load();
|
|
21465
|
+
const agents = createAgentRegistry(settings2.get().agents.disabled);
|
|
21466
|
+
const [, shells] = await Promise.all([agents.locateAll(), locateShells()]);
|
|
21467
|
+
const shell = pickShell(shells, settings2.get().console.shell);
|
|
21077
21468
|
await agents.prepareAll();
|
|
21078
21469
|
ensurePtyHelperExecutable();
|
|
21079
21470
|
const store = new WorkspaceStore();
|
|
21080
21471
|
const archived = new ArchivedSessions();
|
|
21081
21472
|
await archived.load();
|
|
21082
|
-
const settings2 = new SettingsStore();
|
|
21083
|
-
await settings2.load();
|
|
21084
21473
|
const catalog = new VaultCatalog();
|
|
21085
21474
|
await catalog.load(settings2.get().vault.dir ?? defaultVaultDir());
|
|
21086
21475
|
const index = new SessionIndex(agents, archived, catalog);
|
|
@@ -21092,6 +21481,12 @@ async function main() {
|
|
|
21092
21481
|
store,
|
|
21093
21482
|
(sessionId) => index.agentOf(sessionId)
|
|
21094
21483
|
);
|
|
21484
|
+
const appSettings = new AppSettingsService({
|
|
21485
|
+
settings: settings2,
|
|
21486
|
+
agents,
|
|
21487
|
+
shells,
|
|
21488
|
+
onShellChange: (next) => registry.setShell(next)
|
|
21489
|
+
});
|
|
21095
21490
|
const notes = new NotesStore();
|
|
21096
21491
|
await notes.load();
|
|
21097
21492
|
const drafts = new DraftStore();
|
|
@@ -21134,7 +21529,7 @@ async function main() {
|
|
|
21134
21529
|
httpServer,
|
|
21135
21530
|
port,
|
|
21136
21531
|
token,
|
|
21137
|
-
|
|
21532
|
+
appSettings,
|
|
21138
21533
|
registry,
|
|
21139
21534
|
index,
|
|
21140
21535
|
archived,
|