@letta-ai/letta-code 0.29.6 → 0.29.7

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/letta.js CHANGED
@@ -5462,7 +5462,7 @@ var package_default;
5462
5462
  var init_package = __esm(() => {
5463
5463
  package_default = {
5464
5464
  name: "@letta-ai/letta-code",
5465
- version: "0.29.6",
5465
+ version: "0.29.7",
5466
5466
  description: "Letta Code is a CLI tool for interacting with stateful Letta agents from the terminal.",
5467
5467
  type: "module",
5468
5468
  packageManager: "bun@1.3.0",
@@ -155347,7 +155347,8 @@ async function runSlackAttachmentDownloadTask(params) {
155347
155347
  startTime: new Date,
155348
155348
  outputFile,
155349
155349
  totalStdoutLines: 0,
155350
- totalStderrLines: 0
155350
+ totalStderrLines: 0,
155351
+ runtimeScope: params.runtimeScope
155351
155352
  };
155352
155353
  backgroundProcesses.set(taskId, processState);
155353
155354
  appendToOutputFile(outputFile, `${params.description}
@@ -155781,6 +155782,10 @@ async function downloadSlackFile(ctx) {
155781
155782
  }
155782
155783
  const result = await runSlackAttachmentDownloadTask({
155783
155784
  description: `Slack attachment download ${attachmentId} from message ${messageId} in ${request.chatId}`,
155785
+ runtimeScope: {
155786
+ agentId: ctx.route.agentId,
155787
+ conversationId: ctx.route.conversationId
155788
+ },
155784
155789
  download: (signal) => downloadAttachment.call(ctx.adapter, {
155785
155790
  attachmentId,
155786
155791
  chatId: request.chatId,
@@ -170597,11 +170602,6 @@ function evictConversationRuntimeIfIdle(runtime) {
170597
170602
  }
170598
170603
  runtime.listener.conversationRuntimes.delete(runtime.key);
170599
170604
  scheduleWorktreeWatcherIdleStop(runtime.listener, runtime);
170600
- for (const [requestId, runtimeKey] of runtime.listener.approvalRuntimeKeyByRequestId) {
170601
- if (runtimeKey === runtime.key) {
170602
- runtime.listener.approvalRuntimeKeyByRequestId.delete(requestId);
170603
- }
170604
- }
170605
170605
  if (runtime.listener.pendingQueueEmitScope?.agent_id === runtime.agentId && normalizeConversationId(runtime.listener.pendingQueueEmitScope?.conversation_id) === runtime.conversationId) {
170606
170606
  runtime.listener.pendingQueueEmitScope = undefined;
170607
170607
  }
@@ -170648,6 +170648,7 @@ function createConversationRuntime(listener, agentId, conversationId) {
170648
170648
  conversationId: normalizedConversationId,
170649
170649
  skillSources: listener.skillSourcesByConversation.get(runtimeKey)?.slice(),
170650
170650
  activeChannelTurn: null,
170651
+ activeConnectionId: null,
170651
170652
  turnLifecycle,
170652
170653
  messageQueue: Promise.resolve(),
170653
170654
  pendingApprovalResolvers: new Map,
@@ -170759,7 +170760,7 @@ function getPendingControlRequests(runtime, params) {
170759
170760
  if (!conversationRuntime) {
170760
170761
  return requests;
170761
170762
  }
170762
- for (const pending of conversationRuntime.pendingApprovalResolvers.values()) {
170763
+ for (const pending of new Set(conversationRuntime.pendingApprovalResolvers.values())) {
170763
170764
  const request = pending.controlRequest;
170764
170765
  if (!request)
170765
170766
  continue;
@@ -172463,7 +172464,8 @@ async function bash(args) {
172463
172464
  run_in_background = false,
172464
172465
  signal,
172465
172466
  onOutput,
172466
- secretEnv
172467
+ secretEnv,
172468
+ parentScope
172467
172469
  } = args;
172468
172470
  const userCwd = getCurrentWorkingDirectory();
172469
172471
  if (command === "/bg") {
@@ -172533,7 +172535,8 @@ async function bash(args) {
172533
172535
  startTime: new Date,
172534
172536
  outputFile,
172535
172537
  totalStdoutLines: 0,
172536
- totalStderrLines: 0
172538
+ totalStderrLines: 0,
172539
+ runtimeScope: parentScope
172537
172540
  });
172538
172541
  const bgProcess = backgroundProcesses.get(bashId);
172539
172542
  if (!bgProcess) {
@@ -173164,6 +173167,268 @@ var init_worktree_lock = __esm(() => {
173164
173167
  HOSTNAME = os3.hostname();
173165
173168
  });
173166
173169
 
173170
+ // src/websocket/listener/transport.ts
173171
+ import WebSocket3 from "ws";
173172
+
173173
+ class LocalListenerTransport {
173174
+ kind = "local";
173175
+ bufferedAmount = 0;
173176
+ isOpen() {
173177
+ return true;
173178
+ }
173179
+ send(_data) {}
173180
+ }
173181
+ function isListenerTransportOpen(transport) {
173182
+ if ("isOpen" in transport && typeof transport.isOpen === "function") {
173183
+ return transport.isOpen();
173184
+ }
173185
+ return transport.readyState === WebSocket3.OPEN;
173186
+ }
173187
+ function getListenerTransportKind(transport) {
173188
+ return "kind" in transport ? transport.kind : "websocket";
173189
+ }
173190
+ var init_transport = () => {};
173191
+
173192
+ // src/websocket/listener/connection.ts
173193
+ function toListenerConnection(connectionId) {
173194
+ return { type: "ToConnection", connectionId };
173195
+ }
173196
+ function getResumeStates(runtime) {
173197
+ let states2 = resumeStateByRuntime.get(runtime);
173198
+ if (!states2) {
173199
+ states2 = new Map;
173200
+ resumeStateByRuntime.set(runtime, states2);
173201
+ }
173202
+ return states2;
173203
+ }
173204
+ function socketForTransport(transport) {
173205
+ return "kind" in transport ? null : transport;
173206
+ }
173207
+ function refreshLegacySingleConnection(runtime) {
173208
+ const live = [...runtime.connections.values()].filter((connection) => isListenerTransportOpen(connection.writer));
173209
+ const only = live.length === 1 ? live[0] : null;
173210
+ runtime.transport = only?.writer ?? runtime.processTransport;
173211
+ runtime.streamTransport = only?.streamWriter ?? null;
173212
+ runtime.socket = only ? socketForTransport(only.writer) : null;
173213
+ runtime.streamSocket = only?.streamWriter ? socketForTransport(only.streamWriter) : null;
173214
+ }
173215
+ function createConnectionRequestKey(connectionId, requestId) {
173216
+ return JSON.stringify([connectionId, requestId]);
173217
+ }
173218
+ function openListenerConnection(params) {
173219
+ const existing = params.runtime.connections.get(params.connectionId);
173220
+ if (existing) {
173221
+ throw new Error(`Listener connection already open: ${params.connectionId}`);
173222
+ }
173223
+ const resumeStates = getResumeStates(params.runtime);
173224
+ const resumed = resumeStates.get(params.connectionId);
173225
+ resumeStates.delete(params.connectionId);
173226
+ const connection = {
173227
+ id: params.connectionId,
173228
+ ordinal: resumed?.ordinal ?? params.runtime.nextConnectionOrdinal,
173229
+ writer: params.writer,
173230
+ streamWriter: params.streamWriter ?? null,
173231
+ cancellation: params.cancellation ?? new AbortController,
173232
+ initialized: false,
173233
+ subscriptions: resumed?.subscriptions ?? new Set,
173234
+ eventSeqCounter: resumed?.eventSeqCounter ?? 0,
173235
+ options: params.options
173236
+ };
173237
+ if (!resumed) {
173238
+ params.runtime.nextConnectionOrdinal += 1;
173239
+ }
173240
+ params.runtime.connections.set(connection.id, connection);
173241
+ for (const runtimeKey of connection.subscriptions) {
173242
+ let connectionIds = params.runtime.connectionIdsByRuntimeKey.get(runtimeKey);
173243
+ if (!connectionIds) {
173244
+ connectionIds = new Set;
173245
+ params.runtime.connectionIdsByRuntimeKey.set(runtimeKey, connectionIds);
173246
+ }
173247
+ connectionIds.add(connection.id);
173248
+ }
173249
+ refreshLegacySingleConnection(params.runtime);
173250
+ return connection;
173251
+ }
173252
+ function markListenerConnectionInitialized(runtime, connectionId) {
173253
+ const connection = runtime.connections.get(connectionId);
173254
+ if (connection) {
173255
+ connection.initialized = true;
173256
+ }
173257
+ }
173258
+ function subscribeListenerConnection(runtime, connectionId, scope) {
173259
+ const connection = runtime.connections.get(connectionId);
173260
+ if (!connection || typeof scope.agent_id !== "string") {
173261
+ return false;
173262
+ }
173263
+ const runtimeKey = getConversationRuntimeKey(scope.agent_id, scope.conversation_id);
173264
+ connection.subscriptions.add(runtimeKey);
173265
+ let connectionIds = runtime.connectionIdsByRuntimeKey.get(runtimeKey);
173266
+ if (!connectionIds) {
173267
+ connectionIds = new Set;
173268
+ runtime.connectionIdsByRuntimeKey.set(runtimeKey, connectionIds);
173269
+ }
173270
+ connectionIds.add(connectionId);
173271
+ return true;
173272
+ }
173273
+ function unsubscribeListenerConnection(runtime, connectionId, runtimeKey) {
173274
+ const connection = runtime.connections.get(connectionId);
173275
+ if (!connection?.subscriptions.delete(runtimeKey)) {
173276
+ return false;
173277
+ }
173278
+ const connectionIds = runtime.connectionIdsByRuntimeKey.get(runtimeKey);
173279
+ connectionIds?.delete(connectionId);
173280
+ if (connectionIds?.size === 0) {
173281
+ runtime.connectionIdsByRuntimeKey.delete(runtimeKey);
173282
+ }
173283
+ return true;
173284
+ }
173285
+ function getSubscribedListenerConnections(runtime, scope) {
173286
+ if (typeof scope.agent_id !== "string") {
173287
+ return [];
173288
+ }
173289
+ const runtimeKey = getConversationRuntimeKey(scope.agent_id, scope.conversation_id);
173290
+ const connectionIds = runtime.connectionIdsByRuntimeKey.get(runtimeKey);
173291
+ if (!connectionIds) {
173292
+ return [];
173293
+ }
173294
+ return [...connectionIds].map((connectionId) => runtime.connections.get(connectionId)).filter((connection) => connection?.initialized === true && isListenerTransportOpen(connection.writer)).sort((a, b) => a.ordinal - b.ordinal);
173295
+ }
173296
+ function findListenerConnectionByTransport(runtime, transport) {
173297
+ for (const connection of runtime.connections.values()) {
173298
+ if (connection.writer === transport || connection.streamWriter === transport) {
173299
+ return connection;
173300
+ }
173301
+ }
173302
+ return null;
173303
+ }
173304
+ function nextListenerConnectionEventSeq(connection, runtime) {
173305
+ if (!connection) {
173306
+ return nextEventSeq(runtime);
173307
+ }
173308
+ connection.eventSeqCounter += 1;
173309
+ return connection.eventSeqCounter;
173310
+ }
173311
+ function resolveListenerConnectionTargets(params) {
173312
+ const runtime = params.runtime;
173313
+ let connections;
173314
+ switch (params.routing.type) {
173315
+ case "ToConnection": {
173316
+ const explicitConnection = runtime?.connections.get(params.routing.connectionId);
173317
+ if (explicitConnection) {
173318
+ connections = [explicitConnection];
173319
+ break;
173320
+ }
173321
+ if ((runtime?.connections.size ?? 0) === 0 && params.routing.connectionId === (runtime?.connectionId ?? "legacy")) {
173322
+ connections = [null];
173323
+ break;
173324
+ }
173325
+ return [];
173326
+ }
173327
+ case "ToSubscribers": {
173328
+ connections = runtime ? getSubscribedListenerConnections(runtime, params.scope) : [];
173329
+ if (connections.length > 0) {
173330
+ break;
173331
+ }
173332
+ if ((runtime?.connections.size ?? 0) === 0) {
173333
+ connections = [null];
173334
+ break;
173335
+ }
173336
+ return [];
173337
+ }
173338
+ case "Broadcast": {
173339
+ connections = runtime ? [...runtime.connections.values()].filter((connection) => connection.initialized && isListenerTransportOpen(connection.writer)) : [];
173340
+ if (connections.length === 0 && (runtime?.connections.size ?? 0) === 0) {
173341
+ connections = [null];
173342
+ }
173343
+ break;
173344
+ }
173345
+ }
173346
+ return connections.map((connection) => {
173347
+ const streamTransport = connection?.streamWriter;
173348
+ if (params.streamMessage && streamTransport && isListenerTransportOpen(streamTransport)) {
173349
+ return { connection, transport: streamTransport };
173350
+ }
173351
+ const legacyStreamTransport = params.runtime?.streamTransport;
173352
+ if (!connection && params.streamMessage && legacyStreamTransport && isListenerTransportOpen(legacyStreamTransport)) {
173353
+ return { connection, transport: legacyStreamTransport };
173354
+ }
173355
+ return { connection, transport: connection?.writer ?? params.origin };
173356
+ });
173357
+ }
173358
+ function closeListenerConnection(runtime, connectionId) {
173359
+ getResumeStates(runtime).delete(connectionId);
173360
+ const connection = runtime.connections.get(connectionId);
173361
+ if (!connection) {
173362
+ return null;
173363
+ }
173364
+ for (const runtimeKey of [...connection.subscriptions]) {
173365
+ unsubscribeListenerConnection(runtime, connectionId, runtimeKey);
173366
+ }
173367
+ runtime.connections.delete(connectionId);
173368
+ connection.cancellation.abort();
173369
+ refreshLegacySingleConnection(runtime);
173370
+ return connection;
173371
+ }
173372
+ function suspendListenerConnection(runtime, connectionId) {
173373
+ const connection = runtime.connections.get(connectionId);
173374
+ if (!connection) {
173375
+ return null;
173376
+ }
173377
+ const resumeState = {
173378
+ ordinal: connection.ordinal,
173379
+ subscriptions: new Set(connection.subscriptions),
173380
+ eventSeqCounter: connection.eventSeqCounter
173381
+ };
173382
+ const closed = closeListenerConnection(runtime, connectionId);
173383
+ getResumeStates(runtime).set(connectionId, resumeState);
173384
+ return closed;
173385
+ }
173386
+
173387
+ class ProcessRuntimeTransport {
173388
+ runtime;
173389
+ kind = "runtime";
173390
+ constructor(runtime) {
173391
+ this.runtime = runtime;
173392
+ }
173393
+ get bufferedAmount() {
173394
+ let total = 0;
173395
+ for (const connection of this.runtime.connections.values()) {
173396
+ total += connection.writer.bufferedAmount;
173397
+ }
173398
+ return total;
173399
+ }
173400
+ isOpen() {
173401
+ for (const connection of this.runtime.connections.values()) {
173402
+ if (connection.initialized && isListenerTransportOpen(connection.writer)) {
173403
+ return true;
173404
+ }
173405
+ }
173406
+ return false;
173407
+ }
173408
+ send(data) {
173409
+ throw new Error(`Process runtime transport cannot send an implicit message (${data.length} bytes); resolve ToConnection, ToSubscribers, or Broadcast first`);
173410
+ }
173411
+ }
173412
+ function getOrCreateProcessTransport(runtime) {
173413
+ runtime.processTransport ??= new ProcessRuntimeTransport(runtime);
173414
+ if (runtime.connections.size !== 1) {
173415
+ refreshLegacySingleConnection(runtime);
173416
+ }
173417
+ return runtime.processTransport;
173418
+ }
173419
+ var TO_SUBSCRIBERS, BROADCAST, resumeStateByRuntime;
173420
+ var init_connection = __esm(() => {
173421
+ init_runtime6();
173422
+ init_transport();
173423
+ TO_SUBSCRIBERS = {
173424
+ type: "ToSubscribers"
173425
+ };
173426
+ BROADCAST = {
173427
+ type: "Broadcast"
173428
+ };
173429
+ resumeStateByRuntime = new WeakMap;
173430
+ });
173431
+
173167
173432
  // src/cli/helpers/memory-reminder.ts
173168
173433
  function isValidStepCount(value) {
173169
173434
  return typeof value === "number" && Number.isFinite(value) && Number.isInteger(value) && value > 0;
@@ -173434,6 +173699,43 @@ var init_system_prompt_warning = __esm(() => {
173434
173699
  systemPromptDoctorStateByAgent = new Map;
173435
173700
  });
173436
173701
 
173702
+ // src/websocket/listener/background-process-snapshot.ts
173703
+ function belongsToRuntime(entry, runtimeScope) {
173704
+ return entry.runtimeScope?.agentId === runtimeScope.agentId && entry.runtimeScope.conversationId === runtimeScope.conversationId;
173705
+ }
173706
+ function buildBackgroundProcessSnapshot(agentId, conversationId = "default") {
173707
+ if (agentId === null) {
173708
+ return [];
173709
+ }
173710
+ const runtimeScope = agentId === undefined ? undefined : { agentId, conversationId };
173711
+ const bashProcesses = Array.from(backgroundProcesses.entries()).filter(([, proc]) => proc.status === "running" && (!runtimeScope || belongsToRuntime(proc, runtimeScope))).map(([processId, proc]) => ({
173712
+ process_id: processId,
173713
+ kind: "bash",
173714
+ command: proc.command,
173715
+ started_at_ms: proc.startTime?.getTime() ?? null,
173716
+ status: proc.status,
173717
+ exit_code: proc.exitCode
173718
+ }));
173719
+ const taskProcesses = Array.from(backgroundTasks.entries()).filter(([, task]) => task.status === "running" && (!runtimeScope || belongsToRuntime(task, runtimeScope))).map(([processId, task]) => ({
173720
+ process_id: processId,
173721
+ kind: "agent_task",
173722
+ task_type: task.subagentType,
173723
+ description: task.description,
173724
+ started_at_ms: task.startTime.getTime(),
173725
+ status: task.status,
173726
+ subagent_id: task.subagentId,
173727
+ ...task.error ? { error: task.error } : {}
173728
+ }));
173729
+ return [...bashProcesses, ...taskProcesses].sort((a, b) => {
173730
+ const aStart = a.started_at_ms ?? 0;
173731
+ const bStart = b.started_at_ms ?? 0;
173732
+ return bStart - aStart;
173733
+ });
173734
+ }
173735
+ var init_background_process_snapshot = __esm(() => {
173736
+ init_process_manager();
173737
+ });
173738
+
173437
173739
  // src/websocket/listener/channel-turn-session.ts
173438
173740
  function getChannelTurnSourceKey(source2) {
173439
173741
  return [
@@ -346947,28 +347249,6 @@ var init_mod_commands = __esm(async () => {
346947
347249
  ]);
346948
347250
  });
346949
347251
 
346950
- // src/websocket/listener/transport.ts
346951
- import WebSocket3 from "ws";
346952
-
346953
- class LocalListenerTransport {
346954
- kind = "local";
346955
- bufferedAmount = 0;
346956
- isOpen() {
346957
- return true;
346958
- }
346959
- send(_data) {}
346960
- }
346961
- function isListenerTransportOpen(transport) {
346962
- if ("isOpen" in transport && typeof transport.isOpen === "function") {
346963
- return transport.isOpen();
346964
- }
346965
- return transport.readyState === WebSocket3.OPEN;
346966
- }
346967
- function getListenerTransportKind(transport) {
346968
- return "kind" in transport ? transport.kind : "websocket";
346969
- }
346970
- var init_transport = () => {};
346971
-
346972
347252
  // src/websocket/listener/outbound-wire.ts
346973
347253
  import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync24 } from "node:fs";
346974
347254
  import { dirname as dirname14 } from "node:path";
@@ -347302,31 +347582,6 @@ function getScopeForRuntime(runtime, scope) {
347302
347582
  }
347303
347583
  return scope ?? {};
347304
347584
  }
347305
- function buildBackgroundProcessSnapshot() {
347306
- const bashProcesses = Array.from(backgroundProcesses.entries()).filter(([, proc]) => proc.status === "running").map(([processId, proc]) => ({
347307
- process_id: processId,
347308
- kind: "bash",
347309
- command: proc.command,
347310
- started_at_ms: proc.startTime?.getTime() ?? null,
347311
- status: proc.status,
347312
- exit_code: proc.exitCode
347313
- }));
347314
- const taskProcesses = Array.from(backgroundTasks.entries()).filter(([, task]) => task.status === "running").map(([processId, task]) => ({
347315
- process_id: processId,
347316
- kind: "agent_task",
347317
- task_type: task.subagentType,
347318
- description: task.description,
347319
- started_at_ms: task.startTime.getTime(),
347320
- status: task.status,
347321
- subagent_id: task.subagentId,
347322
- ...task.error ? { error: task.error } : {}
347323
- }));
347324
- return [...bashProcesses, ...taskProcesses].sort((a, b) => {
347325
- const aStart = a.started_at_ms ?? 0;
347326
- const bStart = b.started_at_ms ?? 0;
347327
- return bStart - aStart;
347328
- });
347329
- }
347330
347585
  function emitRuntimeStateUpdates(runtime, scope) {
347331
347586
  emitLoopStatusIfOpen(runtime, scope);
347332
347587
  emitDeviceStatusIfOpen(runtime, scope);
@@ -347401,7 +347656,7 @@ function buildDeviceStatus(runtime, params) {
347401
347656
  current_toolset_preference: conversationRuntime?.currentToolsetPreference ?? toolsetPreference,
347402
347657
  current_loaded_tools: conversationRuntime?.currentLoadedTools ?? [],
347403
347658
  current_available_skills: [],
347404
- background_processes: buildBackgroundProcessSnapshot(),
347659
+ background_processes: buildBackgroundProcessSnapshot(scopedAgentId, scopedConversationId),
347405
347660
  pending_control_requests: interruptedCacheActive ? [] : getPendingControlRequests(listener, scope),
347406
347661
  experiments: experimentManager.list(),
347407
347662
  memory_directory: scopedAgentId ? getScopedMemoryFilesystemRoot(scopedAgentId) : null,
@@ -347464,87 +347719,90 @@ function isStreamChannelMessage(type3) {
347464
347719
  function classifyOutboundFrame(message) {
347465
347720
  return COALESCABLE_STATUS_MESSAGE_TYPES.has(message.type) ? "status" : "critical";
347466
347721
  }
347467
- function emitProtocolV2Message(socket, runtime, message, scope) {
347722
+ function emitProtocolV2Message(socket, runtime, message, scope, routing) {
347468
347723
  const listener = getListenerRuntime(runtime);
347469
- let targetSocket = socket;
347470
- if (listener?.streamTransport && isStreamChannelMessage(message.type)) {
347471
- if (isListenerTransportOpen(listener.streamTransport)) {
347472
- targetSocket = listener.streamTransport;
347473
- }
347474
- }
347475
347724
  const runtimeScope = resolveRuntimeScope(listener, getScopeForRuntime(runtime, scope));
347476
347725
  if (!runtimeScope)
347477
347726
  return;
347478
347727
  notifyStreamObservers(listener, message, runtimeScope);
347479
- if (!isListenerTransportOpen(targetSocket))
347480
- return;
347481
347728
  const frameClass = classifyOutboundFrame(message);
347482
- enqueueOutboundFrame(targetSocket, {
347483
- typeLabel: message.type,
347484
- frameClass,
347485
- ...frameClass === "status" ? {
347486
- coalesceKey: `${message.type}:${runtimeScope.agent_id ?? ""}:${runtimeScope.conversation_id ?? ""}`
347487
- } : {},
347488
- build: () => {
347489
- const eventSeq = nextEventSeq(listener);
347490
- if (eventSeq === null)
347491
- return null;
347492
- const outbound = {
347493
- ...message,
347494
- runtime: runtimeScope,
347495
- event_seq: eventSeq,
347496
- emitted_at: new Date().toISOString(),
347497
- idempotency_key: `${message.type}:${eventSeq}:${crypto.randomUUID()}`
347498
- };
347499
- let payload;
347500
- try {
347501
- payload = JSON.stringify(outbound);
347502
- } catch (error54) {
347503
- console.error(`[Listen V2] Failed to emit ${message.type} (seq=${eventSeq})`, error54);
347729
+ const targets = resolveListenerConnectionTargets({
347730
+ runtime: listener,
347731
+ origin: socket,
347732
+ scope: runtimeScope,
347733
+ routing,
347734
+ streamMessage: isStreamChannelMessage(message.type)
347735
+ });
347736
+ for (const { connection, transport: targetSocket } of targets) {
347737
+ if (!isListenerTransportOpen(targetSocket))
347738
+ continue;
347739
+ enqueueOutboundFrame(targetSocket, {
347740
+ typeLabel: message.type,
347741
+ frameClass,
347742
+ ...frameClass === "status" ? {
347743
+ coalesceKey: `${message.type}:${runtimeScope.agent_id ?? ""}:${runtimeScope.conversation_id ?? ""}`
347744
+ } : {},
347745
+ build: () => {
347746
+ const eventSeq = nextListenerConnectionEventSeq(connection, listener);
347747
+ if (eventSeq === null)
347748
+ return null;
347749
+ const outbound = {
347750
+ ...message,
347751
+ runtime: runtimeScope,
347752
+ event_seq: eventSeq,
347753
+ emitted_at: new Date().toISOString(),
347754
+ idempotency_key: `${message.type}:${eventSeq}:${crypto.randomUUID()}`
347755
+ };
347756
+ let payload;
347757
+ try {
347758
+ payload = JSON.stringify(outbound);
347759
+ } catch (error54) {
347760
+ console.error(`[Listen V2] Failed to emit ${message.type} (seq=${eventSeq})`, error54);
347761
+ safeEmitWsEvent("send", "lifecycle", {
347762
+ type: "_ws_send_error",
347763
+ message_type: message.type,
347764
+ event_seq: eventSeq,
347765
+ error: error54 instanceof Error ? error54.message : String(error54)
347766
+ });
347767
+ return null;
347768
+ }
347769
+ return {
347770
+ payload,
347771
+ perfKey: getProtocolPerfKey(message),
347772
+ onSent: () => {
347773
+ if (isDebugEnabled()) {
347774
+ console.log(`[Listen V2] Emitting ${message.type} (seq=${eventSeq})`);
347775
+ }
347776
+ safeEmitWsEvent("send", "protocol", outbound);
347777
+ }
347778
+ };
347779
+ },
347780
+ onSendError: (error54) => {
347781
+ console.error(`[Listen V2] Failed to emit ${message.type}`, error54);
347504
347782
  safeEmitWsEvent("send", "lifecycle", {
347505
347783
  type: "_ws_send_error",
347506
347784
  message_type: message.type,
347507
- event_seq: eventSeq,
347508
347785
  error: error54 instanceof Error ? error54.message : String(error54)
347509
347786
  });
347510
- return null;
347511
347787
  }
347512
- return {
347513
- payload,
347514
- perfKey: getProtocolPerfKey(message),
347515
- onSent: () => {
347516
- if (isDebugEnabled()) {
347517
- console.log(`[Listen V2] Emitting ${message.type} (seq=${eventSeq})`);
347518
- }
347519
- safeEmitWsEvent("send", "protocol", outbound);
347520
- }
347521
- };
347522
- },
347523
- onSendError: (error54) => {
347524
- console.error(`[Listen V2] Failed to emit ${message.type}`, error54);
347525
- safeEmitWsEvent("send", "lifecycle", {
347526
- type: "_ws_send_error",
347527
- message_type: message.type,
347528
- error: error54 instanceof Error ? error54.message : String(error54)
347529
- });
347530
- }
347531
- });
347788
+ });
347789
+ }
347532
347790
  }
347533
- function emitDeviceStatusUpdate(socket, runtime, scope) {
347791
+ function emitDeviceStatusUpdate(socket, runtime, scope, routing = TO_SUBSCRIBERS) {
347534
347792
  const deviceStatus = buildDeviceStatus(runtime, scope);
347535
347793
  recordDeviceStatus(socket, getScopeForRuntime(runtime, scope), deviceStatus);
347536
347794
  const message = {
347537
347795
  type: "update_device_status",
347538
347796
  device_status: deviceStatus
347539
347797
  };
347540
- emitProtocolV2Message(socket, runtime, message, scope);
347798
+ emitProtocolV2Message(socket, runtime, message, scope, routing);
347541
347799
  }
347542
- function emitLoopStatusUpdate(socket, runtime, scope) {
347800
+ function emitLoopStatusUpdate(socket, runtime, scope, routing = TO_SUBSCRIBERS) {
347543
347801
  const message = {
347544
347802
  type: "update_loop_status",
347545
347803
  loop_status: buildLoopStatus(runtime, scope)
347546
347804
  };
347547
- emitProtocolV2Message(socket, runtime, message, scope);
347805
+ emitProtocolV2Message(socket, runtime, message, scope, routing);
347548
347806
  }
347549
347807
  function emitLoopStatusIfOpen(runtime, scope) {
347550
347808
  const listener = getListenerRuntime(runtime);
@@ -347560,7 +347818,7 @@ function emitDeviceStatusIfOpen(runtime, scope) {
347560
347818
  emitDeviceStatusUpdate(transport, runtime, scope);
347561
347819
  }
347562
347820
  }
347563
- function emitQueueUpdate(socket, runtime, scope) {
347821
+ function emitQueueUpdate(socket, runtime, scope, routing = TO_SUBSCRIBERS) {
347564
347822
  const listener = getListenerRuntime(runtime);
347565
347823
  if (!listener) {
347566
347824
  return;
@@ -347570,7 +347828,7 @@ function emitQueueUpdate(socket, runtime, scope) {
347570
347828
  type: "update_queue",
347571
347829
  queue: buildQueueSnapshot(runtime, resolvedScope)
347572
347830
  };
347573
- emitProtocolV2Message(socket, runtime, message, resolvedScope);
347831
+ emitProtocolV2Message(socket, runtime, message, resolvedScope, routing);
347574
347832
  }
347575
347833
  function isTextContentPart(part) {
347576
347834
  return !!part && typeof part === "object" && "type" in part && part.type === "text" && "text" in part && typeof part.text === "string";
@@ -347670,7 +347928,7 @@ function emitQueueUpdateIfOpen(runtime, scope) {
347670
347928
  emitQueueUpdate(transport, runtime, scope);
347671
347929
  }
347672
347930
  }
347673
- function emitDeviceStatusUpdateIfChanged(socket, runtime, scope, options3) {
347931
+ function emitDeviceStatusUpdateIfChanged(socket, runtime, scope, options3, routing = TO_SUBSCRIBERS) {
347674
347932
  const resolvedScope = getScopeForRuntime(runtime, scope);
347675
347933
  const deviceStatus = buildDeviceStatus(runtime, resolvedScope);
347676
347934
  if (!shouldEmitDeviceStatus(socket, resolvedScope, deviceStatus, options3?.force)) {
@@ -347680,14 +347938,15 @@ function emitDeviceStatusUpdateIfChanged(socket, runtime, scope, options3) {
347680
347938
  type: "update_device_status",
347681
347939
  device_status: deviceStatus
347682
347940
  };
347683
- emitProtocolV2Message(socket, runtime, message, resolvedScope);
347941
+ emitProtocolV2Message(socket, runtime, message, resolvedScope, routing);
347684
347942
  return true;
347685
347943
  }
347686
347944
  function emitStateSync(socket, runtime, scope, options3) {
347687
- emitDeviceStatusUpdateIfChanged(socket, runtime, scope, options3?.forceDeviceStatus ? { force: true } : undefined);
347688
- emitLoopStatusUpdate(socket, runtime, scope);
347689
- emitQueueUpdate(socket, runtime, scope);
347690
- emitSubagentStateUpdate(socket, runtime, scope);
347945
+ const routing = options3?.routing ?? TO_SUBSCRIBERS;
347946
+ emitDeviceStatusUpdateIfChanged(socket, runtime, scope, options3?.forceDeviceStatus ? { force: true } : undefined, routing);
347947
+ emitLoopStatusUpdate(socket, runtime, scope, routing);
347948
+ emitQueueUpdate(socket, runtime, scope, routing);
347949
+ emitSubagentStateUpdate(socket, runtime, scope, routing);
347691
347950
  }
347692
347951
  function resolveSubagentScopeForSnapshot(runtime, scope) {
347693
347952
  const listener = getListenerRuntime(runtime);
@@ -347728,12 +347987,12 @@ function buildSubagentSnapshot(runtime, scope) {
347728
347987
  error: a.error
347729
347988
  }));
347730
347989
  }
347731
- function emitSubagentStateUpdate(socket, runtime, scope) {
347990
+ function emitSubagentStateUpdate(socket, runtime, scope, routing = TO_SUBSCRIBERS) {
347732
347991
  const message = {
347733
347992
  type: "update_subagent_state",
347734
347993
  subagents: buildSubagentSnapshot(runtime, scope)
347735
347994
  };
347736
- emitProtocolV2Message(socket, runtime, message, scope);
347995
+ emitProtocolV2Message(socket, runtime, message, scope, routing);
347737
347996
  }
347738
347997
  function emitSubagentStateIfOpen(runtime, scope) {
347739
347998
  const listener = getListenerRuntime(runtime);
@@ -347842,7 +348101,7 @@ function emitStreamDelta(socket, runtime, delta2, scope, subagentId) {
347842
348101
  delta: delta2,
347843
348102
  ...subagentId ? { subagent_id: subagentId } : {}
347844
348103
  };
347845
- emitProtocolV2Message(socket, runtime, message, scope);
348104
+ emitProtocolV2Message(socket, runtime, message, scope, TO_SUBSCRIBERS);
347846
348105
  }
347847
348106
  var GIT_CONTEXT_CACHE_TTL_MS = 15000, MAX_GIT_CONTEXT_CACHE_ENTRIES = 64, FROZEN_SUPPORTED_COMMANDS, gitContextCache, STREAM_CHANNEL_MESSAGE_TYPES, COALESCABLE_STATUS_MESSAGE_TYPES;
347848
348107
  var init_protocol_outbound = __esm(async () => {
@@ -347856,9 +348115,10 @@ var init_protocol_outbound = __esm(async () => {
347856
348115
  init_manager();
347857
348116
  init_mode();
347858
348117
  init_settings_manager();
347859
- init_process_manager();
347860
348118
  init_debug();
348119
+ init_background_process_snapshot();
347861
348120
  init_channel_turn_session();
348121
+ init_connection();
347862
348122
  init_constants3();
347863
348123
  init_cwd();
347864
348124
  init_device_status_cache();
@@ -347921,9 +348181,9 @@ async function switchConversationWorkingDirectory(params) {
347921
348181
  reminderState.hasSentSessionContext = false;
347922
348182
  reminderState.pendingSessionContextReason = "cwd_changed";
347923
348183
  }
347924
- const statusSocket = params.statusSocket ?? runtime.socket;
347925
- if (params.emitStatus !== false && statusSocket) {
347926
- emitDeviceStatusUpdate(statusSocket, params.statusRuntime ?? conversationRuntime ?? runtime, {
348184
+ if (params.emitStatus !== false) {
348185
+ const statusTransport = params.statusSocket ?? getOrCreateProcessTransport(runtime);
348186
+ emitDeviceStatusUpdate(statusTransport, params.statusRuntime ?? conversationRuntime ?? runtime, {
347927
348187
  agent_id: agentId,
347928
348188
  conversation_id: conversationId
347929
348189
  });
@@ -347932,6 +348192,7 @@ async function switchConversationWorkingDirectory(params) {
347932
348192
  var init_cwd_change = __esm(async () => {
347933
348193
  init_runtime_context();
347934
348194
  init_settings_manager();
348195
+ init_connection();
347935
348196
  init_cwd();
347936
348197
  init_runtime6();
347937
348198
  await init_protocol_outbound();
@@ -349415,7 +349676,8 @@ async function startExecSession(args) {
349415
349676
  startTime: new Date,
349416
349677
  outputFile,
349417
349678
  totalStdoutLines: 0,
349418
- totalStderrLines: 0
349679
+ totalStderrLines: 0,
349680
+ runtimeScope: args.parentScope
349419
349681
  });
349420
349682
  if (session.status !== "running") {
349421
349683
  scheduleBackgroundProcessCleanup(id2);
@@ -363635,7 +363897,8 @@ function spawnBackgroundSubagentTask(args) {
363635
363897
  output: [],
363636
363898
  startTime: new Date,
363637
363899
  outputFile,
363638
- abortController
363900
+ abortController,
363901
+ runtimeScope: resolvedParentScope
363639
363902
  };
363640
363903
  backgroundTasks.set(taskId, bgTask);
363641
363904
  writeTaskTranscriptStart(outputFile, description, subagentType);
@@ -369549,10 +369812,8 @@ function filterToolRegistryByClientAllowlist(registry2, clientToolAllowlist) {
369549
369812
  }
369550
369813
  function filterExternalToolsByRuntimeContext(externalTools, runtimeContext) {
369551
369814
  return new Map(Array.from(externalTools.entries()).filter(([, tool2]) => {
369552
- if (!tool2.runtime) {
369553
- return true;
369554
- }
369555
- return tool2.runtime.agentId === runtimeContext.agentId && tool2.runtime.conversationId === runtimeContext.conversationId;
369815
+ const matchesConnection = tool2.connectionId === undefined || tool2.connectionId === runtimeContext.connectionId;
369816
+ return matchesConnection && (!tool2.runtime || tool2.runtime.agentId === runtimeContext.agentId && tool2.runtime.conversationId === runtimeContext.conversationId);
369556
369817
  }));
369557
369818
  }
369558
369819
  function filterExternalToolsByScopeIds(externalTools, externalToolScopeIds) {
@@ -370747,6 +371008,9 @@ async function executeToolInner(name, args, options3) {
370747
371008
  if (Object.keys(secretEnv).length > 0) {
370748
371009
  enhancedArgs = { ...enhancedArgs, secretEnv };
370749
371010
  }
371011
+ if (options3?.parentScope) {
371012
+ enhancedArgs = { ...enhancedArgs, parentScope: options3.parentScope };
371013
+ }
370750
371014
  }
370751
371015
  if (internalName === "Task") {
370752
371016
  if (options3?.toolCallId) {
@@ -371460,6 +371724,7 @@ function parseInheritedChannelContextEnv() {
371460
371724
  }
371461
371725
  async function prepareToolExecutionContextForScope(params) {
371462
371726
  const {
371727
+ connectionId,
371463
371728
  agentId,
371464
371729
  conversationId,
371465
371730
  overrideModel,
@@ -371522,6 +371787,7 @@ async function prepareToolExecutionContextForScope(params) {
371522
371787
  modAdapters,
371523
371788
  agent: agent2,
371524
371789
  runtimeContext: {
371790
+ connectionId,
371525
371791
  agentId,
371526
371792
  agentName: agent2.name ?? null,
371527
371793
  conversationId: scopedConversationId,
@@ -435895,6 +436161,7 @@ function consumeQueuedTurn(runtime) {
435895
436161
  let hasTaskNotification = false;
435896
436162
  let hasCronPrompt = false;
435897
436163
  let hasModContinue = false;
436164
+ let batchConnectionId;
435898
436165
  let batchImageFailureMode = null;
435899
436166
  const isNoCoalesce = (candidate) => candidate.kind === "message" && candidate.noCoalesce === true;
435900
436167
  for (const item of queuedItems) {
@@ -435905,6 +436172,11 @@ function consumeQueuedTurn(runtime) {
435905
436172
  break;
435906
436173
  }
435907
436174
  if (item.kind === "message") {
436175
+ const itemConnectionId = runtime.queuedMessagesByItemId.get(item.id)?.connectionId;
436176
+ if (batchConnectionId !== undefined && itemConnectionId !== undefined && itemConnectionId !== batchConnectionId) {
436177
+ break;
436178
+ }
436179
+ batchConnectionId ??= itemConnectionId;
435908
436180
  const itemImageFailureMode = getInboundImageFailureMode(runtime.queuedMessagesByItemId.get(item.id));
435909
436181
  if (batchImageFailureMode !== null && itemImageFailureMode !== batchImageFailureMode) {
435910
436182
  break;
@@ -436087,14 +436359,6 @@ function ensureConversationQueueRuntime(listener, runtime) {
436087
436359
  function getOrCreateScopedRuntime(listener, agentId, conversationId) {
436088
436360
  return ensureConversationQueueRuntime(listener, getOrCreateConversationRuntime(listener, agentId, conversationId));
436089
436361
  }
436090
- function findFallbackRuntime(listener) {
436091
- for (const cr of listener.conversationRuntimes.values()) {
436092
- if (cr.queueRuntime) {
436093
- return cr;
436094
- }
436095
- }
436096
- return null;
436097
- }
436098
436362
  var init_conversation_runtime = __esm(async () => {
436099
436363
  init_queue_runtime();
436100
436364
  init_runtime6();
@@ -436236,21 +436500,20 @@ async function resolveCronFireConversationId(task2) {
436236
436500
  return task2.conversation_id === "default" ? undefined : task2.conversation_id;
436237
436501
  }
436238
436502
  function emitCronsUpdated(socket, task2, conversationId) {
436239
- if (!isListenerTransportOpen(socket)) {
436503
+ const listener = getActiveRuntime();
436504
+ if (!listener)
436240
436505
  return;
436241
- }
436506
+ const runtimeScope = {
436507
+ agent_id: task2.agent_id,
436508
+ conversation_id: conversationId ?? task2.conversation_id ?? "default"
436509
+ };
436242
436510
  const payload = {
436243
436511
  type: "crons_updated",
436244
436512
  timestamp: Date.now(),
436245
436513
  agent_id: task2.agent_id,
436246
- conversation_id: conversationId ?? task2.conversation_id
436514
+ conversation_id: runtimeScope.conversation_id
436247
436515
  };
436248
- try {
436249
- socket.send(JSON.stringify(payload));
436250
- safeEmitWsEvent("send", "protocol", payload);
436251
- } catch (err) {
436252
- console.error(`[Cron] Error sending crons_updated for task ${task2.id}:`, err instanceof Error ? err.message : err);
436253
- }
436516
+ emitProtocolV2Message(socket, listener, payload, runtimeScope, TO_SUBSCRIBERS);
436254
436517
  }
436255
436518
  function refreshTaskCache(state) {
436256
436519
  const mtime = getCronFileMtime();
@@ -436631,13 +436894,14 @@ function stopScheduler() {
436631
436894
  var schedulerState = null, listenerFireContext = null, TICK_INTERVAL_MS = 60000, GC_INTERVAL_MS, LEASE_RETRY_MS = 30000, MAX_LEASE_RETRIES = 3, NEW_CONVERSATION_TARGET = "new";
436632
436895
  var init_scheduler = __esm(async () => {
436633
436896
  init_backend2();
436897
+ init_connection();
436634
436898
  init_runtime6();
436635
- init_transport();
436636
436899
  init_cron_file();
436637
436900
  init_parse_interval();
436638
436901
  init_run_log();
436639
436902
  await __promiseAll([
436640
436903
  init_conversation_runtime(),
436904
+ init_protocol_outbound(),
436641
436905
  init_queue()
436642
436906
  ]);
436643
436907
  GC_INTERVAL_MS = 60 * 60000;
@@ -445600,6 +445864,34 @@ var init_turn_status = __esm(async () => {
445600
445864
  });
445601
445865
 
445602
445866
  // src/websocket/listener/approval.ts
445867
+ function pendingApprovalEntries(runtime) {
445868
+ return [...new Set(runtime.pendingApprovalResolvers.values())];
445869
+ }
445870
+ function removePendingApproval(runtime, pending) {
445871
+ for (const [requestKey, candidate] of runtime.pendingApprovalResolvers) {
445872
+ if (candidate !== pending)
445873
+ continue;
445874
+ runtime.pendingApprovalResolvers.delete(requestKey);
445875
+ }
445876
+ pending.connectionIds.clear();
445877
+ }
445878
+ function addPendingApprovalConnection(runtime, pending, connectionId) {
445879
+ const unownedKey = createConnectionRequestKey(UNOWNED_APPROVAL_CONNECTION_ID, pending.requestId);
445880
+ runtime.pendingApprovalResolvers.delete(unownedKey);
445881
+ const requestKey = createConnectionRequestKey(connectionId, pending.requestId);
445882
+ pending.connectionIds.add(connectionId);
445883
+ runtime.pendingApprovalResolvers.set(requestKey, pending);
445884
+ }
445885
+ function keepPendingApprovalUnowned(runtime, pending) {
445886
+ const requestKey = createConnectionRequestKey(UNOWNED_APPROVAL_CONNECTION_ID, pending.requestId);
445887
+ runtime.pendingApprovalResolvers.set(requestKey, pending);
445888
+ }
445889
+ function hasPendingApprovalRequestId(runtime, requestId) {
445890
+ return pendingApprovalEntries(runtime).some((pending) => pending.requestId === requestId);
445891
+ }
445892
+ function getPendingApprovalRequestIds(runtime) {
445893
+ return new Set(pendingApprovalEntries(runtime).map((pending) => pending.requestId));
445894
+ }
445603
445895
  function rememberPendingApprovalBatchIds(runtime, pendingApprovals, batchId) {
445604
445896
  for (const approval of pendingApprovals) {
445605
445897
  if (approval.toolCallId) {
@@ -445685,17 +445977,20 @@ function validateApprovalResultIds(decisions, approvals) {
445685
445977
  }, null, 2));
445686
445978
  throw new Error("Approval ID mismatch - refusing to send mismatched IDs");
445687
445979
  }
445688
- function resolvePendingApprovalResolver(runtime, response) {
445980
+ function resolvePendingApprovalResolver(runtime, response, connectionId) {
445689
445981
  const requestId = response.request_id;
445690
445982
  if (typeof requestId !== "string" || requestId.length === 0) {
445691
445983
  return false;
445692
445984
  }
445693
- const pending = runtime.pendingApprovalResolvers.get(requestId);
445985
+ const requestKey = connectionId ? createConnectionRequestKey(connectionId, requestId) : [...runtime.pendingApprovalResolvers.entries()].find(([, candidate]) => candidate.requestId === requestId)?.[0];
445986
+ if (!requestKey) {
445987
+ return false;
445988
+ }
445989
+ const pending = runtime.pendingApprovalResolvers.get(requestKey);
445694
445990
  if (!pending) {
445695
445991
  return false;
445696
445992
  }
445697
- runtime.pendingApprovalResolvers.delete(requestId);
445698
- runtime.listener.approvalRuntimeKeyByRequestId.delete(requestId);
445993
+ removePendingApproval(runtime, pending);
445699
445994
  if (runtime.pendingApprovalResolvers.size === 0 && !runtime.isProcessing) {
445700
445995
  setCommandLoopStatus(runtime, "WAITING_ON_INPUT");
445701
445996
  }
@@ -445712,15 +446007,10 @@ function resolvePendingApprovalResolver(runtime, response) {
445712
446007
  return true;
445713
446008
  }
445714
446009
  function rejectPendingApprovalResolvers(runtime, reason) {
445715
- for (const [, pending] of runtime.pendingApprovalResolvers) {
446010
+ for (const pending of pendingApprovalEntries(runtime)) {
445716
446011
  pending.reject(new Error(reason));
445717
446012
  }
445718
446013
  runtime.pendingApprovalResolvers.clear();
445719
- for (const [requestId, runtimeKey] of runtime.listener.approvalRuntimeKeyByRequestId) {
445720
- if (runtimeKey === runtime.key) {
445721
- runtime.listener.approvalRuntimeKeyByRequestId.delete(requestId);
445722
- }
445723
- }
445724
446014
  if (!runtime.isProcessing && !runtime.cancelRequested) {
445725
446015
  setCommandLoopStatus(runtime, "WAITING_ON_INPUT");
445726
446016
  }
@@ -445734,8 +446024,49 @@ function rejectPendingApprovalResolvers(runtime, reason) {
445734
446024
  });
445735
446025
  evictConversationRuntimeIfIdle(runtime);
445736
446026
  }
446027
+ function rejectPendingApprovalResolversForConnection(runtime, connectionId, _reason) {
446028
+ for (const pending of pendingApprovalEntries(runtime)) {
446029
+ if (!pending.connectionIds.delete(connectionId)) {
446030
+ continue;
446031
+ }
446032
+ const requestKey = createConnectionRequestKey(connectionId, pending.requestId);
446033
+ runtime.pendingApprovalResolvers.delete(requestKey);
446034
+ if (pending.connectionIds.size === 0) {
446035
+ keepPendingApprovalUnowned(runtime, pending);
446036
+ }
446037
+ }
446038
+ evictConversationRuntimeIfIdle(runtime);
446039
+ }
446040
+ function replayPendingApprovalRequestsToConnection(runtime, connectionId) {
446041
+ const connection = runtime.listener.connections.get(connectionId);
446042
+ if (!connection?.initialized || !isListenerTransportOpen(connection.writer)) {
446043
+ return;
446044
+ }
446045
+ for (const pending of pendingApprovalEntries(runtime)) {
446046
+ addPendingApprovalConnection(runtime, pending, connectionId);
446047
+ if (!pending.controlRequest)
446048
+ continue;
446049
+ emitProtocolV2Message(connection.writer, runtime, pending.controlRequest, {
446050
+ agent_id: runtime.agentId,
446051
+ conversation_id: runtime.conversationId
446052
+ }, toListenerConnection(connectionId));
446053
+ }
446054
+ }
445737
446055
  function requestApprovalOverWS(runtime, socket, turnLease, requestId, controlRequest) {
445738
- if (!isListenerTransportOpen(socket)) {
446056
+ const scope = {
446057
+ agent_id: runtime.agentId,
446058
+ conversation_id: runtime.conversationId
446059
+ };
446060
+ const subscribers = getSubscribedListenerConnections(runtime.listener, scope);
446061
+ const originConnection = findListenerConnectionByTransport(runtime.listener, socket);
446062
+ const connectionIds = new Set(subscribers.map((subscriber) => subscriber.id));
446063
+ if (connectionIds.size === 0 && originConnection?.initialized && isListenerTransportOpen(originConnection.writer)) {
446064
+ connectionIds.add(originConnection.id);
446065
+ }
446066
+ if (connectionIds.size === 0 && runtime.listener.connections.size === 0 && isListenerTransportOpen(socket)) {
446067
+ connectionIds.add(runtime.listener.connectionId ?? "legacy");
446068
+ }
446069
+ if (connectionIds.size === 0 && !isListenerTransportOpen(socket) && runtime.listener.connections.size === 0) {
445739
446070
  return Promise.reject(new Error("WebSocket not open"));
445740
446071
  }
445741
446072
  const abortSignal = turnLease.signal;
@@ -445745,51 +446076,53 @@ function requestApprovalOverWS(runtime, socket, turnLease, requestId, controlReq
445745
446076
  }
445746
446077
  return new Promise((resolve31, reject) => {
445747
446078
  let settled = false;
446079
+ const pending = {
446080
+ requestId,
446081
+ connectionIds,
446082
+ resolve: (response) => {
446083
+ if (settled) {
446084
+ return;
446085
+ }
446086
+ settled = true;
446087
+ cleanupAbortListener();
446088
+ resolve31(response);
446089
+ },
446090
+ reject: (error54) => {
446091
+ if (settled) {
446092
+ return;
446093
+ }
446094
+ settled = true;
446095
+ cleanupAbortListener();
446096
+ reject(error54);
446097
+ },
446098
+ controlRequest
446099
+ };
445748
446100
  const cleanupAbortListener = () => {
445749
446101
  abortSignal.removeEventListener("abort", handleAbort);
445750
446102
  };
445751
- const wrappedResolve = (response) => {
445752
- if (settled) {
445753
- return;
445754
- }
445755
- settled = true;
445756
- cleanupAbortListener();
445757
- resolve31(response);
445758
- };
445759
- const wrappedReject = (error54) => {
445760
- if (settled) {
445761
- return;
445762
- }
445763
- settled = true;
445764
- cleanupAbortListener();
445765
- reject(error54);
445766
- };
445767
446103
  const handleAbort = () => {
445768
- runtime.pendingApprovalResolvers.delete(requestId);
445769
- runtime.listener.approvalRuntimeKeyByRequestId.delete(requestId);
445770
- wrappedReject(new Error("Cancelled by user"));
446104
+ removePendingApproval(runtime, pending);
446105
+ pending.reject(new Error("Cancelled by user"));
445771
446106
  };
445772
446107
  abortSignal.addEventListener("abort", handleAbort, { once: true });
445773
446108
  if (isInterrupted()) {
445774
446109
  handleAbort();
445775
446110
  return;
445776
446111
  }
445777
- runtime.pendingApprovalResolvers.set(requestId, {
445778
- resolve: wrappedResolve,
445779
- reject: wrappedReject,
445780
- controlRequest
445781
- });
445782
- runtime.listener.approvalRuntimeKeyByRequestId.set(requestId, runtime.key);
446112
+ if (connectionIds.size === 0) {
446113
+ keepPendingApprovalUnowned(runtime, pending);
446114
+ } else {
446115
+ for (const connectionId of connectionIds) {
446116
+ addPendingApprovalConnection(runtime, pending, connectionId);
446117
+ }
446118
+ }
445783
446119
  if (isInterrupted()) {
445784
446120
  handleAbort();
445785
446121
  return;
445786
446122
  }
445787
446123
  runtime.turnLifecycle.recordStopReason(turnLease, "requires_approval");
445788
446124
  setTurnLoopStatus(runtime, turnLease, "WAITING_ON_APPROVAL");
445789
- emitProtocolV2Message(socket, runtime, controlRequest, {
445790
- agent_id: runtime.agentId,
445791
- conversation_id: runtime.conversationId
445792
- });
446125
+ emitProtocolV2Message(socket, runtime, controlRequest, scope, TO_SUBSCRIBERS);
445793
446126
  emitLoopStatusIfOpen(runtime.listener, {
445794
446127
  agent_id: runtime.agentId,
445795
446128
  conversation_id: runtime.conversationId
@@ -445800,7 +446133,9 @@ function requestApprovalOverWS(runtime, socket, turnLease, requestId, controlReq
445800
446133
  });
445801
446134
  });
445802
446135
  }
446136
+ var UNOWNED_APPROVAL_CONNECTION_ID = "__unowned_approval__";
445803
446137
  var init_approval = __esm(async () => {
446138
+ init_connection();
445804
446139
  init_runtime6();
445805
446140
  init_transport();
445806
446141
  await __promiseAll([
@@ -447017,6 +447352,93 @@ var init_agents_conversations = __esm(async () => {
447017
447352
  await init_protocol_inbound();
447018
447353
  });
447019
447354
 
447355
+ // src/websocket/listener/commands/channel-registry-events.ts
447356
+ function broadcastChannelRegistryUpdate(runtime, origin, message) {
447357
+ const payload = JSON.stringify(message);
447358
+ const targets = resolveListenerConnectionTargets({
447359
+ runtime,
447360
+ origin,
447361
+ scope: {},
447362
+ routing: BROADCAST,
447363
+ streamMessage: false
447364
+ });
447365
+ for (const target2 of targets) {
447366
+ if (isListenerTransportOpen(target2.transport)) {
447367
+ try {
447368
+ target2.transport.send(payload);
447369
+ } catch (error54) {
447370
+ trackBoundaryError({
447371
+ context: "listener_channel_registry_broadcast",
447372
+ errorType: "listener_channel_registry_send_failed",
447373
+ error: error54
447374
+ });
447375
+ }
447376
+ }
447377
+ }
447378
+ }
447379
+ function handleChannelRegistryEvent(event2, transport, runtime) {
447380
+ const broadcast = (message) => broadcastChannelRegistryUpdate(runtime, transport, message);
447381
+ if (event2.type === "pairings_updated") {
447382
+ const channelId = event2.channelId;
447383
+ broadcast({
447384
+ type: "channel_pairings_updated",
447385
+ timestamp: Date.now(),
447386
+ channel_id: channelId
447387
+ });
447388
+ broadcast({
447389
+ type: "channels_updated",
447390
+ timestamp: Date.now(),
447391
+ channel_id: channelId
447392
+ });
447393
+ return;
447394
+ }
447395
+ if (event2.type === "targets_updated") {
447396
+ const channelId = event2.channelId;
447397
+ broadcast({
447398
+ type: "channel_targets_updated",
447399
+ timestamp: Date.now(),
447400
+ channel_id: channelId
447401
+ });
447402
+ broadcast({
447403
+ type: "channels_updated",
447404
+ timestamp: Date.now(),
447405
+ channel_id: channelId
447406
+ });
447407
+ return;
447408
+ }
447409
+ if (event2.type === "channel_account_state_updated") {
447410
+ const channelId = event2.channelId;
447411
+ broadcast({
447412
+ type: "channel_accounts_updated",
447413
+ timestamp: Date.now(),
447414
+ channel_id: channelId,
447415
+ account_id: event2.accountId
447416
+ });
447417
+ broadcast({
447418
+ type: "channels_updated",
447419
+ timestamp: Date.now(),
447420
+ channel_id: channelId
447421
+ });
447422
+ return;
447423
+ }
447424
+ const permissionModeState = getOrCreateConversationPermissionModeStateRef(runtime, event2.agentId, event2.conversationId);
447425
+ permissionModeState.mode = event2.defaultPermissionMode;
447426
+ persistPermissionModeMapForRuntime(runtime);
447427
+ const seededWorkingDirectory = seedConversationWorkingDirectory(runtime, event2.agentId, event2.conversationId, runtime.bootWorkingDirectory);
447428
+ if (seededWorkingDirectory) {
447429
+ emitDeviceStatusUpdate(transport, getOrCreateConversationRuntime(runtime, event2.agentId, event2.conversationId));
447430
+ }
447431
+ }
447432
+ var init_channel_registry_events = __esm(async () => {
447433
+ init_error_reporting();
447434
+ init_connection();
447435
+ init_cwd();
447436
+ init_permission_mode();
447437
+ init_runtime6();
447438
+ init_transport();
447439
+ await init_protocol_outbound();
447440
+ });
447441
+
447020
447442
  // src/channels/custom/scaffolding.ts
447021
447443
  import { existsSync as existsSync48, mkdirSync as mkdirSync34, rmSync as rmSync11, writeFileSync as writeFileSync24 } from "node:fs";
447022
447444
  function removeUserPlugin(channelId) {
@@ -447044,7 +447466,6 @@ var init_scaffolding = __esm(() => {
447044
447466
  });
447045
447467
 
447046
447468
  // src/websocket/listener/commands/channels.ts
447047
- import WebSocket4 from "ws";
447048
447469
  function setChannelsServiceLoaderOverride(loader) {
447049
447470
  channelsServiceLoaderOverride = loader;
447050
447471
  }
@@ -447762,52 +448183,13 @@ async function handleChannelsProtocolCommand(parsed, socket, runtime, opts, proc
447762
448183
  }
447763
448184
  return true;
447764
448185
  }
447765
- function handleChannelRegistryEvent(event2, socket, runtime, safeSocketSend) {
447766
- if (event2.type === "pairings_updated") {
447767
- if (socket instanceof WebSocket4) {
447768
- emitChannelPairingsUpdated(socket, safeSocketSend, event2.channelId);
447769
- emitChannelsUpdated(socket, safeSocketSend, event2.channelId);
447770
- }
447771
- return;
447772
- }
447773
- if (event2.type === "targets_updated") {
447774
- if (socket instanceof WebSocket4) {
447775
- emitChannelTargetsUpdated(socket, safeSocketSend, event2.channelId);
447776
- emitChannelsUpdated(socket, safeSocketSend, event2.channelId);
447777
- }
447778
- return;
447779
- }
447780
- if (event2.type === "channel_account_state_updated") {
447781
- if (socket instanceof WebSocket4) {
447782
- emitChannelAccountsUpdated(socket, safeSocketSend, {
447783
- channelId: event2.channelId,
447784
- accountId: event2.accountId
447785
- });
447786
- emitChannelsUpdated(socket, safeSocketSend, event2.channelId);
447787
- }
447788
- return;
447789
- }
447790
- const permissionModeState = getOrCreateConversationPermissionModeStateRef(runtime, event2.agentId, event2.conversationId);
447791
- permissionModeState.mode = event2.defaultPermissionMode;
447792
- persistPermissionModeMapForRuntime(runtime);
447793
- const seededWorkingDirectory = seedConversationWorkingDirectory(runtime, event2.agentId, event2.conversationId, runtime.bootWorkingDirectory);
447794
- if (seededWorkingDirectory) {
447795
- emitDeviceStatusUpdate(socket, getOrCreateConversationRuntime(runtime, event2.agentId, event2.conversationId));
447796
- }
447797
- }
447798
448186
  var channelsServiceLoaderOverride = null;
447799
448187
  var init_channels2 = __esm(async () => {
447800
448188
  init_account_config7();
447801
448189
  init_scaffolding();
447802
448190
  init_plugin_registry();
447803
448191
  init_types6();
447804
- init_cwd();
447805
- init_permission_mode();
447806
- init_runtime6();
447807
- await __promiseAll([
447808
- init_protocol_inbound(),
447809
- init_protocol_outbound()
447810
- ]);
448192
+ await init_protocol_inbound();
447811
448193
  });
447812
448194
 
447813
448195
  // src/websocket/listener/commands/cron.ts
@@ -449626,11 +450008,7 @@ var init_model_toolset = __esm(async () => {
449626
450008
  });
449627
450009
 
449628
450010
  // src/websocket/listener/external-tools.ts
449629
- function isSocketOpen(socket) {
449630
- return socket?.readyState === WEBSOCKET_OPEN;
449631
- }
449632
450011
  function getPendingExternalToolCalls(runtime) {
449633
- runtime.pendingExternalToolCalls ??= new Map;
449634
450012
  return runtime.pendingExternalToolCalls;
449635
450013
  }
449636
450014
  function getRegisteredTools(runtime) {
@@ -449641,25 +450019,35 @@ function getRegisteredTools(runtime) {
449641
450019
  }
449642
450020
  return registeredTools;
449643
450021
  }
450022
+ function getConnectionIdsByRegistrationKey(runtime) {
450023
+ let controllers = connectionIdByRegistrationKey.get(runtime);
450024
+ if (!controllers) {
450025
+ controllers = new Map;
450026
+ connectionIdByRegistrationKey.set(runtime, controllers);
450027
+ }
450028
+ return controllers;
450029
+ }
449644
450030
  function getRuntimeKey(runtime) {
449645
450031
  return `${runtime.agent_id}:${runtime.conversation_id}`;
449646
450032
  }
449647
- function getToolRegistrationKey(runtime, scopeId, toolName) {
450033
+ function getToolRegistrationKey(connectionId, runtime, scopeId, toolName) {
449648
450034
  return JSON.stringify([
449649
450035
  "runtime",
450036
+ connectionId,
449650
450037
  runtime.agent_id,
449651
450038
  runtime.conversation_id,
449652
450039
  scopeId ?? null,
449653
450040
  toolName
449654
450041
  ]);
449655
450042
  }
449656
- function toExternalToolDefinition(tool2, runtime, scopeId) {
450043
+ function toExternalToolDefinition(tool2, connectionId, runtime, scopeId) {
449657
450044
  return {
449658
450045
  name: tool2.name,
450046
+ connectionId,
449659
450047
  ...tool2.label !== undefined ? { label: tool2.label } : {},
449660
450048
  description: tool2.description,
449661
450049
  parameters: tool2.parameters,
449662
- registrationKey: getToolRegistrationKey(runtime, scopeId, tool2.name),
450050
+ registrationKey: getToolRegistrationKey(connectionId, runtime, scopeId, tool2.name),
449663
450051
  ...scopeId !== undefined ? { scopeId } : {},
449664
450052
  runtime: {
449665
450053
  agentId: runtime.agent_id,
@@ -449667,16 +450055,17 @@ function toExternalToolDefinition(tool2, runtime, scopeId) {
449667
450055
  }
449668
450056
  };
449669
450057
  }
449670
- function sendJson(socket, payload) {
449671
- socket.send(JSON.stringify(payload));
449672
- }
449673
450058
  function installExternalToolBridge(runtime) {
449674
450059
  setExternalToolExecutor(async (toolCallId, toolName, input, context3) => {
449675
- const socket = runtime.socket;
449676
- if (!isSocketOpen(socket) || runtime.intentionallyClosed) {
450060
+ const registrationKey = context3?.tool.registrationKey;
450061
+ const connectionId = registrationKey ? getConnectionIdsByRegistrationKey(runtime).get(registrationKey) : undefined;
450062
+ const connection = connectionId ? runtime.connections.get(connectionId) : undefined;
450063
+ if (!connection || !isListenerTransportOpen(connection.writer) || runtime.intentionallyClosed) {
449677
450064
  throw new Error("External tool controller is not connected");
449678
450065
  }
450066
+ const writer = connection.writer;
449679
450067
  const requestId = `external-tool-${crypto.randomUUID()}`;
450068
+ const requestKey = createConnectionRequestKey(connection.id, requestId);
449680
450069
  const toolRuntime = context3?.tool.runtime;
449681
450070
  const requestRuntime = toolRuntime?.agentId && toolRuntime.conversationId ? {
449682
450071
  agent_id: toolRuntime.agentId,
@@ -449693,10 +450082,11 @@ function installExternalToolBridge(runtime) {
449693
450082
  };
449694
450083
  const result = await new Promise((resolve31, reject) => {
449695
450084
  const timeout = setTimeout(() => {
449696
- getPendingExternalToolCalls(runtime).delete(requestId);
450085
+ getPendingExternalToolCalls(runtime).delete(requestKey);
449697
450086
  reject(new Error(`External tool call timed out: ${toolName}`));
449698
450087
  }, EXTERNAL_TOOL_CALL_TIMEOUT_MS);
449699
- getPendingExternalToolCalls(runtime).set(requestId, {
450088
+ getPendingExternalToolCalls(runtime).set(requestKey, {
450089
+ connectionId: connection.id,
449700
450090
  resolve: (response) => {
449701
450091
  resolve31({
449702
450092
  content: [...response.content],
@@ -449707,36 +450097,49 @@ function installExternalToolBridge(runtime) {
449707
450097
  timeout
449708
450098
  });
449709
450099
  try {
449710
- sendJson(socket, request);
450100
+ writer.send(JSON.stringify(request));
449711
450101
  } catch (error54) {
449712
450102
  clearTimeout(timeout);
449713
- getPendingExternalToolCalls(runtime).delete(requestId);
450103
+ getPendingExternalToolCalls(runtime).delete(requestKey);
449714
450104
  reject(error54 instanceof Error ? error54 : new Error(String(error54)));
449715
450105
  }
449716
450106
  });
449717
450107
  return result;
449718
450108
  });
449719
450109
  }
449720
- function registerRuntimeExternalTools(runtime, runtimeScope, groups = []) {
450110
+ function registerRuntimeExternalTools(runtime, connectionId, runtimeScope, groups) {
450111
+ const resolvedGroups = groups ?? [];
449721
450112
  const registeredTools = getRegisteredTools(runtime);
449722
- const runtimeKey = getRuntimeKey(runtimeScope);
450113
+ const runtimeKey = createConnectionRequestKey(connectionId, getRuntimeKey(runtimeScope));
449723
450114
  const previousTools = registeredTools.get(runtimeKey) ?? [];
449724
450115
  unregisterExternalTools(previousTools);
449725
- const tools = groups.flatMap((group) => group.tools.map((tool2) => toExternalToolDefinition(tool2, runtimeScope, group.scope_id)));
450116
+ const tools = resolvedGroups.flatMap((group) => group.tools.map((tool2) => toExternalToolDefinition(tool2, connectionId, runtimeScope, group.scope_id)));
450117
+ const controllers = getConnectionIdsByRegistrationKey(runtime);
450118
+ for (const tool2 of previousTools) {
450119
+ if (tool2.registrationKey) {
450120
+ controllers.delete(tool2.registrationKey);
450121
+ }
450122
+ }
449726
450123
  if (tools.length > 0) {
449727
450124
  registerExternalTools(tools);
450125
+ for (const tool2 of tools) {
450126
+ if (tool2.registrationKey) {
450127
+ controllers.set(tool2.registrationKey, connectionId);
450128
+ }
450129
+ }
449728
450130
  registeredTools.set(runtimeKey, tools);
449729
450131
  } else {
449730
450132
  registeredTools.delete(runtimeKey);
449731
450133
  }
449732
450134
  }
449733
- function handleExternalToolCallResponseCommand(runtime, command) {
449734
- const pending = getPendingExternalToolCalls(runtime).get(command.request_id);
450135
+ function handleExternalToolCallResponseCommand(runtime, connectionId, command) {
450136
+ const requestKey = createConnectionRequestKey(connectionId, command.request_id);
450137
+ const pending = getPendingExternalToolCalls(runtime).get(requestKey);
449735
450138
  if (!pending) {
449736
450139
  return false;
449737
450140
  }
449738
450141
  clearTimeout(pending.timeout);
449739
- getPendingExternalToolCalls(runtime).delete(command.request_id);
450142
+ getPendingExternalToolCalls(runtime).delete(requestKey);
449740
450143
  if (command.error !== undefined) {
449741
450144
  pending.reject(new Error(command.error));
449742
450145
  return true;
@@ -449748,6 +450151,35 @@ function handleExternalToolCallResponseCommand(runtime, command) {
449748
450151
  pending.resolve(command.result);
449749
450152
  return true;
449750
450153
  }
450154
+ function rejectPendingExternalToolCallsForConnection(runtime, connectionId, reason) {
450155
+ const pendingExternalToolCalls = getPendingExternalToolCalls(runtime);
450156
+ for (const [requestKey, pending] of pendingExternalToolCalls) {
450157
+ if (pending.connectionId !== connectionId) {
450158
+ continue;
450159
+ }
450160
+ clearTimeout(pending.timeout);
450161
+ pendingExternalToolCalls.delete(requestKey);
450162
+ pending.reject(new Error(reason));
450163
+ }
450164
+ const registeredTools = registeredToolsByRuntime.get(runtime);
450165
+ if (!registeredTools) {
450166
+ return;
450167
+ }
450168
+ const controllers = getConnectionIdsByRegistrationKey(runtime);
450169
+ for (const [registrationScope, tools] of registeredTools) {
450170
+ const ownedByConnection = tools.some((tool2) => tool2.registrationKey !== undefined && controllers.get(tool2.registrationKey) === connectionId);
450171
+ if (!ownedByConnection) {
450172
+ continue;
450173
+ }
450174
+ unregisterExternalTools(tools);
450175
+ registeredTools.delete(registrationScope);
450176
+ for (const tool2 of tools) {
450177
+ if (tool2.registrationKey) {
450178
+ controllers.delete(tool2.registrationKey);
450179
+ }
450180
+ }
450181
+ }
450182
+ }
449751
450183
  function rejectPendingExternalToolCalls(runtime, reason) {
449752
450184
  const pendingExternalToolCalls = getPendingExternalToolCalls(runtime);
449753
450185
  for (const [requestId, pending] of pendingExternalToolCalls) {
@@ -449762,12 +450194,16 @@ function rejectPendingExternalToolCalls(runtime, reason) {
449762
450194
  }
449763
450195
  registeredToolsByRuntime.delete(runtime);
449764
450196
  }
450197
+ connectionIdByRegistrationKey.delete(runtime);
449765
450198
  }
449766
- var EXTERNAL_TOOL_CALL_TIMEOUT_MS, WEBSOCKET_OPEN = 1, registeredToolsByRuntime;
450199
+ var EXTERNAL_TOOL_CALL_TIMEOUT_MS, registeredToolsByRuntime, connectionIdByRegistrationKey;
449767
450200
  var init_external_tools = __esm(async () => {
450201
+ init_connection();
450202
+ init_transport();
449768
450203
  await init_manager4();
449769
450204
  EXTERNAL_TOOL_CALL_TIMEOUT_MS = 5 * 60 * 1000;
449770
450205
  registeredToolsByRuntime = new WeakMap;
450206
+ connectionIdByRegistrationKey = new WeakMap;
449771
450207
  });
449772
450208
 
449773
450209
  // src/websocket/listener/commands/runtime-start.ts
@@ -449915,9 +450351,18 @@ async function handleRuntimeStartCommand(parsed, context3) {
449915
450351
  agent2 = await resolveRuntimeStartAgent(parsed, created);
449916
450352
  conversation = await resolveRuntimeStartConversation(parsed, agent2, created);
449917
450353
  runtimeScope = buildRuntimeScope(agent2, conversation);
450354
+ const { connectionId } = context3;
450355
+ const assertConnectionOpen = () => {
450356
+ if (context3.runtime.connections.size > 0 && (!context3.runtime.connections.has(connectionId) || context3.runtime.connections.get(connectionId)?.cancellation.signal.aborted)) {
450357
+ throw new Error("App-server connection closed during runtime start");
450358
+ }
450359
+ };
450360
+ assertConnectionOpen();
449918
450361
  const scopedRuntime = context3.getOrCreateScopedRuntime(context3.runtime, runtimeScope.agent_id, runtimeScope.conversation_id);
449919
450362
  await applyRuntimeStartState(parsed, context3, runtimeScope, scopedRuntime);
449920
- registerRuntimeExternalTools(context3.runtime, runtimeScope, parsed.external_tools ?? []);
450363
+ assertConnectionOpen();
450364
+ subscribeListenerConnection(context3.runtime, connectionId, runtimeScope);
450365
+ registerRuntimeExternalTools(context3.runtime, connectionId, runtimeScope, parsed.external_tools ?? []);
449921
450366
  const sent = sendRuntimeStartResponse(context3, parsed, {
449922
450367
  success: true,
449923
450368
  runtime: runtimeScope,
@@ -449959,6 +450404,7 @@ var init_runtime_start = __esm(async () => {
449959
450404
  init_backend2();
449960
450405
  init_mode();
449961
450406
  init_settings_manager();
450407
+ init_connection();
449962
450408
  init_cwd();
449963
450409
  init_permission_mode();
449964
450410
  await __promiseAll([
@@ -457404,16 +457850,19 @@ async function handleApprovalStop(params) {
457404
457850
  return interruptTermination();
457405
457851
  }
457406
457852
  const onFileWrite = (filePath, content) => {
457407
- if (runtime.turnLifecycle.isCurrent(turnLease) && isListenerTransportOpen(socket)) {
457408
- socket.send(JSON.stringify({
457409
- type: "file_ops",
457410
- path: filePath,
457411
- cg_entries: [],
457412
- ops: [],
457413
- source: "agent",
457414
- document_content: content
457415
- }));
457416
- }
457853
+ if (!runtime.turnLifecycle.isCurrent(turnLease))
457854
+ return;
457855
+ emitProtocolV2Message(socket, runtime, {
457856
+ type: "file_ops",
457857
+ path: filePath,
457858
+ cg_entries: [],
457859
+ ops: [],
457860
+ source: "agent",
457861
+ document_content: content
457862
+ }, {
457863
+ agent_id: agentId,
457864
+ conversation_id: conversationId
457865
+ }, TO_SUBSCRIBERS);
457417
457866
  };
457418
457867
  let executionResults;
457419
457868
  try {
@@ -457559,9 +458008,9 @@ var init_turn_approval = __esm(async () => {
457559
458008
  init_diff_preview();
457560
458009
  init_format_denial();
457561
458010
  init_interactive_policy();
458011
+ init_connection();
457562
458012
  init_secrets_sync();
457563
458013
  init_skill_injection();
457564
- init_transport();
457565
458014
  init_turn_input_state();
457566
458015
  await __promiseAll([
457567
458016
  init_approval_execution(),
@@ -458995,6 +459444,7 @@ async function prepareListenerTurn(params) {
458995
459444
  }
458996
459445
  const modAdapters = await ensureListenerModAdaptersForAgent(runtime.listener, agentId);
458997
459446
  const preparedToolContext = await prepareToolExecutionContextForScope({
459447
+ connectionId,
458998
459448
  agentId,
458999
459449
  conversationId,
459000
459450
  clientToolAllowlist: msg.clientToolAllowlist,
@@ -459078,6 +459528,9 @@ async function handleIncomingMessageInner(msg, socket, runtime, onStatusChange,
459078
459528
  origin: "message",
459079
459529
  workingDirectory: turnWorkingDirectory
459080
459530
  });
459531
+ if (connectionId) {
459532
+ runtime.activeConnectionId = connectionId;
459533
+ }
459081
459534
  if (!runtime.turnLifecycle.isCurrent(turnLease)) {
459082
459535
  throw new Error("Cannot continue a turn with a stale lifecycle lease");
459083
459536
  }
@@ -459682,6 +460135,9 @@ async function handleIncomingMessageInner(msg, socket, runtime, onStatusChange,
459682
460135
  });
459683
460136
  }
459684
460137
  richDraftStreamer?.dispose();
460138
+ if (runtime.activeConnectionId === connectionId) {
460139
+ runtime.activeConnectionId = null;
460140
+ }
459685
460141
  try {
459686
460142
  if (finalizedByThisInvocation) {
459687
460143
  await runListenerTurnCleanup({
@@ -459787,15 +460243,19 @@ function handleModeChange(msg, socket, runtime, scope) {
459787
460243
  }
459788
460244
  }
459789
460245
  }
459790
- function resolveRuntimeForApprovalRequest(listener, requestId) {
460246
+ function resolveRuntimeForApprovalRequest(listener, scope, requestId, connectionId) {
459791
460247
  if (!requestId) {
459792
460248
  return null;
459793
460249
  }
459794
- const runtimeKey = listener.approvalRuntimeKeyByRequestId.get(requestId);
459795
- if (!runtimeKey) {
460250
+ const runtime = getConversationRuntime(listener, scope.agent_id, scope.conversation_id);
460251
+ if (!runtime) {
459796
460252
  return null;
459797
460253
  }
459798
- return listener.conversationRuntimes.get(runtimeKey) ?? null;
460254
+ if (connectionId) {
460255
+ const requestKey = createConnectionRequestKey(connectionId, requestId);
460256
+ return runtime.pendingApprovalResolvers.has(requestKey) ? runtime : null;
460257
+ }
460258
+ return hasPendingApprovalRequestId(runtime, requestId) ? runtime : null;
459799
460259
  }
459800
460260
  async function handleApprovalResponseInput(listener, params, deps = {
459801
460261
  resolveRuntimeForApprovalRequest,
@@ -459804,8 +460264,8 @@ async function handleApprovalResponseInput(listener, params, deps = {
459804
460264
  resolveRecoveredApprovalResponse,
459805
460265
  scheduleQueuePump
459806
460266
  }) {
459807
- const approvalRuntime = deps.resolveRuntimeForApprovalRequest(listener, params.response.request_id);
459808
- if (approvalRuntime && deps.resolvePendingApprovalResolver(approvalRuntime, params.response)) {
460267
+ const approvalRuntime = deps.resolveRuntimeForApprovalRequest(listener, params.runtime, params.response.request_id, params.connectionId);
460268
+ if (approvalRuntime && deps.resolvePendingApprovalResolver(approvalRuntime, params.response, params.connectionId)) {
459809
460269
  deps.scheduleQueuePump(approvalRuntime, params.socket, params.opts, params.processQueuedTurn);
459810
460270
  return true;
459811
460271
  }
@@ -460044,6 +460504,7 @@ var init_control_inputs = __esm(async () => {
460044
460504
  init_mode();
460045
460505
  init_error_reporting();
460046
460506
  init_debug();
460507
+ init_connection();
460047
460508
  init_cwd();
460048
460509
  init_permission_mode();
460049
460510
  init_runtime6();
@@ -460062,16 +460523,30 @@ var init_control_inputs = __esm(async () => {
460062
460523
  });
460063
460524
 
460064
460525
  // src/websocket/terminal-handler.ts
460526
+ import { existsSync as existsSync51 } from "node:fs";
460065
460527
  import * as os6 from "node:os";
460066
- import WebSocket5 from "ws";
460528
+ import WebSocket4 from "ws";
460529
+ function getTerminalKey(connectionId, terminalId) {
460530
+ return JSON.stringify([connectionId, terminalId]);
460531
+ }
460067
460532
  function getDefaultShell() {
460068
460533
  if (os6.platform() === "win32") {
460069
460534
  return process.env.COMSPEC || "cmd.exe";
460070
460535
  }
460071
- return process.env.SHELL || "/bin/zsh";
460536
+ const candidates2 = [
460537
+ process.env.SHELL,
460538
+ os6.platform() === "darwin" ? "/bin/zsh" : "/bin/bash",
460539
+ "/bin/bash"
460540
+ ];
460541
+ for (const candidate of candidates2) {
460542
+ if (candidate && existsSync51(candidate)) {
460543
+ return candidate;
460544
+ }
460545
+ }
460546
+ return "/bin/sh";
460072
460547
  }
460073
460548
  function sendTerminalMessage(socket, message) {
460074
- if (socket.readyState === WebSocket5.OPEN) {
460549
+ if (socket.readyState === WebSocket4.OPEN) {
460075
460550
  socket.send(JSON.stringify(message));
460076
460551
  }
460077
460552
  }
@@ -460097,7 +460572,8 @@ function makeOutputBatcher(onFlush) {
460097
460572
  }
460098
460573
  };
460099
460574
  }
460100
- function spawnBun(shell2, cwd2, cols, rows, terminal_id, socket) {
460575
+ function spawnBun(shell2, cwd2, cols, rows, terminal_id, connectionId, socket) {
460576
+ const terminalKey = getTerminalKey(connectionId, terminal_id);
460101
460577
  const handleData = makeOutputBatcher((data) => sendTerminalMessage(socket, { type: "terminal_output", terminal_id, data }));
460102
460578
  const proc2 = Bun.spawn([shell2], {
460103
460579
  cwd: cwd2,
@@ -460113,9 +460589,9 @@ function spawnBun(shell2, cwd2, cols, rows, terminal_id, socket) {
460113
460589
  throw new Error("Bun.spawn terminal object missing — API unavailable");
460114
460590
  }
460115
460591
  proc2.exited.then((exitCode) => {
460116
- const current = terminals.get(terminal_id);
460592
+ const current = terminals.get(terminalKey);
460117
460593
  if (current && current.pid === proc2.pid) {
460118
- terminals.delete(terminal_id);
460594
+ terminals.delete(terminalKey);
460119
460595
  sendTerminalMessage(socket, {
460120
460596
  type: "terminal_exited",
460121
460597
  terminal_id,
@@ -460144,10 +460620,12 @@ function spawnBun(shell2, cwd2, cols, rows, terminal_id, socket) {
460144
460620
  },
460145
460621
  pid: proc2.pid,
460146
460622
  terminalId: terminal_id,
460623
+ connectionId,
460147
460624
  spawnedAt: Date.now()
460148
460625
  };
460149
460626
  }
460150
- function spawnNodePty(shell2, cwd2, cols, rows, terminal_id, socket) {
460627
+ function spawnNodePty(shell2, cwd2, cols, rows, terminal_id, connectionId, socket) {
460628
+ const terminalKey = getTerminalKey(connectionId, terminal_id);
460151
460629
  const pty = __require("node-pty");
460152
460630
  const handleData = makeOutputBatcher((data) => sendTerminalMessage(socket, { type: "terminal_output", terminal_id, data }));
460153
460631
  const ptyProcess = pty.spawn(shell2, [], {
@@ -460163,9 +460641,9 @@ function spawnNodePty(shell2, cwd2, cols, rows, terminal_id, socket) {
460163
460641
  });
460164
460642
  ptyProcess.onData(handleData);
460165
460643
  ptyProcess.onExit(({ exitCode }) => {
460166
- const current = terminals.get(terminal_id);
460644
+ const current = terminals.get(terminalKey);
460167
460645
  if (current && current.pid === ptyProcess.pid) {
460168
- terminals.delete(terminal_id);
460646
+ terminals.delete(terminalKey);
460169
460647
  sendTerminalMessage(socket, {
460170
460648
  type: "terminal_exited",
460171
460649
  terminal_id,
@@ -460191,12 +460669,14 @@ function spawnNodePty(shell2, cwd2, cols, rows, terminal_id, socket) {
460191
460669
  },
460192
460670
  pid: ptyProcess.pid,
460193
460671
  terminalId: terminal_id,
460672
+ connectionId,
460194
460673
  spawnedAt: Date.now()
460195
460674
  };
460196
460675
  }
460197
- function handleTerminalSpawn(msg, socket, cwd2) {
460676
+ function handleTerminalSpawn(msg, socket, cwd2, connectionId = "legacy") {
460198
460677
  const { terminal_id, cols, rows } = msg;
460199
- const existing = terminals.get(terminal_id);
460678
+ const terminalKey = getTerminalKey(connectionId, terminal_id);
460679
+ const existing = terminals.get(terminalKey);
460200
460680
  if (existing && Date.now() - existing.spawnedAt < 2000) {
460201
460681
  let alive = true;
460202
460682
  try {
@@ -460213,14 +460693,14 @@ function handleTerminalSpawn(msg, socket, cwd2) {
460213
460693
  });
460214
460694
  return;
460215
460695
  }
460216
- terminals.delete(terminal_id);
460696
+ terminals.delete(terminalKey);
460217
460697
  }
460218
- killTerminal(terminal_id);
460698
+ killTerminal(terminal_id, connectionId);
460219
460699
  const shell2 = getDefaultShell();
460220
460700
  console.log(`[Terminal] Spawning PTY (${IS_BUN ? "bun" : "node-pty"}): shell=${shell2}, cwd=${cwd2}, cols=${cols}, rows=${rows}`);
460221
460701
  try {
460222
- const session = IS_BUN ? spawnBun(shell2, cwd2, cols, rows, terminal_id, socket) : spawnNodePty(shell2, cwd2, cols, rows, terminal_id, socket);
460223
- terminals.set(terminal_id, session);
460702
+ const session = IS_BUN ? spawnBun(shell2, cwd2, cols, rows, terminal_id, connectionId, socket) : spawnNodePty(shell2, cwd2, cols, rows, terminal_id, connectionId, socket);
460703
+ terminals.set(terminalKey, session);
460224
460704
  console.log(`[Terminal] Session stored for terminal_id=${terminal_id}, pid=${session.pid}`);
460225
460705
  sendTerminalMessage(socket, {
460226
460706
  type: "terminal_spawned",
@@ -460237,31 +460717,39 @@ function handleTerminalSpawn(msg, socket, cwd2) {
460237
460717
  });
460238
460718
  }
460239
460719
  }
460240
- function handleTerminalInput(msg) {
460241
- terminals.get(msg.terminal_id)?.write(msg.data);
460720
+ function handleTerminalInput(msg, connectionId = "legacy") {
460721
+ terminals.get(getTerminalKey(connectionId, msg.terminal_id))?.write(msg.data);
460242
460722
  }
460243
- function handleTerminalResize(msg) {
460244
- terminals.get(msg.terminal_id)?.resize(msg.cols, msg.rows);
460723
+ function handleTerminalResize(msg, connectionId = "legacy") {
460724
+ terminals.get(getTerminalKey(connectionId, msg.terminal_id))?.resize(msg.cols, msg.rows);
460245
460725
  }
460246
- function handleTerminalKill(msg) {
460247
- const session = terminals.get(msg.terminal_id);
460726
+ function handleTerminalKill(msg, connectionId = "legacy") {
460727
+ const session = terminals.get(getTerminalKey(connectionId, msg.terminal_id));
460248
460728
  if (session && Date.now() - session.spawnedAt < 2000) {
460249
460729
  console.log(`[Terminal] Ignoring kill for recently spawned session (age=${Date.now() - session.spawnedAt}ms)`);
460250
460730
  return;
460251
460731
  }
460252
- killTerminal(msg.terminal_id);
460732
+ killTerminal(msg.terminal_id, connectionId);
460253
460733
  }
460254
- function killTerminal(terminalId) {
460255
- const session = terminals.get(terminalId);
460734
+ function killTerminal(terminalId, connectionId) {
460735
+ const terminalKey = getTerminalKey(connectionId, terminalId);
460736
+ const session = terminals.get(terminalKey);
460256
460737
  if (session) {
460257
460738
  console.log(`[Terminal] killTerminal: terminalId=${terminalId}, pid=${session.pid}`);
460258
460739
  session.kill();
460259
- terminals.delete(terminalId);
460740
+ terminals.delete(terminalKey);
460741
+ }
460742
+ }
460743
+ function killListenerConnectionTerminals(connectionId) {
460744
+ for (const session of [...terminals.values()]) {
460745
+ if (session.connectionId === connectionId) {
460746
+ killTerminal(session.terminalId, connectionId);
460747
+ }
460260
460748
  }
460261
460749
  }
460262
460750
  function killAllTerminals() {
460263
- for (const [id2] of terminals) {
460264
- killTerminal(id2);
460751
+ for (const session of [...terminals.values()]) {
460752
+ killTerminal(session.terminalId, session.connectionId);
460265
460753
  }
460266
460754
  }
460267
460755
  var IS_BUN, FLUSH_INTERVAL_MS = 16, MAX_BUFFER_BYTES, terminals;
@@ -460489,7 +460977,7 @@ __export(exports_custom, {
460489
460977
  GLOBAL_COMMANDS_DIR: () => GLOBAL_COMMANDS_DIR,
460490
460978
  COMMANDS_DIR: () => COMMANDS_DIR
460491
460979
  });
460492
- import { existsSync as existsSync51 } from "node:fs";
460980
+ import { existsSync as existsSync52 } from "node:fs";
460493
460981
  import { readdir as readdir10, readFile as readFile20 } from "node:fs/promises";
460494
460982
  import { basename as basename25, dirname as dirname28, join as join65 } from "node:path";
460495
460983
  async function getCustomCommands() {
@@ -460523,7 +461011,7 @@ async function discoverCustomCommands(projectPath = join65(process.cwd(), COMMAN
460523
461011
  return result;
460524
461012
  }
460525
461013
  async function discoverFromDirectory(dirPath, source2) {
460526
- if (!existsSync51(dirPath)) {
461014
+ if (!existsSync52(dirPath)) {
460527
461015
  return [];
460528
461016
  }
460529
461017
  const commands = [];
@@ -461488,6 +461976,144 @@ var init_commands2 = __esm(async () => {
461488
461976
  ]);
461489
461977
  });
461490
461978
 
461979
+ // src/websocket/listener/connection-lifecycle.ts
461980
+ import WebSocket5 from "ws";
461981
+ function createConnectionTurnProcessor(runtime) {
461982
+ return async (queuedTurn, dequeuedBatch) => {
461983
+ const scopedRuntime = getOrCreateScopedRuntime(runtime, queuedTurn.agentId, queuedTurn.conversationId);
461984
+ if (!queuedTurn.connectionId) {
461985
+ await handleIncomingMessage(queuedTurn, getOrCreateProcessTransport(runtime), scopedRuntime, undefined, undefined, dequeuedBatch.batchId);
461986
+ return;
461987
+ }
461988
+ const connection = runtime.connections.get(queuedTurn.connectionId);
461989
+ if (!connection || connection.cancellation.signal.aborted) {
461990
+ return;
461991
+ }
461992
+ await handleIncomingMessage(queuedTurn, connection.writer, scopedRuntime, connection.options.onStatusChange, connection.id, dequeuedBatch.batchId);
461993
+ };
461994
+ }
461995
+ function cleanupListenerConnection(runtime, connectionId) {
461996
+ for (const conversationRuntime of runtime.conversationRuntimes.values()) {
461997
+ if (conversationRuntime.activeConnectionId === connectionId) {
461998
+ const hasEligibleFailover = getSubscribedListenerConnections(runtime, {
461999
+ agent_id: conversationRuntime.agentId,
462000
+ conversation_id: conversationRuntime.conversationId
462001
+ }).some((connection) => connection.id !== connectionId);
462002
+ if (hasEligibleFailover) {
462003
+ conversationRuntime.activeConnectionId = null;
462004
+ } else {
462005
+ conversationRuntime.turnLifecycle.requestCancellation();
462006
+ }
462007
+ }
462008
+ for (const [
462009
+ itemId,
462010
+ queuedMessage
462011
+ ] of conversationRuntime.queuedMessagesByItemId) {
462012
+ if (queuedMessage.connectionId === connectionId) {
462013
+ conversationRuntime.queueRuntime.removeItem(itemId);
462014
+ conversationRuntime.queuedMessagesByItemId.delete(itemId);
462015
+ }
462016
+ }
462017
+ rejectPendingApprovalResolversForConnection(conversationRuntime, connectionId, "Listener connection closed");
462018
+ }
462019
+ rejectPendingExternalToolCallsForConnection(runtime, connectionId, "Listener connection closed");
462020
+ killListenerConnectionTerminals(connectionId);
462021
+ const closedSubscriptionKeys = [
462022
+ ...runtime.connections.get(connectionId)?.subscriptions ?? []
462023
+ ];
462024
+ closeListenerConnection(runtime, connectionId);
462025
+ for (const runtimeKey of closedSubscriptionKeys) {
462026
+ if (!runtime.connectionIdsByRuntimeKey.has(runtimeKey)) {
462027
+ const scopedRuntime = runtime.conversationRuntimes.get(runtimeKey);
462028
+ if (scopedRuntime) {
462029
+ evictConversationRuntimeIfIdle(scopedRuntime);
462030
+ }
462031
+ }
462032
+ }
462033
+ }
462034
+ function closeListenerRuntimeConnections(runtime, suppressCallbacks) {
462035
+ const socketsToClose = new Set;
462036
+ const collectSocket = (transport) => {
462037
+ if (transport && getListenerTransportKind(transport) === "websocket") {
462038
+ socketsToClose.add(transport);
462039
+ }
462040
+ };
462041
+ if (runtime.socket) {
462042
+ socketsToClose.add(runtime.socket);
462043
+ }
462044
+ if (runtime.streamSocket) {
462045
+ socketsToClose.add(runtime.streamSocket);
462046
+ }
462047
+ for (const connection of runtime.connections.values()) {
462048
+ collectSocket(connection.writer);
462049
+ collectSocket(connection.streamWriter);
462050
+ }
462051
+ for (const connectionId of [...runtime.connections.keys()]) {
462052
+ closeListenerConnection(runtime, connectionId);
462053
+ }
462054
+ runtime.connectionIdsByRuntimeKey.clear();
462055
+ runtime.socket = null;
462056
+ runtime.transport = null;
462057
+ runtime.streamSocket = null;
462058
+ runtime.streamTransport = null;
462059
+ for (const socket of socketsToClose) {
462060
+ if (suppressCallbacks) {
462061
+ socket.removeAllListeners();
462062
+ }
462063
+ if (socket.readyState === WebSocket5.OPEN || socket.readyState === WebSocket5.CONNECTING) {
462064
+ socket.close();
462065
+ }
462066
+ }
462067
+ }
462068
+ var init_connection_lifecycle = __esm(async () => {
462069
+ init_terminal_handler();
462070
+ init_connection();
462071
+ init_runtime6();
462072
+ init_transport();
462073
+ await __promiseAll([
462074
+ init_approval(),
462075
+ init_conversation_runtime(),
462076
+ init_external_tools(),
462077
+ init_turn()
462078
+ ]);
462079
+ });
462080
+
462081
+ // src/websocket/listener/connection-state-sync.ts
462082
+ function emitInitialConnectionState(runtime, transport, connectionId, options3 = {}) {
462083
+ if (options3.emitInitialState === false)
462084
+ return;
462085
+ const routing = toListenerConnection(connectionId);
462086
+ if (runtime.conversationRuntimes.size === 0) {
462087
+ emitLoopStatusUpdate(transport, runtime, undefined, routing);
462088
+ return;
462089
+ }
462090
+ for (const conversationRuntime of runtime.conversationRuntimes.values()) {
462091
+ const scope = {
462092
+ agent_id: conversationRuntime.agentId,
462093
+ conversation_id: conversationRuntime.conversationId
462094
+ };
462095
+ emitDeviceStatusUpdate(transport, conversationRuntime, scope, routing);
462096
+ emitLoopStatusUpdate(transport, conversationRuntime, scope, routing);
462097
+ }
462098
+ }
462099
+ function replaySubscribedConnectionState(listener, transport, runtime, scope, forceDeviceStatus) {
462100
+ const connection = findListenerConnectionByTransport(listener, transport);
462101
+ if (connection) {
462102
+ replayPendingApprovalRequestsToConnection(runtime, connection.id);
462103
+ }
462104
+ emitStateSync(transport, listener, scope, {
462105
+ forceDeviceStatus,
462106
+ ...connection ? { routing: toListenerConnection(connection.id) } : {}
462107
+ });
462108
+ }
462109
+ var init_connection_state_sync = __esm(async () => {
462110
+ init_connection();
462111
+ await __promiseAll([
462112
+ init_approval(),
462113
+ init_protocol_outbound()
462114
+ ]);
462115
+ });
462116
+
461491
462117
  // src/websocket/listener/grep-in-files.ts
461492
462118
  import { execFile as execFile14 } from "node:child_process";
461493
462119
  import { createRequire as createRequire6 } from "node:module";
@@ -462356,7 +462982,7 @@ function buildAppServerInfoResponse(command, options3) {
462356
462982
  conversation_management: true,
462357
462983
  memory_management: true,
462358
462984
  runtime_start: true,
462359
- split_channels: true
462985
+ split_channels: false
462360
462986
  }
462361
462987
  };
462362
462988
  }
@@ -463701,6 +464327,7 @@ function createListenerMessageHandler(params) {
463701
464327
  const {
463702
464328
  runtime,
463703
464329
  socket,
464330
+ connectionId: explicitConnectionId,
463704
464331
  opts,
463705
464332
  processQueuedTurn,
463706
464333
  fileCommandSession,
@@ -463717,6 +464344,7 @@ function createListenerMessageHandler(params) {
463717
464344
  wireChannelIngress,
463718
464345
  processIncomingMessage = handleIncomingMessage
463719
464346
  } = params;
464347
+ const connectionId = explicitConnectionId ?? opts.connectionId;
463720
464348
  return async (data) => {
463721
464349
  const raw2 = data.toString();
463722
464350
  let parsedScope = null;
@@ -463746,6 +464374,9 @@ function createListenerMessageHandler(params) {
463746
464374
  return;
463747
464375
  }
463748
464376
  console.log(`[Listen V2] Received ${summarizeV2Command(parsed)}`);
464377
+ if (parsedScope) {
464378
+ subscribeListenerConnection(runtime, connectionId, parsedScope);
464379
+ }
463749
464380
  if (parsed.type === "__invalid_input") {
463750
464381
  emitLoopErrorNotice(socket, runtime, {
463751
464382
  message: parsed.reason,
@@ -463762,6 +464393,7 @@ function createListenerMessageHandler(params) {
463762
464393
  }
463763
464394
  if (handleRuntimeStartProtocolCommand(parsed, {
463764
464395
  socket,
464396
+ connectionId,
463765
464397
  runtime,
463766
464398
  safeSocketSend,
463767
464399
  runDetachedListenerTask,
@@ -463771,7 +464403,7 @@ function createListenerMessageHandler(params) {
463771
464403
  return;
463772
464404
  }
463773
464405
  if (parsed.type === "external_tool_call_response") {
463774
- handleExternalToolCallResponseCommand(runtime, parsed);
464406
+ handleExternalToolCallResponseCommand(runtime, connectionId, parsed);
463775
464407
  return;
463776
464408
  }
463777
464409
  if (parsed.type === "sync") {
@@ -463825,6 +464457,7 @@ function createListenerMessageHandler(params) {
463825
464457
  if (await handleApprovalResponseInput2(runtime, {
463826
464458
  runtime: parsed.runtime,
463827
464459
  response: parsed.payload,
464460
+ connectionId,
463828
464461
  socket,
463829
464462
  opts: {
463830
464463
  onStatusChange: opts.onStatusChange,
@@ -463849,6 +464482,7 @@ function createListenerMessageHandler(params) {
463849
464482
  }
463850
464483
  const incoming = {
463851
464484
  type: "message",
464485
+ connectionId,
463852
464486
  agentId: parsed.runtime.agent_id,
463853
464487
  conversationId: parsed.runtime.conversation_id,
463854
464488
  clientToolAllowlist: inputPayload.client_tool_allowlist,
@@ -463897,6 +464531,7 @@ function createListenerMessageHandler(params) {
463897
464531
  if (parsed.type === "change_device_state") {
463898
464532
  await handleChangeDeviceStateInput2(runtime, {
463899
464533
  command: parsed,
464534
+ connectionId,
463900
464535
  socket,
463901
464536
  opts: {
463902
464537
  onStatusChange: opts.onStatusChange,
@@ -463923,6 +464558,7 @@ function createListenerMessageHandler(params) {
463923
464558
  try {
463924
464559
  const aborted2 = await handleAbortMessageInput2(runtime, {
463925
464560
  command: parsed,
464561
+ connectionId,
463926
464562
  socket,
463927
464563
  opts: {
463928
464564
  onStatusChange: opts.onStatusChange,
@@ -464092,19 +464728,19 @@ function createListenerMessageHandler(params) {
464092
464728
  return;
464093
464729
  }
464094
464730
  if (parsed.type === "terminal_spawn") {
464095
- handleTerminalSpawn(parsed, socket, parsed.cwd ?? getBootWorkingDirectory(runtime));
464731
+ handleTerminalSpawn(parsed, socket, parsed.cwd ?? getBootWorkingDirectory(runtime), connectionId);
464096
464732
  return;
464097
464733
  }
464098
464734
  if (parsed.type === "terminal_input") {
464099
- handleTerminalInput(parsed);
464735
+ handleTerminalInput(parsed, connectionId);
464100
464736
  return;
464101
464737
  }
464102
464738
  if (parsed.type === "terminal_resize") {
464103
- handleTerminalResize(parsed);
464739
+ handleTerminalResize(parsed, connectionId);
464104
464740
  return;
464105
464741
  }
464106
464742
  if (parsed.type === "terminal_kill") {
464107
- handleTerminalKill(parsed);
464743
+ handleTerminalKill(parsed, connectionId);
464108
464744
  }
464109
464745
  } catch (error54) {
464110
464746
  trackListenerError4("listener_message_handler_failed", error54, "listener_message_handler");
@@ -464131,6 +464767,7 @@ var init_message_router = __esm(async () => {
464131
464767
  init_debug();
464132
464768
  init_terminal_handler();
464133
464769
  init_app_server_info();
464770
+ init_connection();
464134
464771
  init_cwd();
464135
464772
  init_runtime6();
464136
464773
  await __promiseAll([
@@ -464157,6 +464794,97 @@ var init_message_router = __esm(async () => {
464157
464794
  ]);
464158
464795
  });
464159
464796
 
464797
+ // src/websocket/listener/process-services.ts
464798
+ function installProcessEventRouting(params) {
464799
+ const { runtime, processTransport, opts, processQueuedTurn } = params;
464800
+ runtime._unsubscribeSubagentState?.();
464801
+ runtime._unsubscribeSubagentState = subscribe(() => {
464802
+ if (runtime.conversationRuntimes.size === 0) {
464803
+ emitSubagentStateIfOpen(runtime);
464804
+ return;
464805
+ }
464806
+ for (const conversationRuntime of runtime.conversationRuntimes.values()) {
464807
+ emitSubagentStateIfOpen(runtime, {
464808
+ agent_id: conversationRuntime.agentId,
464809
+ conversation_id: conversationRuntime.conversationId
464810
+ });
464811
+ }
464812
+ });
464813
+ runtime._unsubscribeSubagentStreamEvents?.();
464814
+ runtime._unsubscribeSubagentStreamEvents = subscribeToStreamEvents((subagentId, event2) => {
464815
+ if (!isListenerTransportOpen(processTransport))
464816
+ return;
464817
+ const subagent = getSubagents().find((entry) => entry.id === subagentId);
464818
+ if (subagent?.silent === true)
464819
+ return;
464820
+ emitStreamDelta(processTransport, runtime, event2, subagent?.parentAgentId ? {
464821
+ agent_id: subagent.parentAgentId,
464822
+ conversation_id: subagent.parentConversationId ?? "default"
464823
+ } : undefined, subagentId);
464824
+ });
464825
+ setMessageQueueAdder((queuedMessage) => {
464826
+ if (!queuedMessage.agentId || !queuedMessage.conversationId)
464827
+ return;
464828
+ const targetRuntime = getOrCreateScopedRuntime(runtime, queuedMessage.agentId, queuedMessage.conversationId);
464829
+ if (!targetRuntime?.queueRuntime)
464830
+ return;
464831
+ targetRuntime.queueRuntime.enqueue({
464832
+ kind: "task_notification",
464833
+ source: "task_notification",
464834
+ text: queuedMessage.text,
464835
+ agentId: queuedMessage.agentId ?? targetRuntime.agentId ?? undefined,
464836
+ conversationId: queuedMessage.conversationId ?? targetRuntime.conversationId
464837
+ });
464838
+ scheduleQueuePump(targetRuntime, processTransport, opts, processQueuedTurn);
464839
+ });
464840
+ }
464841
+ function clearProcessServices(runtime) {
464842
+ runtime._unsubscribeSubagentState?.();
464843
+ runtime._unsubscribeSubagentState = undefined;
464844
+ runtime._unsubscribeSubagentStreamEvents?.();
464845
+ runtime._unsubscribeSubagentStreamEvents = undefined;
464846
+ setMessageQueueAdder(null);
464847
+ stopScheduler();
464848
+ clearRuntimeTimers(runtime);
464849
+ runtime.processServicesStarted = false;
464850
+ }
464851
+ function invalidateProcessServices(runtime) {
464852
+ runtime.processServicesGeneration += 1;
464853
+ clearProcessServices(runtime);
464854
+ }
464855
+ async function waitForProcessServicesSlot(runtime, connectionId) {
464856
+ const initiatingConnection = runtime.connections.get(connectionId);
464857
+ const canInitiate = () => !initiatingConnection || runtime.connections.get(connectionId) === initiatingConnection && !initiatingConnection.cancellation.signal.aborted;
464858
+ while (runtime.processServicesReady) {
464859
+ const pending = runtime.processServicesReady;
464860
+ const pendingGeneration = runtime.processServicesReadyGeneration;
464861
+ try {
464862
+ await pending;
464863
+ } catch (error54) {
464864
+ if (pendingGeneration === runtime.processServicesGeneration)
464865
+ throw error54;
464866
+ }
464867
+ if (runtime.processServicesStarted)
464868
+ return false;
464869
+ if (runtime !== getActiveRuntime() || runtime.intentionallyClosed || !canInitiate()) {
464870
+ return false;
464871
+ }
464872
+ }
464873
+ return canInitiate();
464874
+ }
464875
+ var init_process_services = __esm(async () => {
464876
+ init_subagent_state();
464877
+ init_message_queue_bridge();
464878
+ init_runtime6();
464879
+ init_transport();
464880
+ await __promiseAll([
464881
+ init_scheduler(),
464882
+ init_conversation_runtime(),
464883
+ init_protocol_outbound(),
464884
+ init_queue()
464885
+ ]);
464886
+ });
464887
+
464160
464888
  // src/websocket/listener/lifecycle.ts
464161
464889
  import WebSocket6 from "ws";
464162
464890
  function trackListenerError4(errorType, error54, context3) {
@@ -464219,9 +464947,7 @@ async function replaySyncStateForRuntime(listenerRuntime, socket, scope, opts) {
464219
464947
  }
464220
464948
  }
464221
464949
  }
464222
- emitStateSync(socket, listenerRuntime, scope, {
464223
- forceDeviceStatus: opts?.forceDeviceStatus
464224
- });
464950
+ replaySubscribedConnectionState(listenerRuntime, socket, syncScopedRuntime, scope, opts?.forceDeviceStatus);
464225
464951
  (opts?.scheduleWarmupsAfterSync ?? scheduleListenerWarmupsAfterSync)(listenerRuntime, scope);
464226
464952
  }
464227
464953
  async function recoverPendingChannelControlRequests(listener, opts) {
@@ -464253,7 +464979,7 @@ async function recoverPendingChannelControlRequests(listener, opts) {
464253
464979
  }
464254
464980
  for (const { scope, entries } of entriesByScope.values()) {
464255
464981
  const runtime = getOrCreateScopedRuntime(listener, scope.agent_id, scope.conversation_id);
464256
- const livePendingRequestIds = new Set(runtime.pendingApprovalResolvers.keys());
464982
+ const livePendingRequestIds = getPendingApprovalRequestIds(runtime);
464257
464983
  const shouldRecoverFromBackend = entries.some((entry) => !livePendingRequestIds.has(entry.event.requestId));
464258
464984
  if (shouldRecoverFromBackend) {
464259
464985
  try {
@@ -464378,7 +465104,7 @@ async function wireChannelIngress(listener, socket, opts, processQueuedTurn) {
464378
465104
  scheduleQueuePump(conversationRuntime, socket, opts, processQueuedTurn);
464379
465105
  });
464380
465106
  registry2.setEventHandler((event2) => {
464381
- handleChannelRegistryEvent(event2, socket, listener, safeSocketSend);
465107
+ handleChannelRegistryEvent(event2, socket, listener);
464382
465108
  });
464383
465109
  await recoverPendingChannelControlRequests(listener);
464384
465110
  registry2.setApprovalResponseHandler(async ({ runtime, response }) => handleApprovalResponseInput(listener, {
@@ -464643,6 +465369,14 @@ function createRuntime() {
464643
465369
  hasSuccessfulConnection: false,
464644
465370
  everConnected: false,
464645
465371
  sessionId: `listen-${crypto.randomUUID()}`,
465372
+ nextConnectionOrdinal: 0,
465373
+ connections: new Map,
465374
+ connectionIdsByRuntimeKey: new Map,
465375
+ processTransport: null,
465376
+ processServicesStarted: false,
465377
+ processServicesGeneration: 0,
465378
+ processServicesReady: null,
465379
+ processServicesReadyGeneration: null,
464646
465380
  eventSeqCounter: 0,
464647
465381
  queueEmitScheduled: false,
464648
465382
  pendingQueueEmitScope: undefined,
@@ -464660,7 +465394,6 @@ function createRuntime() {
464660
465394
  connectionId: null,
464661
465395
  connectionName: null,
464662
465396
  conversationRuntimes: new Map,
464663
- approvalRuntimeKeyByRequestId: new Map,
464664
465397
  memfsSyncedAgents: new Map,
464665
465398
  secretsHydrationByAgent: new Map,
464666
465399
  secretsHydrationFreshnessByAgent: new Map,
@@ -464674,9 +465407,8 @@ function stopRuntime(runtime, suppressCallbacks) {
464674
465407
  notifyStreamObserversRuntimeStopped(runtime);
464675
465408
  disposeListenerModAdapter(runtime);
464676
465409
  rejectPendingExternalToolCalls(runtime, "Listener runtime stopped");
464677
- setMessageQueueAdder(null);
464678
465410
  runtime.intentionallyClosed = true;
464679
- clearRuntimeTimers(runtime);
465411
+ invalidateProcessServices(runtime);
464680
465412
  for (const conversationRuntime of runtime.conversationRuntimes.values()) {
464681
465413
  rejectPendingApprovalResolvers(conversationRuntime, "Listener runtime stopped");
464682
465414
  clearConversationRuntimeState(conversationRuntime);
@@ -464686,7 +465418,9 @@ function stopRuntime(runtime, suppressCallbacks) {
464686
465418
  }
464687
465419
  }
464688
465420
  runtime.conversationRuntimes.clear();
464689
- runtime.approvalRuntimeKeyByRequestId.clear();
465421
+ closeListenerRuntimeConnections(runtime, suppressCallbacks);
465422
+ runtime.processServicesReady = null;
465423
+ runtime.processServicesReadyGeneration = null;
464690
465424
  clearListenerWarmState(runtime);
464691
465425
  runtime.reminderStateByConversation.clear();
464692
465426
  runtime.skillSourcesByConversation.clear();
@@ -464694,30 +465428,6 @@ function stopRuntime(runtime, suppressCallbacks) {
464694
465428
  runtime.systemPromptRecompileByConversation.clear();
464695
465429
  runtime.queuedSystemPromptRecompileByConversation.clear();
464696
465430
  stopAllWorktreeWatchers(runtime);
464697
- if (!runtime.socket) {
464698
- if (runtime.streamSocket && (runtime.streamSocket.readyState === WebSocket6.OPEN || runtime.streamSocket.readyState === WebSocket6.CONNECTING)) {
464699
- runtime.streamSocket.close();
464700
- }
464701
- runtime.streamSocket = null;
464702
- runtime.streamTransport = null;
464703
- runtime.transport = null;
464704
- return;
464705
- }
464706
- const socket = runtime.socket;
464707
- runtime.socket = null;
464708
- runtime.transport = null;
464709
- const streamSocket = runtime.streamSocket;
464710
- runtime.streamSocket = null;
464711
- runtime.streamTransport = null;
464712
- if (suppressCallbacks) {
464713
- socket.removeAllListeners();
464714
- }
464715
- if (socket.readyState === WebSocket6.OPEN || socket.readyState === WebSocket6.CONNECTING) {
464716
- socket.close();
464717
- }
464718
- if (streamSocket && (streamSocket.readyState === WebSocket6.OPEN || streamSocket.readyState === WebSocket6.CONNECTING)) {
464719
- streamSocket.close();
464720
- }
464721
465431
  }
464722
465432
  async function startConnectedListenerRuntime(runtime, transport, opts, processQueuedTurn, options3 = {}) {
464723
465433
  if (runtime !== getActiveRuntime() || runtime.intentionallyClosed) {
@@ -464726,103 +465436,88 @@ async function startConnectedListenerRuntime(runtime, transport, opts, processQu
464726
465436
  const shouldStartHeartbeat = options3.startHeartbeat !== false;
464727
465437
  const cronSchedulerDisabledByEnv = process.env.LETTA_DISABLE_CRON_SCHEDULER === "1";
464728
465438
  const shouldStartCronScheduler = options3.startCronScheduler !== false && !cronSchedulerDisabledByEnv;
464729
- runtime.transport = transport;
464730
- runtime.streamTransport = options3.streamTransport ?? null;
465439
+ markListenerConnectionInitialized(runtime, opts.connectionId);
464731
465440
  safeEmitWsEvent("recv", "lifecycle", {
464732
465441
  type: getListenerTransportKind(transport) === "websocket" ? "_ws_open" : "_local_open"
464733
465442
  });
464734
465443
  runtime.hasSuccessfulConnection = true;
464735
465444
  runtime.everConnected = true;
464736
465445
  opts.onConnected(opts.connectionId);
464737
- if (runtime.conversationRuntimes.size === 0) {
464738
- emitLoopStatusUpdate(transport, runtime);
464739
- } else {
464740
- for (const conversationRuntime of runtime.conversationRuntimes.values()) {
464741
- const scope = {
464742
- agent_id: conversationRuntime.agentId,
464743
- conversation_id: conversationRuntime.conversationId
464744
- };
464745
- emitDeviceStatusUpdate(transport, conversationRuntime, scope);
464746
- emitLoopStatusUpdate(transport, conversationRuntime, scope);
464747
- }
465446
+ emitInitialConnectionState(runtime, transport, opts.connectionId, options3);
465447
+ if (runtime.processServicesStarted) {
465448
+ return;
464748
465449
  }
464749
- runtime._unsubscribeSubagentState?.();
464750
- runtime._unsubscribeSubagentState = subscribe(() => {
464751
- if (runtime.conversationRuntimes.size === 0) {
464752
- emitSubagentStateIfOpen(runtime);
464753
- return;
465450
+ if (!await waitForProcessServicesSlot(runtime, opts.connectionId))
465451
+ return;
465452
+ const processServicesGeneration = runtime.processServicesGeneration + 1;
465453
+ runtime.processServicesGeneration = processServicesGeneration;
465454
+ const processServicesReady = (async () => {
465455
+ const processTransport = getOrCreateProcessTransport(runtime);
465456
+ installProcessEventRouting({
465457
+ runtime,
465458
+ processTransport,
465459
+ opts,
465460
+ processQueuedTurn
465461
+ });
465462
+ if (shouldStartHeartbeat) {
465463
+ runtime.lastPongAt = Date.now();
465464
+ runtime.heartbeatInterval = setInterval(() => {
465465
+ if (getListenerTransportKind(transport) === "websocket" && isListenerPongStale(runtime.lastPongAt, Date.now(), LISTENER_PONG_TIMEOUT_MS)) {
465466
+ trackListenerError4("listener_pong_timeout", new Error(`No relay pong within ${LISTENER_PONG_TIMEOUT_MS}ms; terminating half-open socket to force reconnect`), "listener_heartbeat");
465467
+ runtime.socket?.terminate();
465468
+ return;
465469
+ }
465470
+ safeTransportSend(transport, { type: "ping" }, "listener_ping_send_failed", "listener_heartbeat");
465471
+ }, LISTENER_HEARTBEAT_INTERVAL_MS);
464754
465472
  }
464755
- for (const conversationRuntime of runtime.conversationRuntimes.values()) {
464756
- emitSubagentStateIfOpen(runtime, {
464757
- agent_id: conversationRuntime.agentId,
464758
- conversation_id: conversationRuntime.conversationId
464759
- });
465473
+ if (shouldStartCronScheduler) {
465474
+ startScheduler(processTransport, opts, processQueuedTurn);
464760
465475
  }
464761
- });
464762
- runtime._unsubscribeSubagentStreamEvents?.();
464763
- runtime._unsubscribeSubagentStreamEvents = subscribeToStreamEvents((subagentId, event2) => {
464764
- if (!isListenerTransportOpen(transport))
464765
- return;
464766
- const subagent = getSubagents().find((entry) => entry.id === subagentId);
464767
- if (subagent?.silent === true) {
464768
- return;
465476
+ await (options3.wireChannelIngress ?? wireChannelIngress)(runtime, processTransport, opts, processQueuedTurn);
465477
+ if (runtime.processServicesGeneration === processServicesGeneration) {
465478
+ runtime.processServicesStarted = true;
464769
465479
  }
464770
- emitStreamDelta(transport, runtime, event2, subagent?.parentAgentId ? {
464771
- agent_id: subagent.parentAgentId,
464772
- conversation_id: subagent.parentConversationId ?? "default"
464773
- } : undefined, subagentId);
464774
- });
464775
- setMessageQueueAdder((queuedMessage) => {
464776
- const targetRuntime = queuedMessage.agentId && queuedMessage.conversationId ? getOrCreateScopedRuntime(runtime, queuedMessage.agentId, queuedMessage.conversationId) : findFallbackRuntime(runtime);
464777
- if (!targetRuntime?.queueRuntime) {
465480
+ })();
465481
+ runtime.processServicesReady = processServicesReady;
465482
+ runtime.processServicesReadyGeneration = processServicesGeneration;
465483
+ try {
465484
+ await processServicesReady;
465485
+ } catch (error54) {
465486
+ if (runtime.processServicesGeneration !== processServicesGeneration)
464778
465487
  return;
465488
+ clearProcessServices(runtime);
465489
+ throw error54;
465490
+ } finally {
465491
+ if (runtime.processServicesReady === processServicesReady) {
465492
+ runtime.processServicesReady = null;
465493
+ runtime.processServicesReadyGeneration = null;
464779
465494
  }
464780
- targetRuntime.queueRuntime.enqueue({
464781
- kind: "task_notification",
464782
- source: "task_notification",
464783
- text: queuedMessage.text,
464784
- agentId: queuedMessage.agentId ?? targetRuntime.agentId ?? undefined,
464785
- conversationId: queuedMessage.conversationId ?? targetRuntime.conversationId
464786
- });
464787
- scheduleQueuePump(targetRuntime, transport, opts, processQueuedTurn);
464788
- });
464789
- if (shouldStartHeartbeat) {
464790
- runtime.lastPongAt = Date.now();
464791
- runtime.heartbeatInterval = setInterval(() => {
464792
- if (getListenerTransportKind(transport) === "websocket" && isListenerPongStale(runtime.lastPongAt, Date.now(), LISTENER_PONG_TIMEOUT_MS)) {
464793
- trackListenerError4("listener_pong_timeout", new Error(`No relay pong within ${LISTENER_PONG_TIMEOUT_MS}ms; terminating half-open socket to force reconnect`), "listener_heartbeat");
464794
- runtime.socket?.terminate();
464795
- return;
464796
- }
464797
- safeTransportSend(transport, { type: "ping" }, "listener_ping_send_failed", "listener_heartbeat");
464798
- }, LISTENER_HEARTBEAT_INTERVAL_MS);
464799
465495
  }
464800
- if (shouldStartCronScheduler) {
464801
- startScheduler(transport, opts, processQueuedTurn);
464802
- }
464803
- await wireChannelIngress(runtime, transport, opts, processQueuedTurn);
464804
465496
  }
464805
465497
  async function attachOpenListenerSocket(runtime, socket, opts, options3 = {}) {
464806
465498
  if (runtime !== getActiveRuntime() || runtime.intentionallyClosed) {
464807
465499
  return;
464808
465500
  }
464809
465501
  const streamSocket = options3.streamSocket ?? null;
465502
+ const connection = openListenerConnection({
465503
+ runtime,
465504
+ connectionId: opts.connectionId,
465505
+ writer: socket,
465506
+ streamWriter: streamSocket,
465507
+ options: opts
465508
+ });
464810
465509
  const fileCommandSession = createFileCommandSession({
464811
465510
  socket,
464812
465511
  safeSocketSend,
464813
465512
  runDetachedListenerTask
464814
465513
  });
464815
- runtime.socket = socket;
464816
- runtime.streamSocket = streamSocket;
464817
465514
  installExternalToolBridge(runtime);
464818
465515
  const transport = socket;
464819
- const processQueuedTurn = async (queuedTurn, dequeuedBatch) => {
464820
- const scopedRuntime = getOrCreateScopedRuntime(runtime, queuedTurn.agentId, queuedTurn.conversationId);
464821
- await handleIncomingMessage(queuedTurn, transport, scopedRuntime, opts.onStatusChange, opts.connectionId, dequeuedBatch.batchId);
464822
- };
465516
+ const processQueuedTurn = createConnectionTurnProcessor(runtime);
464823
465517
  const handleMessage = createListenerMessageHandler({
464824
465518
  runtime,
464825
465519
  socket,
465520
+ connectionId: opts.connectionId,
464826
465521
  opts,
464827
465522
  processQueuedTurn,
464828
465523
  fileCommandSession,
@@ -464841,6 +465536,9 @@ async function attachOpenListenerSocket(runtime, socket, opts, options3 = {}) {
464841
465536
  socket.on("message", (data) => {
464842
465537
  (async () => {
464843
465538
  await options3.startupReady;
465539
+ if (connection.cancellation.signal.aborted || runtime.connections.get(opts.connectionId) !== connection) {
465540
+ return;
465541
+ }
464844
465542
  await handleMessage(data);
464845
465543
  })().catch((error54) => {
464846
465544
  trackListenerError4("listener_message_handler_failed", error54, "listener_message_handler");
@@ -464858,12 +465556,7 @@ async function attachOpenListenerSocket(runtime, socket, opts, options3 = {}) {
464858
465556
  reason: reasonText
464859
465557
  });
464860
465558
  fileCommandSession.dispose();
464861
- stopScheduler();
464862
- getChannelRegistry()?.pause();
464863
- stopRuntime(runtime, true);
464864
- if (getActiveRuntime() === runtime) {
464865
- setActiveRuntime(null);
464866
- }
465559
+ cleanupListenerConnection(runtime, opts.connectionId);
464867
465560
  opts.onDisconnected();
464868
465561
  });
464869
465562
  socket.on("error", (error54) => {
@@ -464891,11 +465584,15 @@ async function attachOpenListenerSocket(runtime, socket, opts, options3 = {}) {
464891
465584
  });
464892
465585
  }
464893
465586
  await options3.startupReady;
465587
+ if (connection.cancellation.signal.aborted || runtime.connections.get(opts.connectionId) !== connection) {
465588
+ return;
465589
+ }
464894
465590
  const streamTransport = streamSocket?.readyState === WebSocket6.OPEN ? streamSocket : null;
464895
465591
  await startConnectedListenerRuntime(runtime, transport, opts, processQueuedTurn, {
464896
465592
  startHeartbeat: options3.startHeartbeat ?? false,
464897
465593
  startCronScheduler: options3.startCronScheduler ?? true,
464898
- streamTransport
465594
+ streamTransport,
465595
+ emitInitialState: false
464899
465596
  });
464900
465597
  }
464901
465598
  async function startListenerClient(opts) {
@@ -464929,10 +465626,18 @@ async function startLocalChannelListener(opts) {
464929
465626
  await reloadListenerModAdapter(runtime);
464930
465627
  await loadTools();
464931
465628
  const transport = new LocalListenerTransport;
464932
- const processQueuedTurn = async (queuedTurn, dequeuedBatch) => {
464933
- const scopedRuntime = getOrCreateScopedRuntime(runtime, queuedTurn.agentId, queuedTurn.conversationId);
464934
- await handleIncomingMessage(queuedTurn, transport, scopedRuntime, opts.onStatusChange, opts.connectionId, dequeuedBatch.batchId);
465629
+ const connectionOptions = {
465630
+ ...opts,
465631
+ wsUrl: "local://listener",
465632
+ onDisconnected: () => {}
464935
465633
  };
465634
+ openListenerConnection({
465635
+ runtime,
465636
+ connectionId: opts.connectionId,
465637
+ writer: transport,
465638
+ options: connectionOptions
465639
+ });
465640
+ const processQueuedTurn = createConnectionTurnProcessor(runtime);
464936
465641
  await startConnectedListenerRuntime(runtime, transport, opts, processQueuedTurn, { startHeartbeat: false, startCronScheduler: true });
464937
465642
  } catch (error54) {
464938
465643
  stopRuntime(runtime, true);
@@ -465010,15 +465715,19 @@ async function connectWithRetry(runtime, opts, attempt = 0, startTime = Date.now
465010
465715
  runtime.socket = socket;
465011
465716
  runtime.streamSocket = streamSocket;
465012
465717
  const transport = socket;
465013
- const processQueuedTurn = async (queuedTurn, dequeuedBatch) => {
465014
- const scopedRuntime = getOrCreateScopedRuntime(runtime, queuedTurn.agentId, queuedTurn.conversationId);
465015
- await handleIncomingMessage(queuedTurn, transport, scopedRuntime, opts.onStatusChange, opts.connectionId, dequeuedBatch.batchId);
465016
- };
465718
+ const processQueuedTurn = createConnectionTurnProcessor(runtime);
465017
465719
  socket.on("open", async () => {
465018
465720
  let streamTransport = null;
465019
465721
  if (streamSocket) {
465020
465722
  streamTransport = await waitForStreamSocketOpen(streamSocket, runtime);
465021
465723
  }
465724
+ openListenerConnection({
465725
+ runtime,
465726
+ connectionId: opts.connectionId,
465727
+ writer: socket,
465728
+ streamWriter: streamTransport,
465729
+ options: opts
465730
+ });
465022
465731
  await startConnectedListenerRuntime(runtime, transport, opts, processQueuedTurn, {
465023
465732
  startHeartbeat: true,
465024
465733
  startCronScheduler: true,
@@ -465028,6 +465737,7 @@ async function connectWithRetry(runtime, opts, attempt = 0, startTime = Date.now
465028
465737
  socket.on("message", createListenerMessageHandler({
465029
465738
  runtime,
465030
465739
  socket,
465740
+ connectionId: opts.connectionId,
465031
465741
  opts,
465032
465742
  processQueuedTurn,
465033
465743
  fileCommandSession,
@@ -465053,12 +465763,11 @@ async function connectWithRetry(runtime, opts, attempt = 0, startTime = Date.now
465053
465763
  reason: reason.toString()
465054
465764
  });
465055
465765
  fileCommandSession.dispose();
465056
- stopScheduler();
465766
+ invalidateProcessServices(runtime);
465057
465767
  const channelRegistry = getChannelRegistry();
465058
465768
  if (channelRegistry) {
465059
465769
  channelRegistry.pause();
465060
465770
  }
465061
- setMessageQueueAdder(null);
465062
465771
  for (const conversationRuntime of runtime.conversationRuntimes.values()) {
465063
465772
  conversationRuntime.queuedMessagesByItemId.clear();
465064
465773
  if (conversationRuntime.queueRuntime) {
@@ -465068,12 +465777,8 @@ async function connectWithRetry(runtime, opts, attempt = 0, startTime = Date.now
465068
465777
  if (isDebugEnabled()) {
465069
465778
  console.log(`[Listen] WebSocket disconnected (code: ${code2}, reason: ${reason.toString()})`);
465070
465779
  }
465071
- clearRuntimeTimers(runtime);
465780
+ suspendListenerConnection(runtime, opts.connectionId);
465072
465781
  killAllTerminals();
465073
- runtime._unsubscribeSubagentState?.();
465074
- runtime._unsubscribeSubagentState = undefined;
465075
- runtime._unsubscribeSubagentStreamEvents?.();
465076
- runtime._unsubscribeSubagentStreamEvents = undefined;
465077
465782
  clearListenerWarmState(runtime);
465078
465783
  if (streamSocket) {
465079
465784
  streamSocket.removeAllListeners();
@@ -465150,7 +465855,6 @@ function stopListenerClient() {
465150
465855
  stopRuntime(runtime, true);
465151
465856
  }
465152
465857
  var init_lifecycle = __esm(async () => {
465153
- init_subagent_state();
465154
465858
  init_commands();
465155
465859
  init_progress_builder();
465156
465860
  init_registry();
@@ -465160,10 +465864,10 @@ var init_lifecycle = __esm(async () => {
465160
465864
  init_telemetry();
465161
465865
  init_error_reporting();
465162
465866
  init_debug();
465163
- init_message_queue_bridge();
465164
465867
  init_terminal_handler();
465165
465868
  init_auth();
465166
465869
  init_channel_turn_session();
465870
+ init_connection();
465167
465871
  init_constants3();
465168
465872
  init_cwd();
465169
465873
  init_permission_mode();
@@ -465174,18 +465878,20 @@ var init_lifecycle = __esm(async () => {
465174
465878
  init_manager4(),
465175
465879
  init_approval(),
465176
465880
  init_commands2(),
465177
- init_channels2(),
465881
+ init_channel_registry_events(),
465178
465882
  init_model_toolset(),
465883
+ init_connection_lifecycle(),
465884
+ init_connection_state_sync(),
465179
465885
  init_control_inputs(),
465180
465886
  init_conversation_runtime(),
465181
465887
  init_external_tools(),
465182
465888
  init_file_commands(),
465183
465889
  init_message_router(),
465184
465890
  init_mod_adapter2(),
465891
+ init_process_services(),
465185
465892
  init_protocol_outbound(),
465186
465893
  init_queue(),
465187
465894
  init_recovery(),
465188
- init_turn(),
465189
465895
  init_turn_events(),
465190
465896
  init_warmup(),
465191
465897
  init_worktree_watcher()
@@ -465273,6 +465979,60 @@ function createLegacyTestRuntime() {
465273
465979
  listener.sessionId = value;
465274
465980
  }
465275
465981
  },
465982
+ nextConnectionOrdinal: {
465983
+ get: () => listener.nextConnectionOrdinal,
465984
+ set: (value) => {
465985
+ listener.nextConnectionOrdinal = value;
465986
+ }
465987
+ },
465988
+ connections: {
465989
+ get: () => listener.connections,
465990
+ set: (value) => {
465991
+ listener.connections = value;
465992
+ }
465993
+ },
465994
+ connectionIdsByRuntimeKey: {
465995
+ get: () => listener.connectionIdsByRuntimeKey,
465996
+ set: (value) => {
465997
+ listener.connectionIdsByRuntimeKey = value;
465998
+ }
465999
+ },
466000
+ processTransport: {
466001
+ get: () => listener.processTransport,
466002
+ set: (value) => {
466003
+ listener.processTransport = value;
466004
+ }
466005
+ },
466006
+ processServicesStarted: {
466007
+ get: () => listener.processServicesStarted,
466008
+ set: (value) => {
466009
+ listener.processServicesStarted = value;
466010
+ }
466011
+ },
466012
+ processServicesGeneration: {
466013
+ get: () => listener.processServicesGeneration,
466014
+ set: (value) => {
466015
+ listener.processServicesGeneration = value;
466016
+ }
466017
+ },
466018
+ processServicesReady: {
466019
+ get: () => listener.processServicesReady,
466020
+ set: (value) => {
466021
+ listener.processServicesReady = value;
466022
+ }
466023
+ },
466024
+ processServicesReadyGeneration: {
466025
+ get: () => listener.processServicesReadyGeneration,
466026
+ set: (value) => {
466027
+ listener.processServicesReadyGeneration = value;
466028
+ }
466029
+ },
466030
+ pendingExternalToolCalls: {
466031
+ get: () => listener.pendingExternalToolCalls,
466032
+ set: (value) => {
466033
+ listener.pendingExternalToolCalls = value;
466034
+ }
466035
+ },
465276
466036
  eventSeqCounter: {
465277
466037
  get: () => listener.eventSeqCounter,
465278
466038
  set: (value) => {
@@ -465345,12 +466105,6 @@ function createLegacyTestRuntime() {
465345
466105
  listener.conversationRuntimes = value;
465346
466106
  }
465347
466107
  },
465348
- approvalRuntimeKeyByRequestId: {
465349
- get: () => listener.approvalRuntimeKeyByRequestId,
465350
- set: (value) => {
465351
- listener.approvalRuntimeKeyByRequestId = value;
465352
- }
465353
- },
465354
466108
  memfsSyncedAgents: {
465355
466109
  get: () => listener.memfsSyncedAgents,
465356
466110
  set: (value) => {
@@ -465423,6 +466177,7 @@ var init_client7 = __esm(async () => {
465423
466177
  await __promiseAll([
465424
466178
  init_approval(),
465425
466179
  init_agents_conversations(),
466180
+ init_channel_registry_events(),
465426
466181
  init_channels2(),
465427
466182
  init_cron3(),
465428
466183
  init_memory6(),
@@ -465499,7 +466254,7 @@ var init_client7 = __esm(async () => {
465499
466254
  handleListMemoryCommand: (parsed, socket, overrides) => handleListMemoryCommand(parsed, socket, safeSocketSend, overrides),
465500
466255
  isDetachedChannelsCommand,
465501
466256
  handleChannelsProtocolCommand: (parsed, socket, runtime, opts, processQueuedTurn) => handleChannelsProtocolCommand(parsed, socket, runtime, opts, processQueuedTurn, runDetachedListenerTask, wireChannelIngress, safeSocketSend),
465502
- handleChannelRegistryEvent: (event2, socket, runtime) => handleChannelRegistryEvent(event2, socket, runtime, safeSocketSend),
466257
+ handleChannelRegistryEvent: (event2, socket, runtime) => handleChannelRegistryEvent(event2, socket, runtime),
465503
466258
  handleAgentConversationManagementCommand: (parsed, socket) => handleAgentConversationManagementCommand(parsed, socket, safeSocketSend),
465504
466259
  handleAgentConversationManagementProtocolCommand: (parsed, socket) => handleAgentConversationManagementProtocolCommand(parsed, {
465505
466260
  socket,
@@ -465508,6 +466263,7 @@ var init_client7 = __esm(async () => {
465508
466263
  }),
465509
466264
  handleRuntimeStartCommand: (parsed, socket, runtime) => handleRuntimeStartCommand(parsed, {
465510
466265
  socket,
466266
+ connectionId: runtime.connectionId ?? "test-connection",
465511
466267
  runtime,
465512
466268
  safeSocketSend,
465513
466269
  runDetachedListenerTask,
@@ -465519,6 +466275,7 @@ var init_client7 = __esm(async () => {
465519
466275
  }),
465520
466276
  handleRuntimeStartProtocolCommand: (parsed, socket, runtime) => handleRuntimeStartProtocolCommand(parsed, {
465521
466277
  socket,
466278
+ connectionId: runtime.connectionId ?? "test-connection",
465522
466279
  runtime,
465523
466280
  safeSocketSend,
465524
466281
  runDetachedListenerTask,
@@ -466121,7 +466878,7 @@ var init_listen = __esm(async () => {
466121
466878
  import { randomUUID as randomUUID26 } from "node:crypto";
466122
466879
  import {
466123
466880
  copyFileSync as copyFileSync3,
466124
- existsSync as existsSync52,
466881
+ existsSync as existsSync53,
466125
466882
  mkdirSync as mkdirSync38,
466126
466883
  readdirSync as readdirSync20,
466127
466884
  readFileSync as readFileSync34,
@@ -466129,7 +466886,7 @@ import {
466129
466886
  } from "node:fs";
466130
466887
  import { join as join67 } from "node:path";
466131
466888
  function readJsonl(path41) {
466132
- if (!existsSync52(path41))
466889
+ if (!existsSync53(path41))
466133
466890
  return [];
466134
466891
  return readFileSync34(path41, "utf8").split(`
466135
466892
  `).filter((line) => line.trim().length > 0).map((line) => JSON.parse(line));
@@ -466417,13 +467174,13 @@ function migrateLocalBackendTranscripts(input) {
466417
467174
  skipped: [],
466418
467175
  dryRun: input.dryRun === true
466419
467176
  };
466420
- if (!existsSync52(conversationsDir))
467177
+ if (!existsSync53(conversationsDir))
466421
467178
  return result;
466422
467179
  for (const name of readdirSync20(conversationsDir)) {
466423
467180
  const conversationDir = join67(conversationsDir, name);
466424
467181
  const messagesPath = join67(conversationDir, "messages.jsonl");
466425
467182
  const manifestPath = join67(conversationDir, "manifest.json");
466426
- const hasManifest = existsSync52(manifestPath);
467183
+ const hasManifest = existsSync53(manifestPath);
466427
467184
  const existingManifest = hasManifest ? (() => {
466428
467185
  try {
466429
467186
  return JSON.parse(readFileSync34(manifestPath, "utf8"));
@@ -466467,7 +467224,7 @@ function migrateLocalBackendTranscripts(input) {
466467
467224
  copyFileSync3(messagesPath, backupPath);
466468
467225
  const conversationPath = join67(conversationDir, "conversation.json");
466469
467226
  let conversation;
466470
- if (existsSync52(conversationPath)) {
467227
+ if (existsSync53(conversationPath)) {
466471
467228
  try {
466472
467229
  conversation = JSON.parse(readFileSync34(conversationPath, "utf8"));
466473
467230
  } catch {
@@ -466661,7 +467418,7 @@ var init_memory_tokens = __esm(() => {
466661
467418
  });
466662
467419
 
466663
467420
  // src/cli/subcommands/memory.ts
466664
- import { cpSync, existsSync as existsSync53, mkdirSync as mkdirSync39, rmSync as rmSync12, statSync as statSync17 } from "node:fs";
467421
+ import { cpSync, existsSync as existsSync54, mkdirSync as mkdirSync39, rmSync as rmSync12, statSync as statSync17 } from "node:fs";
466665
467422
  import { readdir as readdir13 } from "node:fs/promises";
466666
467423
  import { dirname as dirname30, join as join68 } from "node:path";
466667
467424
  import { parseArgs as parseArgs10 } from "node:util";
@@ -466723,7 +467480,7 @@ function formatBackupTimestamp(date6 = new Date) {
466723
467480
  }
466724
467481
  async function listBackups(agentId) {
466725
467482
  const agentRoot = getAgentRoot(agentId);
466726
- if (!existsSync53(agentRoot)) {
467483
+ if (!existsSync54(agentRoot)) {
466727
467484
  return [];
466728
467485
  }
466729
467486
  const entries = await readdir13(agentRoot, { withFileTypes: true });
@@ -466826,14 +467583,14 @@ async function runMemorySubcommand(argv) {
466826
467583
  }
466827
467584
  if (action3 === "backup") {
466828
467585
  const root2 = getMemoryRoot(agentId);
466829
- if (!existsSync53(root2)) {
467586
+ if (!existsSync54(root2)) {
466830
467587
  console.error(`Memory directory not found for agent ${agentId}.`);
466831
467588
  return 1;
466832
467589
  }
466833
467590
  const agentRoot = getAgentRoot(agentId);
466834
467591
  const backupName = `memory-backup-${formatBackupTimestamp()}`;
466835
467592
  const backupPath = join68(agentRoot, backupName);
466836
- if (existsSync53(backupPath)) {
467593
+ if (existsSync54(backupPath)) {
466837
467594
  console.error(`Backup already exists at ${backupPath}`);
466838
467595
  return 1;
466839
467596
  }
@@ -466857,7 +467614,7 @@ async function runMemorySubcommand(argv) {
466857
467614
  return 1;
466858
467615
  }
466859
467616
  const backupPath = resolveBackupPath(agentId, from);
466860
- if (!existsSync53(backupPath)) {
467617
+ if (!existsSync54(backupPath)) {
466861
467618
  console.error(`Backup not found: ${backupPath}`);
466862
467619
  return 1;
466863
467620
  }
@@ -466879,11 +467636,11 @@ async function runMemorySubcommand(argv) {
466879
467636
  return 1;
466880
467637
  }
466881
467638
  const root2 = getMemoryRoot(agentId);
466882
- if (!existsSync53(root2)) {
467639
+ if (!existsSync54(root2)) {
466883
467640
  console.error(`Memory directory not found for agent ${agentId}.`);
466884
467641
  return 1;
466885
467642
  }
466886
- if (existsSync53(out)) {
467643
+ if (existsSync54(out)) {
466887
467644
  const stat15 = statSync17(out);
466888
467645
  if (stat15.isDirectory()) {
466889
467646
  const contents = await readdir13(out);
@@ -466931,10 +467688,10 @@ var init_memory7 = __esm(() => {
466931
467688
  });
466932
467689
 
466933
467690
  // src/backend/local/transcript-search.ts
466934
- import { existsSync as existsSync54, readdirSync as readdirSync21, readFileSync as readFileSync35, statSync as statSync18 } from "node:fs";
467691
+ import { existsSync as existsSync55, readdirSync as readdirSync21, readFileSync as readFileSync35, statSync as statSync18 } from "node:fs";
466935
467692
  import { join as join69 } from "node:path";
466936
467693
  function readJsonFile3(path41) {
466937
- if (!existsSync54(path41))
467694
+ if (!existsSync55(path41))
466938
467695
  return;
466939
467696
  try {
466940
467697
  return JSON.parse(readFileSync35(path41, "utf8"));
@@ -466943,7 +467700,7 @@ function readJsonFile3(path41) {
466943
467700
  }
466944
467701
  }
466945
467702
  function readJsonlFile2(path41) {
466946
- if (!existsSync54(path41))
467703
+ if (!existsSync55(path41))
466947
467704
  return [];
466948
467705
  try {
466949
467706
  return readFileSync35(path41, "utf8").split(`
@@ -467179,7 +467936,7 @@ function collectConversationMessages(input) {
467179
467936
  }
467180
467937
  function conversationDirectories(storageDir) {
467181
467938
  const conversationsDir = join69(storageDir, "conversations");
467182
- if (!existsSync54(conversationsDir))
467939
+ if (!existsSync55(conversationsDir))
467183
467940
  return [];
467184
467941
  try {
467185
467942
  return readdirSync21(conversationsDir).map((entry) => join69(conversationsDir, entry)).filter((entryPath) => statSync18(entryPath).isDirectory());
@@ -467648,7 +468405,7 @@ import {
467648
468405
  } from "node:child_process";
467649
468406
  import {
467650
468407
  copyFileSync as copyFileSync4,
467651
- existsSync as existsSync55,
468408
+ existsSync as existsSync56,
467652
468409
  lstatSync as lstatSync3,
467653
468410
  mkdirSync as mkdirSync40,
467654
468411
  mkdtempSync as mkdtempSync3,
@@ -467857,7 +468614,7 @@ function copyDependencyDirectoryFiltered(params) {
467857
468614
  return copied;
467858
468615
  }
467859
468616
  function copyDependencyNodeModules(params) {
467860
- if (!existsSync55(params.sourceNodeModulesDirectory))
468617
+ if (!existsSync56(params.sourceNodeModulesDirectory))
467861
468618
  return;
467862
468619
  copyDependencyDirectoryFiltered({
467863
468620
  installedPackageName: params.installedPackageName,
@@ -467867,7 +468624,7 @@ function copyDependencyNodeModules(params) {
467867
468624
  }
467868
468625
  function copyPackageInternalNodeModules(params) {
467869
468626
  const sourceNodeModulesDirectory = path41.join(params.packageDirectory, "node_modules");
467870
- if (!existsSync55(sourceNodeModulesDirectory))
468627
+ if (!existsSync56(sourceNodeModulesDirectory))
467871
468628
  return;
467872
468629
  copyDependencyDirectoryFiltered({
467873
468630
  sourceRoot: sourceNodeModulesDirectory,
@@ -467889,12 +468646,12 @@ function removeIfExists(targetPath) {
467889
468646
  }
467890
468647
  function restoreDestination(params) {
467891
468648
  rmSync13(params.destinationRoot, { force: true, recursive: true });
467892
- if (params.backupRoot && existsSync55(params.backupRoot)) {
468649
+ if (params.backupRoot && existsSync56(params.backupRoot)) {
467893
468650
  renameSync6(params.backupRoot, params.destinationRoot);
467894
468651
  }
467895
468652
  }
467896
468653
  function restoreDestinationIfNeeded(params) {
467897
- if (!params.backupRoot || !existsSync55(params.backupRoot)) {
468654
+ if (!params.backupRoot || !existsSync56(params.backupRoot)) {
467898
468655
  rmSync13(params.destinationRoot, { force: true, recursive: true });
467899
468656
  return;
467900
468657
  }
@@ -467935,7 +468692,7 @@ function installPreparedManagedModPackage(params) {
467935
468692
  targetPackageRoot: stagingRoot
467936
468693
  });
467937
468694
  }
467938
- if (existsSync55(destinationRoot)) {
468695
+ if (existsSync56(destinationRoot)) {
467939
468696
  backupRoot = makeSiblingTempDirectory(destinationRoot, "backup");
467940
468697
  rmSync13(backupRoot, { force: true, recursive: true });
467941
468698
  renameSync6(destinationRoot, backupRoot);
@@ -468218,7 +468975,7 @@ function hasRuntimeDependencies(packageJson) {
468218
468975
  }
468219
468976
  function readPackageJsonIfExists(packageDirectory) {
468220
468977
  const packageJsonPath = path41.join(packageDirectory, "package.json");
468221
- if (!existsSync55(packageJsonPath))
468978
+ if (!existsSync56(packageJsonPath))
468222
468979
  return null;
468223
468980
  return readPackageJson(packageJsonPath);
468224
468981
  }
@@ -468232,7 +468989,7 @@ function isRegularModFile(filePath) {
468232
468989
  }
468233
468990
  function inferCompatibilityModEntries(packageDirectory) {
468234
468991
  const modsDirectory = path41.join(packageDirectory, "mods");
468235
- if (existsSync55(modsDirectory)) {
468992
+ if (existsSync56(modsDirectory)) {
468236
468993
  const stats = lstatSync3(modsDirectory);
468237
468994
  if (stats.isSymbolicLink()) {
468238
468995
  throw new Error(`Package mods directory must not be a symlink: mods`);
@@ -468507,7 +469264,7 @@ var init_package_installer = __esm(() => {
468507
469264
  // src/mods/package-scaffolder.ts
468508
469265
  import {
468509
469266
  copyFileSync as copyFileSync5,
468510
- existsSync as existsSync56,
469267
+ existsSync as existsSync57,
468511
469268
  lstatSync as lstatSync4,
468512
469269
  mkdirSync as mkdirSync41,
468513
469270
  rmSync as rmSync14,
@@ -468600,7 +469357,7 @@ function scaffoldLocalModPackage(options3) {
468600
469357
  const sourceFile = readSourceFile(options3.sourceFile);
468601
469358
  const packageName = assertValidPackageName(options3.packageName);
468602
469359
  const outputDirectory = path42.resolve(options3.outputDirectory ?? defaultOutputDirectory(sourceFile, packageName));
468603
- if (existsSync56(outputDirectory)) {
469360
+ if (existsSync57(outputDirectory)) {
468604
469361
  throw new Error(`Output directory already exists: ${outputDirectory}`);
468605
469362
  }
468606
469363
  const modFileName = path42.basename(sourceFile);
@@ -469311,7 +470068,7 @@ async function runTurnViaListenerRuntime(params) {
469311
470068
  const listener = await ensureListenerRuntime(params.onLog);
469312
470069
  const scopedRuntime = getOrCreateScopedRuntime(listener, params.agentId, params.conversationId);
469313
470070
  getOrCreateConversationPermissionModeStateRef(listener, params.agentId, params.conversationId).mode = "unrestricted";
469314
- const socket = listener.socket ?? listener.transport ?? bridgeTransport;
470071
+ const socket = getOrCreateProcessTransport(listener);
469315
470072
  const dispatchOptions = {
469316
470073
  connectionId: listener.connectionId ?? "openai-api",
469317
470074
  wsUrl: "",
@@ -469323,10 +470080,7 @@ async function runTurnViaListenerRuntime(params) {
469323
470080
  params.onLog?.(`OpenAI-compat turn error: ${error54.message}`);
469324
470081
  }
469325
470082
  };
469326
- const processQueuedTurn = async (queuedTurn, dequeuedBatch) => {
469327
- const queuedScope = getOrCreateScopedRuntime(listener, queuedTurn.agentId, queuedTurn.conversationId);
469328
- await handleIncomingMessage(queuedTurn, socket, queuedScope, undefined, dispatchOptions.connectionId, dequeuedBatch.batchId);
469329
- };
470083
+ const processQueuedTurn = createConnectionTurnProcessor(listener);
469330
470084
  return await new Promise((resolve32) => {
469331
470085
  let text2 = "";
469332
470086
  let usage = {
@@ -469437,7 +470191,7 @@ async function runTurnViaListenerRuntime(params) {
469437
470191
  socket,
469438
470192
  options: dispatchOptions,
469439
470193
  processQueuedTurn,
469440
- processIncomingMessage: handleIncomingMessage,
470194
+ processIncomingMessage: (incoming2, incomingSocket, incomingRuntime, onStatusChange, _connectionId, batchId) => handleIncomingMessage(incoming2, incomingSocket, incomingRuntime, onStatusChange, undefined, batchId),
469441
470195
  trackListenerError: (errorType, error54) => {
469442
470196
  params.onLog?.(`OpenAI-compat listener error (${errorType}): ${error54 instanceof Error ? error54.message : String(error54)}`);
469443
470197
  }
@@ -469447,14 +470201,15 @@ async function runTurnViaListenerRuntime(params) {
469447
470201
  }
469448
470202
  });
469449
470203
  }
469450
- var runTurnImpl, OPENAI_TURN_TIMEOUT_MS, bridgeTransport, bridgeRuntimeStart = null, bridgeOwnedRuntime = null;
470204
+ var runTurnImpl, OPENAI_TURN_TIMEOUT_MS, bridgeRuntimeStart = null, bridgeOwnedRuntime = null;
469451
470205
  var init_app_server_openai_turn = __esm(async () => {
469452
470206
  init_settings_manager();
470207
+ init_connection();
469453
470208
  init_permission_mode();
469454
470209
  init_runtime6();
469455
- init_transport();
469456
470210
  init_turn_observers();
469457
470211
  await __promiseAll([
470212
+ init_connection_lifecycle(),
469458
470213
  init_conversation_runtime(),
469459
470214
  init_inbound_dispatch(),
469460
470215
  init_lifecycle(),
@@ -469462,7 +470217,6 @@ var init_app_server_openai_turn = __esm(async () => {
469462
470217
  ]);
469463
470218
  runTurnImpl = runTurnViaListenerRuntime;
469464
470219
  OPENAI_TURN_TIMEOUT_MS = 15 * 60 * 1000;
469465
- bridgeTransport = new LocalListenerTransport;
469466
470220
  });
469467
470221
 
469468
470222
  // src/websocket/app-server-openai.ts
@@ -469470,7 +470224,7 @@ import { randomUUID as randomUUID28 } from "node:crypto";
469470
470224
  function isOpenAiCompatPath(pathname) {
469471
470225
  return pathname === MODELS_PATH || pathname === CHAT_COMPLETIONS_PATH;
469472
470226
  }
469473
- function sendJson2(response, statusCode2, body3) {
470227
+ function sendJson(response, statusCode2, body3) {
469474
470228
  const payload = JSON.stringify(body3);
469475
470229
  response.writeHead(statusCode2, {
469476
470230
  "content-type": "application/json",
@@ -469479,7 +470233,7 @@ function sendJson2(response, statusCode2, body3) {
469479
470233
  response.end(payload);
469480
470234
  }
469481
470235
  function sendOpenAiError(response, statusCode2, message, type3, code2 = null) {
469482
- sendJson2(response, statusCode2, {
470236
+ sendJson(response, statusCode2, {
469483
470237
  error: { message, type: type3, param: null, code: code2 }
469484
470238
  });
469485
470239
  }
@@ -469608,7 +470362,7 @@ async function handleListModels(response) {
469608
470362
  created: toModelCreatedTimestamp(agent2.created_at),
469609
470363
  owned_by: "letta"
469610
470364
  }));
469611
- sendJson2(response, 200, { object: "list", data });
470365
+ sendJson(response, 200, { object: "list", data });
469612
470366
  }
469613
470367
  async function resolveAgentForModel(model) {
469614
470368
  const agents = await listAgentEntries();
@@ -469886,7 +470640,7 @@ async function handleChatCompletions(request, response, options3) {
469886
470640
  sendOpenAiError(response, 500, streamError, "server_error");
469887
470641
  return;
469888
470642
  }
469889
- sendJson2(response, 200, {
470643
+ sendJson(response, 200, {
469890
470644
  id: completionId,
469891
470645
  object: "chat.completion",
469892
470646
  created,
@@ -469956,10 +470710,9 @@ function getRequiredAddressInfo(server2) {
469956
470710
  }
469957
470711
  return address;
469958
470712
  }
469959
- function getChannelUrl(baseUrl, path43, channel) {
470713
+ function getWebSocketUrl(baseUrl, path43) {
469960
470714
  const url2 = new URL(baseUrl);
469961
470715
  url2.pathname = path43;
469962
- url2.searchParams.set("channel", channel);
469963
470716
  return url2.toString();
469964
470717
  }
469965
470718
  function closeSocket(socket, code2 = 1001, reason = "closing") {
@@ -469984,35 +470737,6 @@ Content-Length: 0\r
469984
470737
  function getRequestUrl(request, host) {
469985
470738
  return new URL(request.url ?? "/", `http://${request.headers.host ?? host}`);
469986
470739
  }
469987
- function getRequestChannel(url2) {
469988
- const channel = url2.searchParams.get("channel");
469989
- if (channel === null || channel === "" || channel === "control") {
469990
- return "control";
469991
- }
469992
- if (channel === "stream") {
469993
- return "stream";
469994
- }
469995
- return null;
469996
- }
469997
- function attachStreamSocket(activeSession, socket) {
469998
- if (activeSession.streamSocket) {
469999
- closeSocket(socket, 1008, "stream channel already connected");
470000
- return;
470001
- }
470002
- activeSession.streamSocket = socket;
470003
- activeSession.runtime.streamSocket = socket;
470004
- activeSession.runtime.streamTransport = socket;
470005
- socket.on("close", () => {
470006
- if (activeSession.streamSocket === socket) {
470007
- activeSession.streamSocket = null;
470008
- }
470009
- if (activeSession.runtime.streamSocket === socket) {
470010
- activeSession.runtime.streamSocket = null;
470011
- activeSession.runtime.streamTransport = null;
470012
- terminateSocket(activeSession.controlSocket);
470013
- }
470014
- });
470015
- }
470016
470740
  function parseAppServerListenUrl(listen = DEFAULT_LISTEN_URL) {
470017
470741
  let url2;
470018
470742
  try {
@@ -470033,46 +470757,6 @@ function parseAppServerListenUrl(listen = DEFAULT_LISTEN_URL) {
470033
470757
  const path43 = url2.pathname === "/" ? DEFAULT_WS_PATH : url2.pathname;
470034
470758
  return { host: normalizeListenHost(url2.hostname), port, path: path43 };
470035
470759
  }
470036
- async function startControlSession(params) {
470037
- const existingRuntime = getActiveRuntime();
470038
- if (existingRuntime) {
470039
- stopRuntime(existingRuntime, true);
470040
- setActiveRuntime(null);
470041
- }
470042
- const runtime = createRuntime();
470043
- runtime.onWsEvent = undefined;
470044
- runtime.connectionId = `app-server-${crypto.randomUUID()}`;
470045
- runtime.connectionName = params.connectionName;
470046
- setActiveRuntime(runtime);
470047
- telemetry.setSurface(getListenerTelemetrySurface());
470048
- telemetry.init();
470049
- const startupReady = (async () => {
470050
- await reloadListenerModAdapter(runtime);
470051
- await loadTools();
470052
- })();
470053
- const activeSession = {
470054
- runtime,
470055
- controlSocket: params.socket,
470056
- streamSocket: params.streamSocket
470057
- };
470058
- params.onSessionCreated(activeSession);
470059
- await attachOpenListenerSocket(runtime, params.socket, {
470060
- connectionId: runtime.connectionId ?? "app-server",
470061
- wsUrl: params.serverUrl,
470062
- supportsSplitStatusChannels: true,
470063
- deviceId: settingsManager.getOrCreateDeviceId(),
470064
- connectionName: params.connectionName,
470065
- onConnected: () => {},
470066
- onDisconnected: params.onSessionClosed,
470067
- onError: () => {}
470068
- }, {
470069
- streamSocket: params.streamSocket,
470070
- startHeartbeat: false,
470071
- startCronScheduler: true,
470072
- startupReady
470073
- });
470074
- return activeSession;
470075
- }
470076
470760
  async function startAppServer(options3 = {}) {
470077
470761
  await settingsManager.initialize();
470078
470762
  const listen = parseAppServerListenUrl(options3.listen);
@@ -470081,72 +470765,60 @@ async function startAppServer(options3 = {}) {
470081
470765
  throw new Error(`refusing to start non-loopback websocket listener ${listen.host}:${listen.port} without auth; configure \`--ws-auth capability-token\` or \`--ws-auth signed-bearer-token\``);
470082
470766
  }
470083
470767
  const wss = new WebSocketServer({ noServer: true });
470084
- let activeSession = null;
470085
- let pendingStreamSocket = null;
470086
- let pendingStreamTimeout = null;
470087
470768
  let resolvedInfo = null;
470088
- const lastPongAtBySocket = new WeakMap;
470089
- const clearPendingStream = () => {
470090
- if (pendingStreamTimeout) {
470091
- clearTimeout(pendingStreamTimeout);
470092
- pendingStreamTimeout = null;
470769
+ let nextConnectionOrdinal = 0;
470770
+ const runtime = createRuntime();
470771
+ runtime.onWsEvent = undefined;
470772
+ runtime.connectionId = "app-server";
470773
+ runtime.connectionName = options3.connectionName ?? hostname6();
470774
+ let startupReady = null;
470775
+ const getStartupReady = () => {
470776
+ if (startupReady) {
470777
+ return startupReady;
470093
470778
  }
470094
- const socket = pendingStreamSocket;
470095
- pendingStreamSocket = null;
470096
- return socket;
470779
+ const attempt = (async () => {
470780
+ if (options3.initializeRuntime) {
470781
+ await options3.initializeRuntime(runtime);
470782
+ return;
470783
+ }
470784
+ await reloadListenerModAdapter(runtime);
470785
+ await loadTools();
470786
+ })();
470787
+ startupReady = attempt;
470788
+ attempt.catch(() => {
470789
+ if (startupReady === attempt) {
470790
+ startupReady = null;
470791
+ }
470792
+ });
470793
+ return startupReady;
470097
470794
  };
470098
- const handleWebSocketConnection = (socket, channel) => {
470795
+ const lastPongAtBySocket = new WeakMap;
470796
+ const handleWebSocketConnection = (socket) => {
470797
+ const connectionId = `app-server-${nextConnectionOrdinal}`;
470798
+ nextConnectionOrdinal += 1;
470099
470799
  lastPongAtBySocket.set(socket, Date.now());
470100
470800
  socket.on("pong", () => {
470101
- lastPongAtBySocket.set(socket, Date.now());
470102
- });
470103
- if (channel === "stream") {
470104
- if (activeSession) {
470105
- attachStreamSocket(activeSession, socket);
470106
- return;
470107
- }
470108
- if (pendingStreamSocket) {
470109
- closeSocket(socket, 1008, "stream channel already pending");
470110
- return;
470801
+ if (options3.shouldRecordPong?.(connectionId) !== false) {
470802
+ lastPongAtBySocket.set(socket, Date.now());
470111
470803
  }
470112
- pendingStreamSocket = socket;
470113
- pendingStreamTimeout = setTimeout(() => {
470114
- const staleSocket = clearPendingStream();
470115
- closeSocket(staleSocket, 1008, "control channel did not connect");
470116
- }, PENDING_STREAM_TIMEOUT_MS);
470117
- socket.on("close", () => {
470118
- if (pendingStreamSocket === socket) {
470119
- clearPendingStream();
470120
- }
470121
- });
470122
- socket.on("error", (error54) => {
470123
- options3.onLog?.(`App-server stream socket error: ${error54.message}`);
470124
- });
470125
- return;
470126
- }
470127
- if (activeSession) {
470128
- closeSocket(socket, 1008, "control channel already connected");
470129
- return;
470130
- }
470131
- const streamSocket = clearPendingStream();
470132
- startControlSession({
470133
- socket,
470134
- streamSocket,
470804
+ });
470805
+ attachOpenListenerSocket(runtime, socket, {
470806
+ connectionId,
470807
+ wsUrl: resolvedInfo?.url ?? options3.listen ?? DEFAULT_LISTEN_URL,
470808
+ deviceId: settingsManager.getOrCreateDeviceId(),
470135
470809
  connectionName: options3.connectionName ?? hostname6(),
470136
- serverUrl: resolvedInfo?.url ?? options3.listen ?? DEFAULT_LISTEN_URL,
470137
- onSessionCreated: (session) => {
470138
- activeSession = session;
470139
- },
470140
- onSessionClosed: () => {
470141
- activeSession = null;
470810
+ onConnected: () => {},
470811
+ onDisconnected: () => {},
470812
+ onError: (error54) => {
470813
+ options3.onLog?.(`App-server connection ${connectionId} failed: ${error54.message}`);
470142
470814
  }
470815
+ }, {
470816
+ startHeartbeat: false,
470817
+ startCronScheduler: true,
470818
+ startupReady: getStartupReady()
470143
470819
  }).catch((error54) => {
470144
- if (activeSession?.controlSocket === socket) {
470145
- activeSession = null;
470146
- }
470147
- options3.onLog?.(`Failed to start app-server session: ${error54 instanceof Error ? error54.message : String(error54)}`);
470148
- closeSocket(socket, 1011, "failed to start session");
470149
- closeSocket(streamSocket, 1011, "failed to start session");
470820
+ options3.onLog?.(`Failed to start app-server connection: ${error54 instanceof Error ? error54.message : String(error54)}`);
470821
+ closeSocket(socket, 1011, "failed to start connection");
470150
470822
  });
470151
470823
  };
470152
470824
  const server2 = createServer((request, response) => {
@@ -470198,9 +470870,9 @@ async function startAppServer(options3 = {}) {
470198
470870
  rejectUpgrade(socket, 404, "Not Found");
470199
470871
  return;
470200
470872
  }
470201
- const channel = getRequestChannel(requestUrl);
470202
- if (!channel) {
470203
- rejectUpgrade(socket, 400, "Bad Request");
470873
+ if (requestUrl.searchParams.has("channel")) {
470874
+ options3.onLog?.("Rejecting legacy split-channel app-server client; upgrade to a one-socket client");
470875
+ rejectUpgrade(socket, 426, "Upgrade Required");
470204
470876
  return;
470205
470877
  }
470206
470878
  const authError = authorizeUpgrade(request.headers, authPolicy);
@@ -470215,7 +470887,7 @@ async function startAppServer(options3 = {}) {
470215
470887
  return;
470216
470888
  }
470217
470889
  wss.handleUpgrade(request, socket, head2, (websocket) => {
470218
- handleWebSocketConnection(websocket, channel);
470890
+ handleWebSocketConnection(websocket);
470219
470891
  });
470220
470892
  });
470221
470893
  const heartbeatIntervalMs = options3.heartbeatIntervalMs ?? APP_SERVER_HEARTBEAT_INTERVAL_MS;
@@ -470238,25 +470910,38 @@ async function startAppServer(options3 = {}) {
470238
470910
  wss.on("close", () => {
470239
470911
  clearInterval(heartbeatInterval);
470240
470912
  });
470241
- await new Promise((resolve32, reject) => {
470242
- const onError = (error54) => {
470243
- server2.off("listening", onListening);
470244
- reject(error54);
470245
- };
470246
- const onListening = () => {
470247
- server2.off("error", onError);
470248
- resolve32();
470249
- };
470250
- server2.once("error", onError);
470251
- server2.once("listening", onListening);
470252
- server2.listen(listen.port, listen.host);
470253
- });
470913
+ try {
470914
+ await new Promise((resolve32, reject) => {
470915
+ const onError = (error54) => {
470916
+ server2.off("listening", onListening);
470917
+ reject(error54);
470918
+ };
470919
+ const onListening = () => {
470920
+ server2.off("error", onError);
470921
+ resolve32();
470922
+ };
470923
+ server2.once("error", onError);
470924
+ server2.once("listening", onListening);
470925
+ server2.listen(listen.port, listen.host);
470926
+ });
470927
+ } catch (error54) {
470928
+ clearInterval(heartbeatInterval);
470929
+ runtime.intentionallyClosed = true;
470930
+ throw error54;
470931
+ }
470932
+ const existingRuntime = getActiveRuntime();
470933
+ if (existingRuntime) {
470934
+ stopRuntime(existingRuntime, true);
470935
+ setActiveRuntime(null);
470936
+ }
470937
+ setActiveRuntime(runtime);
470938
+ telemetry.setSurface(getListenerTelemetrySurface());
470939
+ telemetry.init();
470254
470940
  const address = getRequiredAddressInfo(server2);
470255
470941
  const baseUrl = `ws://${listen.host}:${address.port}`;
470256
470942
  resolvedInfo = {
470257
470943
  url: baseUrl,
470258
- controlUrl: getChannelUrl(baseUrl, listen.path, "control"),
470259
- streamUrl: getChannelUrl(baseUrl, listen.path, "stream")
470944
+ controlUrl: getWebSocketUrl(baseUrl, listen.path)
470260
470945
  };
470261
470946
  options3.onListening?.(resolvedInfo);
470262
470947
  return {
@@ -470266,21 +470951,13 @@ async function startAppServer(options3 = {}) {
470266
470951
  if (options3.openaiApi) {
470267
470952
  closeOpenAiBridgeRuntime();
470268
470953
  }
470269
- const streamSocket = clearPendingStream();
470270
- terminateSocket(streamSocket);
470271
- if (activeSession) {
470272
- const session = activeSession;
470273
- activeSession = null;
470274
- terminateSocket(session.streamSocket);
470275
- terminateSocket(session.controlSocket);
470276
- stopRuntime(session.runtime, true);
470277
- if (getActiveRuntime() === session.runtime) {
470278
- setActiveRuntime(null);
470279
- }
470280
- }
470281
470954
  for (const client of wss.clients) {
470282
470955
  terminateSocket(client);
470283
470956
  }
470957
+ if (getActiveRuntime() === runtime) {
470958
+ stopRuntime(runtime, true);
470959
+ setActiveRuntime(null);
470960
+ }
470284
470961
  await new Promise((resolve32, reject) => {
470285
470962
  wss.close();
470286
470963
  const timeout = setTimeout(resolve32, 1000);
@@ -470296,7 +470973,7 @@ async function startAppServer(options3 = {}) {
470296
470973
  }
470297
470974
  };
470298
470975
  }
470299
- var DEFAULT_LISTEN_URL = "ws://127.0.0.1:0", DEFAULT_WS_PATH = "/ws", PENDING_STREAM_TIMEOUT_MS = 5000, APP_SERVER_HEARTBEAT_INTERVAL_MS = 30000, APP_SERVER_PONG_TIMEOUT_MS = 90000;
470976
+ var DEFAULT_LISTEN_URL = "ws://127.0.0.1:0", DEFAULT_WS_PATH = "/ws", APP_SERVER_HEARTBEAT_INTERVAL_MS = 30000, APP_SERVER_PONG_TIMEOUT_MS = 90000;
470300
470977
  var init_app_server = __esm(async () => {
470301
470978
  init_settings_manager();
470302
470979
  init_telemetry();
@@ -470402,8 +471079,7 @@ async function runAppServerSubcommand(argv) {
470402
471079
  openaiApi,
470403
471080
  onListening: (info) => {
470404
471081
  console.log(`Listening on ${info.url}`);
470405
- console.log(`Control: ${info.controlUrl}`);
470406
- console.log(`Stream: ${info.streamUrl}`);
471082
+ console.log(`WebSocket: ${info.controlUrl}`);
470407
471083
  if (openaiApi) {
470408
471084
  const openaiBase = new URL(info.url);
470409
471085
  openaiBase.protocol = "http:";
@@ -472207,7 +472883,7 @@ var init_AgentSelector = __esm(async () => {
472207
472883
  // src/cli/subcommands/skills.ts
472208
472884
  import {
472209
472885
  cpSync as cpSync2,
472210
- existsSync as existsSync57,
472886
+ existsSync as existsSync58,
472211
472887
  mkdtempSync as mkdtempSync4,
472212
472888
  readFileSync as readFileSync37,
472213
472889
  rmSync as rmSync15,
@@ -472666,7 +473342,7 @@ async function installSkillDirectory(params) {
472666
473342
  const sourceDir = resolve32(params.sourceDir);
472667
473343
  const memoryDir = resolve32(params.memoryDir);
472668
473344
  const skillMdPath = join71(sourceDir, "SKILL.md");
472669
- if (!existsSync57(skillMdPath)) {
473345
+ if (!existsSync58(skillMdPath)) {
472670
473346
  throw new Error("No SKILL.md found in the skill directory.");
472671
473347
  }
472672
473348
  if (!statSync19(sourceDir).isDirectory()) {
@@ -472676,7 +473352,7 @@ async function installSkillDirectory(params) {
472676
473352
  const skillsDir = join71(memoryDir, "skills");
472677
473353
  const targetPath = join71(skillsDir, name);
472678
473354
  assertInside(skillsDir, targetPath);
472679
- if (existsSync57(targetPath)) {
473355
+ if (existsSync58(targetPath)) {
472680
473356
  if (!params.force) {
472681
473357
  throw new Error(`Skill "${name}" already exists at ${targetPath}. Re-run with --force to replace it.`);
472682
473358
  }
@@ -472692,7 +473368,7 @@ async function installSkillDirectory(params) {
472692
473368
  async function listSkillDirectories(params) {
472693
473369
  const memoryDir = resolve32(params.memoryDir);
472694
473370
  const skillsDir = join71(memoryDir, "skills");
472695
- if (!existsSync57(skillsDir))
473371
+ if (!existsSync58(skillsDir))
472696
473372
  return [];
472697
473373
  const entries = await readdir14(skillsDir, { withFileTypes: true });
472698
473374
  const skills = [];
@@ -472701,7 +473377,7 @@ async function listSkillDirectories(params) {
472701
473377
  continue;
472702
473378
  const skillDir = join71(skillsDir, entry.name);
472703
473379
  const skillMdPath = join71(skillDir, "SKILL.md");
472704
- if (!existsSync57(skillMdPath))
473380
+ if (!existsSync58(skillMdPath))
472705
473381
  continue;
472706
473382
  let name = entry.name;
472707
473383
  let description;
@@ -472725,7 +473401,7 @@ async function deleteSkillDirectory(params) {
472725
473401
  const name = sanitizeSkillName(params.name);
472726
473402
  const targetPath = join71(skillsDir, name);
472727
473403
  assertInside(skillsDir, targetPath);
472728
- if (!existsSync57(targetPath)) {
473404
+ if (!existsSync58(targetPath)) {
472729
473405
  throw new Error(`Skill "${name}" is not installed at ${targetPath}.`);
472730
473406
  }
472731
473407
  if (!statSync19(targetPath).isDirectory()) {
@@ -472774,7 +473450,7 @@ async function installSkill(specifier, agentId, force) {
472774
473450
  tmpDir = downloaded.tmpDir;
472775
473451
  const sourceDir = resolve32(downloaded.sourceDir);
472776
473452
  assertInside(tmpDir, sourceDir);
472777
- if (!existsSync57(sourceDir)) {
473453
+ if (!existsSync58(sourceDir)) {
472778
473454
  const missingPath = source2.type === "git" ? source2.location.subdir ?? "." : source2.type === "direct-file" ? source2.location.url : source2.location.slug;
472779
473455
  throw new Error(`Skill path not found: ${missingPath}`);
472780
473456
  }
@@ -474073,11 +474749,11 @@ var exports_bootstrap_tools = {};
474073
474749
  __export(exports_bootstrap_tools, {
474074
474750
  bootstrapBaseToolsIfNeeded: () => bootstrapBaseToolsIfNeeded
474075
474751
  });
474076
- import { existsSync as existsSync58, mkdirSync as mkdirSync42, writeFileSync as writeFileSync31 } from "node:fs";
474752
+ import { existsSync as existsSync59, mkdirSync as mkdirSync42, writeFileSync as writeFileSync31 } from "node:fs";
474077
474753
  import { homedir as homedir42 } from "node:os";
474078
474754
  import { join as join75 } from "node:path";
474079
474755
  async function bootstrapBaseToolsIfNeeded() {
474080
- if (existsSync58(MARKER_PATH))
474756
+ if (existsSync59(MARKER_PATH))
474081
474757
  return;
474082
474758
  debugLog("bootstrap", "No marker found, bootstrapping base tools...");
474083
474759
  try {
@@ -474643,7 +475319,7 @@ var init_headless_tool_events = __esm(async () => {
474643
475319
  });
474644
475320
 
474645
475321
  // src/skills/builtin/creating-skills/scripts/validate-skill.ts
474646
- import { existsSync as existsSync59, readFileSync as readFileSync38 } from "node:fs";
475322
+ import { existsSync as existsSync60, readFileSync as readFileSync38 } from "node:fs";
474647
475323
  import { basename as basename30, join as join77, resolve as resolve33 } from "node:path";
474648
475324
  import { fileURLToPath as fileURLToPath11 } from "node:url";
474649
475325
  function parseQuotedScalar(value) {
@@ -474742,7 +475418,7 @@ function parseFrontmatter2(source2) {
474742
475418
  }
474743
475419
  function validateSkill(skillPath) {
474744
475420
  const skillMdPath = join77(skillPath, "SKILL.md");
474745
- if (!existsSync59(skillMdPath)) {
475421
+ if (!existsSync60(skillMdPath)) {
474746
475422
  return { valid: false, message: "SKILL.md not found" };
474747
475423
  }
474748
475424
  const content = readFileSync38(skillMdPath, "utf-8");
@@ -479871,7 +480547,7 @@ var init_queued_message_parts = __esm(() => {
479871
480547
 
479872
480548
  // src/cli/helpers/reflection-arena-hf-upload.ts
479873
480549
  import { execFile as execFileCb5 } from "node:child_process";
479874
- import { existsSync as existsSync61 } from "node:fs";
480550
+ import { existsSync as existsSync62 } from "node:fs";
479875
480551
  import { appendFile as appendFile2, chmod as chmod2, mkdir as mkdir18, writeFile as writeFile20 } from "node:fs/promises";
479876
480552
  import { homedir as homedir45 } from "node:os";
479877
480553
  import { join as join79 } from "node:path";
@@ -479925,7 +480601,7 @@ async function writeGitAskpass(repoRoot) {
479925
480601
  }
479926
480602
  async function prepareHfRepo(env5) {
479927
480603
  await mkdir18(HF_CACHE_ROOT, { recursive: true });
479928
- if (!existsSync61(join79(HF_REPO_DIR, ".git"))) {
480604
+ if (!existsSync62(join79(HF_REPO_DIR, ".git"))) {
479929
480605
  await runGit6(HF_CACHE_ROOT, ["clone", "--depth", "1", HF_REPO_URL, HF_REPO_DIR], env5);
479930
480606
  return HF_REPO_DIR;
479931
480607
  }
@@ -481128,7 +481804,7 @@ __export(exports_terminal_keybinding_installer, {
481128
481804
  });
481129
481805
  import {
481130
481806
  copyFileSync as copyFileSync6,
481131
- existsSync as existsSync62,
481807
+ existsSync as existsSync63,
481132
481808
  mkdirSync as mkdirSync44,
481133
481809
  readFileSync as readFileSync40,
481134
481810
  writeFileSync as writeFileSync33
@@ -481197,7 +481873,7 @@ function parseKeybindings(content) {
481197
481873
  }
481198
481874
  }
481199
481875
  function keybindingExists(keybindingsPath) {
481200
- if (!existsSync62(keybindingsPath))
481876
+ if (!existsSync63(keybindingsPath))
481201
481877
  return false;
481202
481878
  try {
481203
481879
  const content = readFileSync40(keybindingsPath, { encoding: "utf-8" });
@@ -481210,7 +481886,7 @@ function keybindingExists(keybindingsPath) {
481210
481886
  }
481211
481887
  }
481212
481888
  function createBackup(keybindingsPath) {
481213
- if (!existsSync62(keybindingsPath))
481889
+ if (!existsSync63(keybindingsPath))
481214
481890
  return null;
481215
481891
  const backupPath = `${keybindingsPath}.letta-backup`;
481216
481892
  try {
@@ -481226,12 +481902,12 @@ function installKeybinding(keybindingsPath) {
481226
481902
  return { success: true, alreadyExists: true };
481227
481903
  }
481228
481904
  const parentDir = dirname34(keybindingsPath);
481229
- if (!existsSync62(parentDir)) {
481905
+ if (!existsSync63(parentDir)) {
481230
481906
  mkdirSync44(parentDir, { recursive: true });
481231
481907
  }
481232
481908
  let keybindings = [];
481233
481909
  let backupPath = null;
481234
- if (existsSync62(keybindingsPath)) {
481910
+ if (existsSync63(keybindingsPath)) {
481235
481911
  backupPath = createBackup(keybindingsPath);
481236
481912
  const content = readFileSync40(keybindingsPath, { encoding: "utf-8" });
481237
481913
  const parsed = parseKeybindings(content);
@@ -481261,7 +481937,7 @@ function installKeybinding(keybindingsPath) {
481261
481937
  }
481262
481938
  function removeKeybinding(keybindingsPath) {
481263
481939
  try {
481264
- if (!existsSync62(keybindingsPath)) {
481940
+ if (!existsSync63(keybindingsPath)) {
481265
481941
  return { success: true };
481266
481942
  }
481267
481943
  const content = readFileSync40(keybindingsPath, { encoding: "utf-8" });
@@ -481329,11 +482005,11 @@ function getWezTermConfigPath() {
481329
482005
  const xdgConfig = process.env.XDG_CONFIG_HOME;
481330
482006
  if (xdgConfig) {
481331
482007
  const xdgPath = join81(xdgConfig, "wezterm", "wezterm.lua");
481332
- if (existsSync62(xdgPath))
482008
+ if (existsSync63(xdgPath))
481333
482009
  return xdgPath;
481334
482010
  }
481335
482011
  const configPath = join81(homedir47(), ".config", "wezterm", "wezterm.lua");
481336
- if (existsSync62(configPath))
482012
+ if (existsSync63(configPath))
481337
482013
  return configPath;
481338
482014
  return join81(homedir47(), ".wezterm.lua");
481339
482015
  }
@@ -481438,7 +482114,7 @@ ${WEZTERM_DELETE_FIX}
481438
482114
  `;
481439
482115
  }
481440
482116
  function wezTermDeleteFixExists(configPath) {
481441
- if (!existsSync62(configPath))
482117
+ if (!existsSync63(configPath))
481442
482118
  return false;
481443
482119
  try {
481444
482120
  const content = readFileSync40(configPath, { encoding: "utf-8" });
@@ -481455,14 +482131,14 @@ function installWezTermDeleteFix() {
481455
482131
  }
481456
482132
  let content = "";
481457
482133
  let backupPath = null;
481458
- if (existsSync62(configPath)) {
482134
+ if (existsSync63(configPath)) {
481459
482135
  backupPath = `${configPath}.letta-backup`;
481460
482136
  copyFileSync6(configPath, backupPath);
481461
482137
  content = readFileSync40(configPath, { encoding: "utf-8" });
481462
482138
  }
481463
482139
  content = injectWezTermDeleteFix(content);
481464
482140
  const parentDir = dirname34(configPath);
481465
- if (!existsSync62(parentDir)) {
482141
+ if (!existsSync63(parentDir)) {
481466
482142
  mkdirSync44(parentDir, { recursive: true });
481467
482143
  }
481468
482144
  writeFileSync33(configPath, content, { encoding: "utf-8" });
@@ -483458,9 +484134,9 @@ function getHeaderText(fileEdit) {
483458
484134
  const relPath = relative17(cwd2, fileEdit.filePath);
483459
484135
  const displayPath = relPath.startsWith("..") ? fileEdit.filePath : relPath;
483460
484136
  if (t2 === "write" || t2 === "write_file" || t2 === "writefile" || t2 === "write_file_gemini" || t2 === "writefilegemini") {
483461
- const { existsSync: existsSync63 } = __require("node:fs");
484137
+ const { existsSync: existsSync64 } = __require("node:fs");
483462
484138
  try {
483463
- if (existsSync63(fileEdit.filePath)) {
484139
+ if (existsSync64(fileEdit.filePath)) {
483464
484140
  return `Overwrite ${displayPath}?`;
483465
484141
  }
483466
484142
  } catch {}
@@ -492949,7 +493625,7 @@ var init_agents8 = __esm(() => {
492949
493625
  // src/cli/commands/install-github-app.ts
492950
493626
  import { execFileSync as execFileSync8 } from "node:child_process";
492951
493627
  import {
492952
- existsSync as existsSync63,
493628
+ existsSync as existsSync64,
492953
493629
  mkdirSync as mkdirSync45,
492954
493630
  mkdtempSync as mkdtempSync5,
492955
493631
  readFileSync as readFileSync41,
@@ -493191,12 +493867,12 @@ function runGit7(args, cwd2) {
493191
493867
  }
493192
493868
  function writeWorkflow(repoDir, workflowPath, content) {
493193
493869
  const absolutePath = join83(repoDir, workflowPath);
493194
- if (!existsSync63(dirname35(absolutePath))) {
493870
+ if (!existsSync64(dirname35(absolutePath))) {
493195
493871
  mkdirSync45(dirname35(absolutePath), { recursive: true });
493196
493872
  }
493197
493873
  const next = `${content.trimEnd()}
493198
493874
  `;
493199
- if (existsSync63(absolutePath)) {
493875
+ if (existsSync64(absolutePath)) {
493200
493876
  const previous = readFileSync41(absolutePath, "utf8");
493201
493877
  if (previous === next) {
493202
493878
  return false;
@@ -497742,7 +498418,7 @@ __export(exports_generate_memory_viewer, {
497742
498418
  generateAndOpenMemoryViewer: () => generateAndOpenMemoryViewer
497743
498419
  });
497744
498420
  import { execFile as execFileCb7 } from "node:child_process";
497745
- import { chmodSync as chmodSync7, existsSync as existsSync64, mkdirSync as mkdirSync46, writeFileSync as writeFileSync35 } from "node:fs";
498421
+ import { chmodSync as chmodSync7, existsSync as existsSync65, mkdirSync as mkdirSync46, writeFileSync as writeFileSync35 } from "node:fs";
497746
498422
  import { homedir as homedir50 } from "node:os";
497747
498423
  import { join as join84 } from "node:path";
497748
498424
  import { promisify as promisify17 } from "node:util";
@@ -498053,7 +498729,7 @@ ${m4.body}` : m4.subject;
498053
498729
  async function generateAndOpenMemoryViewer(agentId, options3) {
498054
498730
  const memoryRoot = getScopedMemoryFilesystemRoot(agentId);
498055
498731
  const repoDir = memoryRoot;
498056
- if (!existsSync64(join84(repoDir, ".git"))) {
498732
+ if (!existsSync65(join84(repoDir, ".git"))) {
498057
498733
  throw new Error("Memory viewer requires memfs. Run /memfs enable first.");
498058
498734
  }
498059
498735
  const data = await collectMemoryData(agentId, repoDir, memoryRoot, options3?.conversationId);
@@ -498063,7 +498739,7 @@ async function generateAndOpenMemoryViewer(agentId, options3) {
498063
498739
  data.context = applyContextUsageSnapshot(data.context, options3?.contextUsage);
498064
498740
  const jsonPayload = JSON.stringify(data).replace(/</g, "\\u003c");
498065
498741
  const html5 = memory_viewer_template_default.replace("<!--LETTA_DATA_PLACEHOLDER-->", () => jsonPayload);
498066
- if (!existsSync64(VIEWERS_DIR)) {
498742
+ if (!existsSync65(VIEWERS_DIR)) {
498067
498743
  mkdirSync46(VIEWERS_DIR, { recursive: true, mode: 448 });
498068
498744
  }
498069
498745
  try {
@@ -498099,7 +498775,7 @@ var init_generate_memory_viewer = __esm(() => {
498099
498775
  });
498100
498776
 
498101
498777
  // src/cli/components/MemfsTreeViewer.tsx
498102
- import { existsSync as existsSync65 } from "node:fs";
498778
+ import { existsSync as existsSync66 } from "node:fs";
498103
498779
  import { join as join85 } from "node:path";
498104
498780
  function renderTreePrefix(node) {
498105
498781
  let prefix = "";
@@ -498128,8 +498804,8 @@ function MemfsTreeViewer({
498128
498804
  const [status, setStatus] = import_react88.useState(null);
498129
498805
  const statusTimerRef = import_react88.useRef(null);
498130
498806
  const memoryRoot = getScopedMemoryFilesystemRoot(agentId);
498131
- const memoryExists = existsSync65(memoryRoot);
498132
- const hasGitRepo = import_react88.useMemo(() => existsSync65(join85(memoryRoot, ".git")), [memoryRoot]);
498807
+ const memoryExists = existsSync66(memoryRoot);
498808
+ const hasGitRepo = import_react88.useMemo(() => existsSync66(join85(memoryRoot, ".git")), [memoryRoot]);
498133
498809
  function showStatus(msg, durationMs) {
498134
498810
  if (statusTimerRef.current)
498135
498811
  clearTimeout(statusTimerRef.current);
@@ -506021,9 +506697,9 @@ function getFileEditHeader(toolName, toolArgs) {
506021
506697
  const relPath = relative17(cwd2, filePath);
506022
506698
  const displayPath2 = relPath.startsWith("..") ? filePath : relPath;
506023
506699
  if (t2 === "write" || t2 === "write_file" || t2 === "writefile" || t2 === "write_file_gemini" || t2 === "writefilegemini") {
506024
- const { existsSync: existsSync66 } = __require("node:fs");
506700
+ const { existsSync: existsSync67 } = __require("node:fs");
506025
506701
  try {
506026
- if (existsSync66(filePath)) {
506702
+ if (existsSync67(filePath)) {
506027
506703
  return `Overwrite ${displayPath2}?`;
506028
506704
  }
506029
506705
  } catch {}
@@ -521363,7 +522039,7 @@ __export(exports_generate_diff_viewer, {
521363
522039
  generateAndOpenDiffViewer: () => generateAndOpenDiffViewer
521364
522040
  });
521365
522041
  import { execFile as execFileCb8 } from "node:child_process";
521366
- import { chmodSync as chmodSync8, existsSync as existsSync66, mkdirSync as mkdirSync47, writeFileSync as writeFileSync36 } from "node:fs";
522042
+ import { chmodSync as chmodSync8, existsSync as existsSync67, mkdirSync as mkdirSync47, writeFileSync as writeFileSync36 } from "node:fs";
521367
522043
  import { homedir as homedir52 } from "node:os";
521368
522044
  import { isAbsolute as isAbsolute27, join as join87, resolve as resolve37 } from "node:path";
521369
522045
  import { promisify as promisify18 } from "node:util";
@@ -521584,7 +522260,7 @@ async function generateAndOpenDiffViewer(targetPath) {
521584
522260
  };
521585
522261
  const jsonPayload = JSON.stringify(payload).replace(/</g, "\\u003c");
521586
522262
  const html5 = diff_viewer_template_default.replace("<!--LETTA_DIFF_DATA_PLACEHOLDER-->", () => jsonPayload);
521587
- if (!existsSync66(VIEWERS_DIR2)) {
522263
+ if (!existsSync67(VIEWERS_DIR2)) {
521588
522264
  mkdirSync47(VIEWERS_DIR2, { recursive: true, mode: 448 });
521589
522265
  }
521590
522266
  try {
@@ -523643,12 +524319,12 @@ __export(exports_shell_aliases, {
523643
524319
  expandAliases: () => expandAliases,
523644
524320
  clearAliasCache: () => clearAliasCache
523645
524321
  });
523646
- import { existsSync as existsSync67, readFileSync as readFileSync42 } from "node:fs";
524322
+ import { existsSync as existsSync68, readFileSync as readFileSync42 } from "node:fs";
523647
524323
  import { homedir as homedir53 } from "node:os";
523648
524324
  import { join as join88 } from "node:path";
523649
524325
  function parseAliasesFromFile(filePath) {
523650
524326
  const aliases = new Map;
523651
- if (!existsSync67(filePath)) {
524327
+ if (!existsSync68(filePath)) {
523652
524328
  return aliases;
523653
524329
  }
523654
524330
  try {
@@ -532012,7 +532688,7 @@ var init_conversation_switch_alert = __esm(() => {
532012
532688
 
532013
532689
  // src/cli/app/use-submit-handler.ts
532014
532690
  import { randomUUID as randomUUID37 } from "node:crypto";
532015
- import { existsSync as existsSync68, readFileSync as readFileSync43, renameSync as renameSync7, writeFileSync as writeFileSync37 } from "node:fs";
532691
+ import { existsSync as existsSync69, readFileSync as readFileSync43, renameSync as renameSync7, writeFileSync as writeFileSync37 } from "node:fs";
532016
532692
  import { tmpdir as tmpdir12 } from "node:os";
532017
532693
  import { join as join89 } from "node:path";
532018
532694
  async function findCustomCommandByName(commandName) {
@@ -532712,7 +533388,7 @@ ${SYSTEM_REMINDER_CLOSE}`),
532712
533388
  join89(memoryRoot, "system", "persona.md"),
532713
533389
  join89(memoryRoot, "memory", "system", "persona.md")
532714
533390
  ];
532715
- const personaPath = personaCandidates.find((candidate) => existsSync68(candidate));
533391
+ const personaPath = personaCandidates.find((candidate) => existsSync69(candidate));
532716
533392
  if (personaPath) {
532717
533393
  const personaContent = readFileSync43(personaPath, "utf-8");
532718
533394
  setCurrentPersonalityId(detectPersonalityFromPersonaFile(personaContent));
@@ -533592,7 +534268,7 @@ Path: ${memoryDir}`, true);
533592
534268
  setCommandRunning(true);
533593
534269
  try {
533594
534270
  const memoryDir = getScopedMemoryFilesystemRoot(agentId);
533595
- if (!existsSync68(memoryDir)) {
534271
+ if (!existsSync69(memoryDir)) {
533596
534272
  updateMemorySyncCommand(cmdId, "No local memory filesystem found to reset.", true, msg);
533597
534273
  return { submitted: true };
533598
534274
  }
@@ -536626,9 +537302,9 @@ Memory may be stale. Try running: git -C ${getScopedMemoryFilesystemRoot(agentId
536626
537302
  (async () => {
536627
537303
  try {
536628
537304
  const { watch: watch3 } = await import("node:fs");
536629
- const { existsSync: existsSync69 } = await import("node:fs");
537305
+ const { existsSync: existsSync70 } = await import("node:fs");
536630
537306
  const memRoot = getScopedMemoryFilesystemRoot(agentId);
536631
- if (!existsSync69(memRoot))
537307
+ if (!existsSync70(memRoot))
536632
537308
  return;
536633
537309
  watcher2 = watch3(memRoot, { recursive: true }, () => {});
536634
537310
  memfsWatcherRef.current = watcher2;
@@ -538740,9 +539416,9 @@ Note: Flags should use double dashes for full names (e.g., --yolo, not -yolo)`);
538740
539416
  }
538741
539417
  } else {
538742
539418
  const { resolve: resolve39 } = await import("node:path");
538743
- const { existsSync: existsSync69 } = await import("node:fs");
539419
+ const { existsSync: existsSync70 } = await import("node:fs");
538744
539420
  const resolvedPath = resolve39(fromAfFile);
538745
- if (!existsSync69(resolvedPath)) {
539421
+ if (!existsSync70(resolvedPath)) {
538746
539422
  console.error(`Error: AgentFile not found: ${resolvedPath}`);
538747
539423
  process.exit(1);
538748
539424
  }
@@ -541433,4 +542109,4 @@ function registerBunOAuthFlows() {
541433
542109
  registerBunOAuthFlows();
541434
542110
  await init_src5().then(() => exports_src2);
541435
542111
 
541436
- //# debugId=EDD0BAA906B8C7B064756E2164756E21
542112
+ //# debugId=DC9DDCD2BDAB4F1564756E2164756E21