@tiflis-io/tiflis-code-workstation 0.3.27 → 0.3.29

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.
Files changed (2) hide show
  1. package/dist/main.js +116 -52
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -1610,9 +1610,7 @@ var TunnelClient = class {
1610
1610
  this.handleRegistrationTimeout();
1611
1611
  }
1612
1612
  }, CONNECTION_TIMING.REGISTRATION_TIMEOUT_MS);
1613
- if (this.registrationTimeout.unref) {
1614
- this.registrationTimeout.unref();
1615
- }
1613
+ this.registrationTimeout.unref();
1616
1614
  }
1617
1615
  /**
1618
1616
  * Handles registration timeout.
@@ -1736,9 +1734,7 @@ var TunnelClient = class {
1736
1734
  this.sendToTunnel(ping);
1737
1735
  }
1738
1736
  }, CONNECTION_TIMING.PING_INTERVAL_MS);
1739
- if (this.pingInterval.unref) {
1740
- this.pingInterval.unref();
1741
- }
1737
+ this.pingInterval.unref();
1742
1738
  }
1743
1739
  /**
1744
1740
  * Schedules a reconnection attempt.
@@ -1759,9 +1755,7 @@ var TunnelClient = class {
1759
1755
  this.scheduleReconnect();
1760
1756
  });
1761
1757
  }, delay);
1762
- if (this.reconnectTimeout.unref) {
1763
- this.reconnectTimeout.unref();
1764
- }
1758
+ this.reconnectTimeout.unref();
1765
1759
  }
1766
1760
  /**
1767
1761
  * Flushes buffered messages after reconnection.
@@ -2965,9 +2959,7 @@ var TerminalOutputBatcher = class {
2965
2959
  if (this.timeout === null) {
2966
2960
  const adaptiveInterval = this.outputRate > 1e3 ? this.batchIntervalMs : Math.min(8, this.batchIntervalMs);
2967
2961
  this.timeout = setTimeout(() => this.flush(), adaptiveInterval);
2968
- if (this.timeout.unref) {
2969
- this.timeout.unref();
2970
- }
2962
+ this.timeout.unref();
2971
2963
  }
2972
2964
  }
2973
2965
  /**
@@ -3075,7 +3067,7 @@ var HeadlessAgentExecutor = class extends EventEmitter {
3075
3067
  detached: true,
3076
3068
  // Create new process group for clean termination
3077
3069
  // Ensure child doesn't interfere with parent's signal handling
3078
- // @ts-ignore - Node.js 16+ option
3070
+ // @ts-expect-error - Node.js 16+ option
3079
3071
  ignoreParentSignals: true
3080
3072
  });
3081
3073
  this.subprocess.stdout?.on("data", (data) => {
@@ -5121,9 +5113,10 @@ var MessageBroadcasterImpl = class {
5121
5113
  }
5122
5114
  /**
5123
5115
  * Broadcasts a message to all clients subscribed to a session (by session ID string).
5124
- * Sends targeted messages to each subscriber individually via forward.to_device.
5116
+ * Sends targeted messages to each subscriber in parallel with timeout.
5117
+ * Prevents slow clients from blocking others.
5125
5118
  */
5126
- broadcastToSubscribers(sessionId, message) {
5119
+ async broadcastToSubscribers(sessionId, message) {
5127
5120
  const session = new SessionId(sessionId);
5128
5121
  const subscribers = this.deps.clientRegistry.getSubscribers(session);
5129
5122
  const authenticatedSubscribers = subscribers.filter(
@@ -5132,9 +5125,44 @@ var MessageBroadcasterImpl = class {
5132
5125
  if (authenticatedSubscribers.length === 0) {
5133
5126
  return;
5134
5127
  }
5135
- for (const client of authenticatedSubscribers) {
5136
- this.deps.tunnelClient.sendToDevice(client.deviceId.value, message);
5137
- }
5128
+ const SEND_TIMEOUT_MS = 2e3;
5129
+ const sendPromises = authenticatedSubscribers.map(async (client) => {
5130
+ try {
5131
+ await Promise.race([
5132
+ new Promise((resolve2, reject) => {
5133
+ const sent = this.deps.tunnelClient.sendToDevice(
5134
+ client.deviceId.value,
5135
+ message
5136
+ );
5137
+ if (sent) {
5138
+ resolve2();
5139
+ } else {
5140
+ reject(new Error(`sendToDevice returned false for ${client.deviceId.value}`));
5141
+ }
5142
+ }),
5143
+ new Promise(
5144
+ (_, reject) => setTimeout(
5145
+ () => reject(new Error(`Send timeout for ${client.deviceId.value}`)),
5146
+ SEND_TIMEOUT_MS
5147
+ )
5148
+ )
5149
+ ]);
5150
+ this.logger.debug(
5151
+ { deviceId: client.deviceId.value, sessionId },
5152
+ "Message sent to subscriber"
5153
+ );
5154
+ } catch (error) {
5155
+ this.logger.warn(
5156
+ {
5157
+ deviceId: client.deviceId.value,
5158
+ sessionId,
5159
+ error: error instanceof Error ? error.message : String(error)
5160
+ },
5161
+ "Failed to send to subscriber (timeout or error)"
5162
+ );
5163
+ }
5164
+ });
5165
+ await Promise.allSettled(sendPromises);
5138
5166
  }
5139
5167
  };
5140
5168
 
@@ -9237,7 +9265,7 @@ async function bootstrap() {
9237
9265
  }
9238
9266
  shutdownInProgress = true;
9239
9267
  logger.info({ signal, count: signalCount }, "Signal received");
9240
- console.log("\nShutting down...");
9268
+ logger.info("Shutting down...");
9241
9269
  shutdown2(signal).catch((error) => {
9242
9270
  logger.error({ error }, "Shutdown error");
9243
9271
  }).finally(() => {
@@ -10140,7 +10168,7 @@ async function bootstrap() {
10140
10168
  message_id: messageId
10141
10169
  }
10142
10170
  };
10143
- messageBroadcaster.broadcastToSubscribers(
10171
+ await messageBroadcaster.broadcastToSubscribers(
10144
10172
  sessionId,
10145
10173
  JSON.stringify(errorEvent)
10146
10174
  );
@@ -10176,7 +10204,7 @@ async function bootstrap() {
10176
10204
  from_device_id: deviceId
10177
10205
  }
10178
10206
  };
10179
- messageBroadcaster.broadcastToSubscribers(
10207
+ await messageBroadcaster.broadcastToSubscribers(
10180
10208
  sessionId,
10181
10209
  JSON.stringify(transcriptionEvent)
10182
10210
  );
@@ -10267,7 +10295,7 @@ async function bootstrap() {
10267
10295
  timestamp: Date.now()
10268
10296
  }
10269
10297
  };
10270
- messageBroadcaster.broadcastToSubscribers(
10298
+ await messageBroadcaster.broadcastToSubscribers(
10271
10299
  sessionId,
10272
10300
  JSON.stringify(errorEvent)
10273
10301
  );
@@ -10395,14 +10423,20 @@ async function bootstrap() {
10395
10423
  is_complete: true
10396
10424
  }
10397
10425
  };
10398
- messageBroadcaster.broadcastToSubscribers(
10426
+ void messageBroadcaster.broadcastToSubscribers(
10399
10427
  sessionId,
10400
10428
  JSON.stringify(cancelOutput)
10401
- );
10402
- logger.info(
10403
- { sessionId },
10404
- "Broadcasted cancel message to subscribers"
10405
- );
10429
+ ).then(() => {
10430
+ logger.info(
10431
+ { sessionId },
10432
+ "Broadcasted cancel message to subscribers"
10433
+ );
10434
+ }).catch((error) => {
10435
+ logger.error(
10436
+ { sessionId, error },
10437
+ "Failed to broadcast cancel message"
10438
+ );
10439
+ });
10406
10440
  chatHistoryService.saveAgentMessage(sessionId, "assistant", "", [
10407
10441
  cancelBlock
10408
10442
  ]);
@@ -10888,9 +10922,13 @@ async function bootstrap() {
10888
10922
  subscriptionRepository,
10889
10923
  logger
10890
10924
  });
10925
+ let supervisorMessageSequence = 0;
10926
+ const agentMessageSequences = /* @__PURE__ */ new Map();
10891
10927
  const broadcaster = messageBroadcaster;
10892
10928
  const agentMessageAccumulator = /* @__PURE__ */ new Map();
10893
10929
  const agentStreamingMessageIds = /* @__PURE__ */ new Map();
10930
+ const STREAMING_STATE_GRACE_PERIOD_MS = 1e4;
10931
+ const agentCleanupTimeouts = /* @__PURE__ */ new Map();
10894
10932
  const getOrCreateAgentStreamingMessageId = (sessionId) => {
10895
10933
  let messageId = agentStreamingMessageIds.get(sessionId);
10896
10934
  if (!messageId) {
@@ -10900,7 +10938,16 @@ async function bootstrap() {
10900
10938
  return messageId;
10901
10939
  };
10902
10940
  const clearAgentStreamingMessageId = (sessionId) => {
10903
- agentStreamingMessageIds.delete(sessionId);
10941
+ const existingTimeout = agentCleanupTimeouts.get(sessionId);
10942
+ if (existingTimeout) {
10943
+ clearTimeout(existingTimeout);
10944
+ }
10945
+ const timeout = setTimeout(() => {
10946
+ agentStreamingMessageIds.delete(sessionId);
10947
+ agentCleanupTimeouts.delete(sessionId);
10948
+ logger.debug({ sessionId }, "Agent streaming state cleaned up after grace period");
10949
+ }, STREAMING_STATE_GRACE_PERIOD_MS);
10950
+ agentCleanupTimeouts.set(sessionId, timeout);
10904
10951
  };
10905
10952
  agentSessionManager.on(
10906
10953
  "blocks",
@@ -10974,10 +11021,14 @@ async function bootstrap() {
10974
11021
  }
10975
11022
  const mergedBlocks = mergeToolBlocks(accumulatedBlocks);
10976
11023
  const fullAccumulatedText = mergedBlocks.filter((b) => b.block_type === "text").map((b) => b.content).join("\n");
11024
+ const currentSequence = agentMessageSequences.get(sessionId) ?? 0;
11025
+ const newSequence = currentSequence + 1;
11026
+ agentMessageSequences.set(sessionId, newSequence);
10977
11027
  const outputEvent = {
10978
11028
  type: "session.output",
10979
11029
  session_id: sessionId,
10980
11030
  streaming_message_id: streamingMessageId,
11031
+ sequence: newSequence,
10981
11032
  payload: {
10982
11033
  content_type: "agent",
10983
11034
  content: fullAccumulatedText,
@@ -10988,13 +11039,19 @@ async function bootstrap() {
10988
11039
  is_complete: isComplete
10989
11040
  }
10990
11041
  };
10991
- broadcaster.broadcastToSubscribers(
11042
+ await broadcaster.broadcastToSubscribers(
10992
11043
  sessionId,
10993
11044
  JSON.stringify(outputEvent)
10994
11045
  );
10995
11046
  if (isComplete) {
10996
11047
  clearAgentStreamingMessageId(sessionId);
10997
- agentMessageAccumulator.delete(sessionId);
11048
+ setTimeout(() => {
11049
+ agentMessageAccumulator.delete(sessionId);
11050
+ logger.debug(
11051
+ { sessionId, messageId: streamingMessageId },
11052
+ "Agent message accumulator cleaned up after grace period"
11053
+ );
11054
+ }, STREAMING_STATE_GRACE_PERIOD_MS);
10998
11055
  }
10999
11056
  if (isComplete && fullTextContent.length > 0) {
11000
11057
  const pendingVoiceCommand = pendingAgentVoiceCommands.get(sessionId);
@@ -11053,7 +11110,7 @@ async function bootstrap() {
11053
11110
  from_device_id: originDeviceId
11054
11111
  }
11055
11112
  };
11056
- broadcaster.broadcastToSubscribers(
11113
+ await broadcaster.broadcastToSubscribers(
11057
11114
  sessionId,
11058
11115
  JSON.stringify(voiceOutputEvent)
11059
11116
  );
@@ -11098,6 +11155,7 @@ async function bootstrap() {
11098
11155
  const outputEvent = {
11099
11156
  type: "supervisor.output",
11100
11157
  streaming_message_id: streamingMessageId,
11158
+ sequence: ++supervisorMessageSequence,
11101
11159
  payload: {
11102
11160
  content_type: "supervisor",
11103
11161
  content: textContent,
@@ -11109,7 +11167,13 @@ async function bootstrap() {
11109
11167
  const message = JSON.stringify(outputEvent);
11110
11168
  broadcaster.broadcastToAll(message);
11111
11169
  if (isComplete) {
11112
- supervisorMessageAccumulator.clear();
11170
+ setTimeout(() => {
11171
+ supervisorMessageAccumulator.clear();
11172
+ logger.debug(
11173
+ { messageId: streamingMessageId },
11174
+ "Supervisor message accumulator cleaned up after grace period"
11175
+ );
11176
+ }, STREAMING_STATE_GRACE_PERIOD_MS);
11113
11177
  }
11114
11178
  if (isComplete && finalOutput && finalOutput.length > 0) {
11115
11179
  chatHistoryService.saveSupervisorMessage(
@@ -11238,21 +11302,23 @@ async function bootstrap() {
11238
11302
  batchIntervalMs: env.TERMINAL_BATCH_INTERVAL_MS,
11239
11303
  maxBatchSize: env.TERMINAL_BATCH_MAX_SIZE,
11240
11304
  onFlush: (batchedData) => {
11241
- const outputMessage = session.addOutputToBuffer(batchedData);
11242
- const outputEvent = {
11243
- type: "session.output",
11244
- session_id: sessionId.value,
11245
- payload: {
11246
- content_type: "terminal",
11247
- content: batchedData,
11248
- timestamp: outputMessage.timestamp,
11249
- sequence: outputMessage.sequence
11250
- }
11251
- };
11252
- broadcaster.broadcastToSubscribers(
11253
- sessionId.value,
11254
- JSON.stringify(outputEvent)
11255
- );
11305
+ void (async () => {
11306
+ const outputMessage = session.addOutputToBuffer(batchedData);
11307
+ const outputEvent = {
11308
+ type: "session.output",
11309
+ session_id: sessionId.value,
11310
+ payload: {
11311
+ content_type: "terminal",
11312
+ content: batchedData,
11313
+ timestamp: outputMessage.timestamp,
11314
+ sequence: outputMessage.sequence
11315
+ }
11316
+ };
11317
+ await broadcaster.broadcastToSubscribers(
11318
+ sessionId.value,
11319
+ JSON.stringify(outputEvent)
11320
+ );
11321
+ })();
11256
11322
  }
11257
11323
  });
11258
11324
  session.onOutput((data) => {
@@ -11388,10 +11454,8 @@ async function bootstrap() {
11388
11454
  };
11389
11455
  registerSignalHandlers(shutdown);
11390
11456
  }
11391
- if (process.stdin && !process.stdin.destroyed) {
11392
- if (process.stdin.isPaused?.()) {
11393
- process.stdin.resume();
11394
- }
11457
+ if (process.stdin.isPaused()) {
11458
+ process.stdin.resume();
11395
11459
  }
11396
11460
  bootstrap().catch((error) => {
11397
11461
  console.error("Failed to bootstrap:", error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiflis-io/tiflis-code-workstation",
3
- "version": "0.3.27",
3
+ "version": "0.3.29",
4
4
  "description": "Workstation server for tiflis-code - manages agent sessions and terminal access",
5
5
  "author": "Roman Barinov <rbarinov@gmail.com>",
6
6
  "license": "FSL-1.1-NC",