@tiflis-io/tiflis-code-workstation 0.3.28 → 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 +108 -34
  2. package/package.json +1 -1
package/dist/main.js CHANGED
@@ -5113,9 +5113,10 @@ var MessageBroadcasterImpl = class {
5113
5113
  }
5114
5114
  /**
5115
5115
  * Broadcasts a message to all clients subscribed to a session (by session ID string).
5116
- * 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.
5117
5118
  */
5118
- broadcastToSubscribers(sessionId, message) {
5119
+ async broadcastToSubscribers(sessionId, message) {
5119
5120
  const session = new SessionId(sessionId);
5120
5121
  const subscribers = this.deps.clientRegistry.getSubscribers(session);
5121
5122
  const authenticatedSubscribers = subscribers.filter(
@@ -5124,9 +5125,44 @@ var MessageBroadcasterImpl = class {
5124
5125
  if (authenticatedSubscribers.length === 0) {
5125
5126
  return;
5126
5127
  }
5127
- for (const client of authenticatedSubscribers) {
5128
- this.deps.tunnelClient.sendToDevice(client.deviceId.value, message);
5129
- }
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);
5130
5166
  }
5131
5167
  };
5132
5168
 
@@ -10132,7 +10168,7 @@ async function bootstrap() {
10132
10168
  message_id: messageId
10133
10169
  }
10134
10170
  };
10135
- messageBroadcaster.broadcastToSubscribers(
10171
+ await messageBroadcaster.broadcastToSubscribers(
10136
10172
  sessionId,
10137
10173
  JSON.stringify(errorEvent)
10138
10174
  );
@@ -10168,7 +10204,7 @@ async function bootstrap() {
10168
10204
  from_device_id: deviceId
10169
10205
  }
10170
10206
  };
10171
- messageBroadcaster.broadcastToSubscribers(
10207
+ await messageBroadcaster.broadcastToSubscribers(
10172
10208
  sessionId,
10173
10209
  JSON.stringify(transcriptionEvent)
10174
10210
  );
@@ -10259,7 +10295,7 @@ async function bootstrap() {
10259
10295
  timestamp: Date.now()
10260
10296
  }
10261
10297
  };
10262
- messageBroadcaster.broadcastToSubscribers(
10298
+ await messageBroadcaster.broadcastToSubscribers(
10263
10299
  sessionId,
10264
10300
  JSON.stringify(errorEvent)
10265
10301
  );
@@ -10387,14 +10423,20 @@ async function bootstrap() {
10387
10423
  is_complete: true
10388
10424
  }
10389
10425
  };
10390
- messageBroadcaster.broadcastToSubscribers(
10426
+ void messageBroadcaster.broadcastToSubscribers(
10391
10427
  sessionId,
10392
10428
  JSON.stringify(cancelOutput)
10393
- );
10394
- logger.info(
10395
- { sessionId },
10396
- "Broadcasted cancel message to subscribers"
10397
- );
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
+ });
10398
10440
  chatHistoryService.saveAgentMessage(sessionId, "assistant", "", [
10399
10441
  cancelBlock
10400
10442
  ]);
@@ -10880,9 +10922,13 @@ async function bootstrap() {
10880
10922
  subscriptionRepository,
10881
10923
  logger
10882
10924
  });
10925
+ let supervisorMessageSequence = 0;
10926
+ const agentMessageSequences = /* @__PURE__ */ new Map();
10883
10927
  const broadcaster = messageBroadcaster;
10884
10928
  const agentMessageAccumulator = /* @__PURE__ */ new Map();
10885
10929
  const agentStreamingMessageIds = /* @__PURE__ */ new Map();
10930
+ const STREAMING_STATE_GRACE_PERIOD_MS = 1e4;
10931
+ const agentCleanupTimeouts = /* @__PURE__ */ new Map();
10886
10932
  const getOrCreateAgentStreamingMessageId = (sessionId) => {
10887
10933
  let messageId = agentStreamingMessageIds.get(sessionId);
10888
10934
  if (!messageId) {
@@ -10892,7 +10938,16 @@ async function bootstrap() {
10892
10938
  return messageId;
10893
10939
  };
10894
10940
  const clearAgentStreamingMessageId = (sessionId) => {
10895
- 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);
10896
10951
  };
10897
10952
  agentSessionManager.on(
10898
10953
  "blocks",
@@ -10966,10 +11021,14 @@ async function bootstrap() {
10966
11021
  }
10967
11022
  const mergedBlocks = mergeToolBlocks(accumulatedBlocks);
10968
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);
10969
11027
  const outputEvent = {
10970
11028
  type: "session.output",
10971
11029
  session_id: sessionId,
10972
11030
  streaming_message_id: streamingMessageId,
11031
+ sequence: newSequence,
10973
11032
  payload: {
10974
11033
  content_type: "agent",
10975
11034
  content: fullAccumulatedText,
@@ -10980,13 +11039,19 @@ async function bootstrap() {
10980
11039
  is_complete: isComplete
10981
11040
  }
10982
11041
  };
10983
- broadcaster.broadcastToSubscribers(
11042
+ await broadcaster.broadcastToSubscribers(
10984
11043
  sessionId,
10985
11044
  JSON.stringify(outputEvent)
10986
11045
  );
10987
11046
  if (isComplete) {
10988
11047
  clearAgentStreamingMessageId(sessionId);
10989
- 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);
10990
11055
  }
10991
11056
  if (isComplete && fullTextContent.length > 0) {
10992
11057
  const pendingVoiceCommand = pendingAgentVoiceCommands.get(sessionId);
@@ -11045,7 +11110,7 @@ async function bootstrap() {
11045
11110
  from_device_id: originDeviceId
11046
11111
  }
11047
11112
  };
11048
- broadcaster.broadcastToSubscribers(
11113
+ await broadcaster.broadcastToSubscribers(
11049
11114
  sessionId,
11050
11115
  JSON.stringify(voiceOutputEvent)
11051
11116
  );
@@ -11090,6 +11155,7 @@ async function bootstrap() {
11090
11155
  const outputEvent = {
11091
11156
  type: "supervisor.output",
11092
11157
  streaming_message_id: streamingMessageId,
11158
+ sequence: ++supervisorMessageSequence,
11093
11159
  payload: {
11094
11160
  content_type: "supervisor",
11095
11161
  content: textContent,
@@ -11101,7 +11167,13 @@ async function bootstrap() {
11101
11167
  const message = JSON.stringify(outputEvent);
11102
11168
  broadcaster.broadcastToAll(message);
11103
11169
  if (isComplete) {
11104
- 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);
11105
11177
  }
11106
11178
  if (isComplete && finalOutput && finalOutput.length > 0) {
11107
11179
  chatHistoryService.saveSupervisorMessage(
@@ -11230,21 +11302,23 @@ async function bootstrap() {
11230
11302
  batchIntervalMs: env.TERMINAL_BATCH_INTERVAL_MS,
11231
11303
  maxBatchSize: env.TERMINAL_BATCH_MAX_SIZE,
11232
11304
  onFlush: (batchedData) => {
11233
- const outputMessage = session.addOutputToBuffer(batchedData);
11234
- const outputEvent = {
11235
- type: "session.output",
11236
- session_id: sessionId.value,
11237
- payload: {
11238
- content_type: "terminal",
11239
- content: batchedData,
11240
- timestamp: outputMessage.timestamp,
11241
- sequence: outputMessage.sequence
11242
- }
11243
- };
11244
- broadcaster.broadcastToSubscribers(
11245
- sessionId.value,
11246
- JSON.stringify(outputEvent)
11247
- );
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
+ })();
11248
11322
  }
11249
11323
  });
11250
11324
  session.onOutput((data) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiflis-io/tiflis-code-workstation",
3
- "version": "0.3.28",
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",